目錄介紹
- 1.關(guān)于Retrofit基本介紹
- 2.最簡單使用【配合Rx使用】
- 3.注解的種類
- 請求方法注解
- 請求頭注解
- 標記注解
- 參數(shù)注解
- 其它注解
- 4.Retrofit相關(guān)請求參數(shù)
- @Query()【備注:get請求/ 接上參數(shù) 】
- @QueryMap()【備注:get請求/ 接上參數(shù) 】
- @Path()【備注:get請求/ 替換url中某個字段】
- @Body()【備注:post請求/ 指定一個對象作為HTTP請求體】
- @Field()【備注:post請求/ 用于傳送表單數(shù)據(jù)】
- @FieldMap()【備注:post請求/ 用于傳送表單數(shù)據(jù)】
- @Header/@Headers()【備注: 添加請求頭部 】
- @Part()作用于方法的參數(shù),用于定義Multipart請求的每和part
- @PartMap()作用于方法的參數(shù)
- 使用時注意事項
- 5.Retrofit與RxJava結(jié)合
- 使Rxjava與retrofit結(jié)合條件
- 可以看到 Observable觀察者
- 可以看到訂閱者
- 6.OkHttpClient
- 攔截器說明
- 日志攔截器
- 請求頭攔截器
- 統(tǒng)一請求攔截器
- 緩存攔截器
- 自定義CookieJar
- 7.踩坑經(jīng)驗
- 8.Form表單提交與multipart/form-data
- 8.1 form表單常用屬性
- 8.2 瀏覽器提交表單時程储,會執(zhí)行如下步驟
- 8.3 提交方式
- 8.4 POST請求
- 8.5 enctype指定的content-type
- 9.content-type介紹
- 9.1 application/x-www-form-urlencoded
- 9.2 application/json
- 9.3 text/xml
- 9.4 multipart/form-data
- 10.Retrofit源碼深入分析
- 10.1 設(shè)計模式分析[建造者模式]
- 10.2 如何理解動態(tài)代理模式
- 10.3 如何攔截方法傀蚌,解析注解
- 10.4 如何構(gòu)建Retrofit的Call
- 10.5 如何執(zhí)行網(wǎng)絡(luò)異步請求enqueue方法
- N.關(guān)于其他
好消息
- 博客筆記大匯總【16年3月到至今】衣形,包括Java基礎(chǔ)及深入知識點寇蚊,Android技術(shù)博客空入,Python學習筆記等等义黎,還包括平時開發(fā)中遇到的bug匯總诉瓦,當然也在工作之余收集了大量的面試題冷尉,長期更新維護并且修正漱挎,持續(xù)完善……開源的文件是markdown格式的!同時也開源了生活博客雀哨,從12年起磕谅,積累共計47篇[近20萬字],轉(zhuǎn)載請注明出處雾棺,謝謝膊夹!
- 鏈接地址:https://github.com/yangchong211/YCBlogs
- 如果覺得好,可以star一下捌浩,謝謝放刨!當然也歡迎提出建議,萬事起于忽微尸饺,量變引起質(zhì)變进统!
1.關(guān)于Retrofit基本介紹
- Retrofit是Square 公司開發(fā)的一款正對Android 網(wǎng)絡(luò)請求的框架。底層基于OkHttp 實現(xiàn)浪听,OkHttp 已經(jīng)得到了google 官方的認可螟碎。
- Retrofit是由Square公司出品的針對于Android和Java的類型安全的Http客戶端,如果看源碼會發(fā)現(xiàn)其實本質(zhì)上是OkHttp的封裝迹栓,使用面向接口的方式進行網(wǎng)絡(luò)請求掉分,利用動態(tài)生成的代理類封裝了網(wǎng)絡(luò)接口請求的底層,其將請求返回JavaBean迈螟,對網(wǎng)絡(luò)認證REST API進行了很友好的支持叉抡。使用Retrofit將會極大的提高我們應用的網(wǎng)絡(luò)體驗。
- RxJava + Retrofit + okHttp組合答毫,流行的網(wǎng)絡(luò)請求框架
- Retrofit 負責請求的數(shù)據(jù)和請求的結(jié)果褥民,使用接口的方式呈現(xiàn),OkHttp 負責請求的過程洗搂,RxJava 負責異步消返,各種線程之間的切換。
- RxJava 在 GitHub 主頁上的自我介紹是 "a library for composing asynchronous and event-based programs using observable sequences for the Java VM"(一個在 Java VM 上使用可觀測的序列來組成異步的耘拇、基于事件的程序的庫)撵颊。這就是 RxJava ,概括得非常精準惫叛〕拢總之就是讓異步操作變得非常簡單。
- 為什么要使用Retrofit嘉涌?
- 優(yōu)點
- 請求的方法參數(shù)注解可以定制
- 支持同步妻熊、異步和RxJava
- 超級解耦
- 可以配置不同的反序列化工具來解析數(shù)據(jù)夸浅,如json、xml等
- 其他說明
- 在處理HTTP請求的時候扔役,因為不同場景或者邊界情況等比較難處理帆喇。你需要考慮網(wǎng)絡(luò)狀態(tài),需要在請求失敗后重試亿胸,需要處理HTTPS等問題坯钦,二這些事情讓你很苦惱,而Retrofit可以將你從這些頭疼的事情中解放出來侈玄。
- 效率高婉刀,其次Retrofit強大且配置靈活,第三和OkHttp無縫銜接序仙,第四Jack Wharton主導的(你懂的)路星。
2.最簡單使用
public interface DouBookApi {
/**
* 根據(jù)tag獲取圖書
* @param tag 搜索關(guān)鍵字
* @param count 一次請求的數(shù)目 最多100
* https://api.douban.com/v2/book/search?tag=文學&start=0&count=30
*/
@GET("v2/book/search")
Observable<DouBookBean> getBook(@Query("tag") String tag,
@Query("start") int start,
@Query("count") int count);
}
public class DouBookModel {
private static DouBookModel bookModel;
private DouBookApi mApiService;
public DouBookModel(Context context) {
mApiService = RetrofitWrapper
.getInstance(ConstantALiYunApi.API_DOUBAN) //baseUrl地址
.create(DouBookApi.class);
}
public static DouBookModel getInstance(Context context){
if(bookModel == null) {
bookModel = new DouBookModel(context);
}
return bookModel;
}
public Observable<DouBookBean> getHotMovie(String tag, int start , int count) {
Observable<DouBookBean> book = mApiService.getBook(tag, start, count);
return book;
}
}
public class RetrofitWrapper {
private static RetrofitWrapper instance;
private Retrofit mRetrofit;
public RetrofitWrapper(String url) {
OkHttpClient.Builder builder = new OkHttpClient.Builder();
//打印日志
HttpLoggingInterceptor logging = new HttpLoggingInterceptor();
logging.setLevel(HttpLoggingInterceptor.Level.BODY);
builder.addInterceptor(logging).build();
OkHttpClient client = builder.addInterceptor(new LogInterceptor("HTTP")).build();
//解析json
Gson gson = new GsonBuilder()
.setLenient()
.create();
mRetrofit = new Retrofit
.Builder()
.baseUrl(url)
.addConverterFactory(GsonConverterFactory.create(gson))
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.client(client)
.build();
}
public static RetrofitWrapper getInstance(String url){
//synchronized 避免同時調(diào)用多個接口,導致線程并發(fā)
synchronized (RetrofitWrapper.class){
instance = new RetrofitWrapper(url);
}
return instance;
}
public <T> T create(final Class<T> service) {
return mRetrofit.create(service);
}
}
DouBookModel model = DouBookModel.getInstance(activity);
model.getHotMovie(mType,start,count)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Subscriber<DouBookBean>() {
@Override
public void onCompleted() {
}
@Override
public void onError(Throwable e) {
}
@Override
public void onNext(DouBookBean bookBean) {
}
});
3.注解的種類
@GET get請求
@POST post請求
@PUT put請求
@DELETE delete請求
@PATCH patch請求诱桂,該請求是對put請求的補充洋丐,用于更新局部資源
@HEAD head請求
@OPTIONS option請求
@HTTP 通用注解,可以替換以上所有的注解,其擁有三個屬性:method挥等,path友绝,hasBody
@Headers 用于添加固定請求頭,可以同時添加多個肝劲。通過該注解添加的請求頭不會相互覆蓋迁客,而是共同存在
@Header 作為方法的參數(shù)傳入,用于添加不固定值的Header辞槐,該注解會更新已有的請求頭
@FormUrlEncoded
表示請求發(fā)送編碼表單數(shù)據(jù)掷漱,每個鍵值對需要使用@Field注解
用于修飾Fiedl注解 和FileldMap注解
使用該注解,表示請求正文將使用表單網(wǎng)址編碼榄檬。字段應該聲明為參數(shù)卜范,并用@Field 注解和 @FieldMap 注解鹿榜,使用@FormUrlEncoded 注解的請求將具有"application/x-www-form-urlencoded" MIME類型舱殿。字段名稱和值將先進行UTF-8進行編碼奥裸,再根據(jù)RFC-3986進行URI編碼。
@Multipart
作用于方法
表示請求發(fā)送multipart數(shù)據(jù)沪袭,使用該注解湾宙,表示請求體是多部分的,每個部分作為一個參數(shù),且用Part注解聲明侠鳄。
@Streaming
作用于方法
未使用@Straming 注解嗡害,默認會把數(shù)據(jù)全部載入內(nèi)存畦攘,之后通過流獲取數(shù)據(jù)也是讀取內(nèi)存中數(shù)據(jù)知押,所以返回數(shù)據(jù)較大時鹃骂,需要使用該注解台盯。
處理返回Response的方法的響應體,用于下載大文件
提醒:如果是下載大文件必須加上@Streaming 否則會報OOM
@Streaming
@GET
Call<ResponseBody> downloadFileWithDynamicUrlAsync(@Url String fileUrl);
參數(shù)注解:@Query 静盅、@QueryMap市咽、@Body、@Field、@FieldMap躺酒、@Part劫灶、@PartMap
@Path、@Url
4.Retrofit相關(guān)請求參數(shù)
- @Query()【備注:get請求/ 接上參數(shù) 】
@Query:作用于方法參數(shù)掖桦,用于添加查詢參數(shù)本昏,即請求參數(shù)
用于在url后拼接上參數(shù),例如:
@GET("book/search")
Call<Book> getSearchBook(@Query("q") String name);//name由調(diào)用者傳入
相當于
@GET("book/search?q=name")
Call<Book> getSearchBook();
用于Get中指定參數(shù)
- @QueryMap()【備注:get請求/ 接上參數(shù) 】
@QueryMap:作用于方法的參數(shù)枪汪。以map的形式添加查詢參數(shù)雀久,即請求參數(shù)宿稀,參數(shù)的鍵和值都通過String.valueOf()轉(zhuǎn)換為String格式。默認map的值進行URL編碼赖捌,map中的每一項發(fā)鍵和值都不能為空祝沸,否則跑出IllegalArgumentException異常。
當然如果入?yún)⒈容^多越庇,就可以把它們都放在Map中罩锐,例如:
@GET("book/search")
Call<Book> getSearchBook(@QueryMap Map<String, String> options);
- @Path()【備注:get請求/ 替換url中某個字段】
/**
* http://api.zhuishushenqi.com/ranking/582ed5fc93b7e855163e707d
* @return
*/
@GET("/ranking/{rankingId}")
Observable<SubHomeTopBean> getRanking(@Path("rankingId") String rankingId);
@GET("group/{id}/users")
Call<Book> groupList(@Path("id") int groupId);
* 像這種請求接口,在group和user之間有個不確定的id值需要傳入卤唉,就可以這種方法唯欣。我們把待定的值字段用{}括起來,當然 {}里的名字不一定就是id搬味,可以任取境氢,但需和@Path后括號里的名字一樣。如果在user后面還需要傳入?yún)?shù)的話碰纬,就可以用Query拼接上萍聊,比如:
@GET("group/{id}/users")
Call<Book> groupList(@Path("id") int groupId, @Query("sort") String sort);
* 當我們調(diào)用這個方法時,假設(shè)我們groupId傳入1悦析,sort傳入“2”寿桨,那么它拼接成的url就是group/1/users?sort=2,當然最后請求的話還會加上前面的baseUrl
- @Body()【備注:post請求/ 指定一個對象作為HTTP請求體】
使用@Body 注解定義的參數(shù)不能為null 强戴。當你發(fā)送一個post或put請求亭螟,但是又不想作為請求參數(shù)或表單的方式發(fā)送請求時,使用該注解定義的參數(shù)可以直接傳入一個實體類,retrofit會通過convert把該實體序列化并將序列化的結(jié)果直接作為請求體發(fā)送出去。
可以指定一個對象作為HTTP請求體,比如:
@POST("users/new")
Call<User> createUser(@Body User user);
它會把我們傳入的User實體類轉(zhuǎn)換為用于傳輸?shù)腍TTP請求體当纱,進行網(wǎng)絡(luò)請求。
多用于post請求發(fā)送非表單數(shù)據(jù),比如想要以post方式傳遞json格式數(shù)據(jù)
- @Field()【備注:post請求/ 用于傳送表單數(shù)據(jù)】
用于傳送表單數(shù)據(jù):
@FormUrlEncoded
@POST("user/edit")
Call<User> updateUser(@Field("first_name") String first, @Field("last_name") String last);
注意開頭必須多加上@FormUrlEncoded這句注釋扁掸,不然會報錯翘县。表單自然是有多組鍵值對組成,這里的first_name就是鍵谴分,而具體傳入的first就是值啦
多用于post請求中表單字段,Filed和FieldMap需要FormUrlEncoded結(jié)合使用
- @FieldMap()【備注:post請求/ 用于傳送表單數(shù)據(jù)】
@FormUrlEncoded
@POST("user/login")
Call<User> login(@FieldMap Map<String,String> map);
- @Header/@Headers()【備注: 添加請求頭部 】
用于動態(tài)添加請求頭部:
@GET("user")
Call<User> getUser(@Header("Authorization") String authorization)
表示將頭部Authorization屬性設(shè)置為你傳入的authorization锈麸;當然你還可以用@Headers表示,作用是一樣的比如:
@Headers("Cache-Control: max-age=640000")
@GET("user")
Call<User> getUser()
當然你可以多個設(shè)置:
@Headers({
"Accept: application/vnd.github.v3.full+json",
"User-Agent: Retrofit-Sample-App"
})
@GET("user")
Call<User> getUser()
- @Part()作用于方法的參數(shù),用于定義Multipart請求的每和part
使用該注解定義的參數(shù)牺蹄,參數(shù)值可以為空忘伞,為空時,則忽略沙兰。使用該注解定義的參數(shù)類型有如下3中方式可選:
1 okhttp2.MulitpartBody.Part氓奈,內(nèi)容將被直接使用。省略part中的名稱僧凰,即@Part MultipartBody.Part part
2 如果類型是RequestBody,那么該值直接與其內(nèi)容類型一起使用熟丸。在注釋中提供part名稱(例如训措,@Part("foo") RequestBody foo)
3 其它對象類型將通過使用轉(zhuǎn)換器轉(zhuǎn)換為適當?shù)母袷健T谧⑨屩刑峁﹑art名稱(例如光羞,@Part("foo") Image photo)绩鸣。
@Multipart
@POST("/")
Call<ResponseBody> example(
@Part("description") String description,
@Part(value = "image", encoding = "8-bit") RequestBody image);
以map的方式定義Multipart請求的每個part map中每一項的鍵和值都不能為空,否則拋出IllegalArgumentException異常纱兑。
使用@PartMap 注解定義的參數(shù)類型有一下兩種:
1 如果類型是RequestBody呀闻,那么該值將直接與其內(nèi)容類型與其使用。
2 其它對象類型將通過使用轉(zhuǎn)換器轉(zhuǎn)換為適當?shù)母袷健?
使用時注意事項
- 1潜慎、Map用來組合復雜的參數(shù)捡多,并且對于FieldMap,HeaderMap铐炫,PartMap垒手,QueryMap這四種作用方法的注解,其參數(shù)類型必須為Map實例倒信,且key的類型必須為String類型科贬,否則拋出異常。
- 2鳖悠、Query榜掌、QueryMap與Field、FieldMap功能一樣乘综,生成的數(shù)據(jù)形式一樣憎账;Query、QueryMap的數(shù)據(jù)體現(xiàn)在Url上卡辰;Field鼠哥、FieldMap的數(shù)據(jù)是請求體
- 3熟菲、{占位符}和PATH盡量只用在URL的path部分,url的參數(shù)使用Query朴恳、QueryMap代替抄罕,保證接口的簡潔
- 4、Query于颖、Field呆贿、Part支持數(shù)據(jù)和實現(xiàn)了iterable接口的類型,如List森渐、Set等做入,方便向后臺傳遞數(shù)組,代碼如下:
- 5、以上部分注解真正的實現(xiàn)在ParameterHandler類中同衣,每個注解的真正實現(xiàn)都是ParameterHandler類中的一個final類型的內(nèi)部類竟块,每個內(nèi)部類都對各個注解的使用要求做了限制,比如參數(shù)是否可空耐齐、鍵和值是否可空等浪秘。
- 6、@FormUrlEncoded 注解和@Multipart 注解不能同時使用埠况,否則會拋出methodError(“Only one encoding annotation is allowed.”)耸携,可在ServiceMethod類中parseMethodAnnotation()方法中找到不能同時使用的具體原因。
- 7辕翰、@Path 與@Url 注解不能同時使用夺衍,否則會拋出parameterError(p, "@Path parameters may not be used with @Url."),可在ServcieMethod類中parseParameterAnnotation()方法中找到不能同時使用的具體代碼喜命。其實原因也是很好理解:Path注解用于替換url中的參數(shù)沟沙,這就要求在使用path注解時,必須已經(jīng)存在請求路徑壁榕。不然沒法替換路徑中指定的參數(shù)尝胆。而@Url 注解是在參數(shù)中指定了請求路徑的,這時候情定請求路徑已經(jīng)晚护桦,path注解找不到請求路徑含衔,更別提更換請求路徑了中的參數(shù)了。
- 8二庵、使用@Body 注解的參數(shù)不能使用form 或multi-part編碼贪染,即如果為方法使用了FormUrlEncoded或Multipart注解,則方法的參數(shù)中不能使用@Body 注解催享,否則會拋出異常parameterError(p, “@Body parameters cannot be used with form or multi-part encoding.”)
5.Retrofit與RxJava結(jié)合
使Rxjava與retrofit結(jié)合條件
- 在Retrofit對象建立的時候添加一句代碼addCallAdapterFactory(RxJavaCallAdapterFactory.create())
完整代碼
mRetrofit = new Retrofit
.Builder()
.baseUrl(url)
.addConverterFactory(GsonConverterFactory.create(gson))
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.client(client)
.build();
public Observable<DouBookBean> getHotMovie(String tag, int start , int count) {
Observable<DouBookBean> book = mApiService.getBook(tag, start, count);
return book;
}
- 可以看到訂閱者
- RxAndroid其實就是對RxJava的擴展杭隙。比如上面這個Android主線程在RxJava中就沒有,因此要使用的話就必須得引用RxAndroid
DouBookModel model = DouBookModel.getInstance(activity);
model.getHotMovie(mType,start,count)
.subscribeOn(Schedulers.io()) //請求數(shù)據(jù)的事件發(fā)生在io線程
.observeOn(AndroidSchedulers.mainThread()) //請求完成后在主線程更顯UI
.subscribe(new Observer<DouBookBean>() { //訂閱
@Override
public void onCompleted() {
//所有事件都完成因妙,可以做些操作痰憎。票髓。
}
@Override
public void onError(Throwable e) {
e.printStackTrace(); //請求過程中發(fā)生錯誤
}
@Override
public void onNext(DouBookBean bookBean) {
//這里的book就是我們請求接口返回的實體類
}
});
6.OkHttpClient
- 攔截器說明
- addNetworkInterceptor添加的是網(wǎng)絡(luò)攔截器Network,Interfacetor它會在request和response時分別被調(diào)用一次铣耘;
- addInterceptor添加的是應用攔截器Application Interceptor他只會在response被調(diào)用一次洽沟。
- 日志攔截器
- 一種是使用HttpLoggingInterceptor,需要使用到依賴
compile 'com.squareup.okhttp3:logging-interceptor:3.5.0'
/**
* 創(chuàng)建日志攔截器
* @return
*/
public static HttpLoggingInterceptor getHttpLoggingInterceptor() {
HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor(new HttpLoggingInterceptor.Logger() {
@Override
public void log(String message) {
Log.e("OkHttp", "log = " + message);
}
});
loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
return loggingInterceptor;
}
- 另一種是創(chuàng)建自定義日志攔截器
- 請求頭攔截器
/**
* 請求頭攔截器
* 使用addHeader()不會覆蓋之前設(shè)置的header,若使用header()則會覆蓋之前的header
* @return
*/
public static Interceptor getRequestHeader() {
Interceptor headerInterceptor = new Interceptor() {
@Override
public okhttp3.Response intercept(Chain chain) throws IOException {
Request originalRequest = chain.request();
Request.Builder builder = originalRequest.newBuilder();
builder.addHeader("version", "1");
builder.addHeader("time", System.currentTimeMillis() + "");
Request.Builder requestBuilder = builder.method(originalRequest.method(), originalRequest.body());
Request request = requestBuilder.build();
return chain.proceed(request);
}
};
return headerInterceptor;
}
使用addInterceptor()方法添加到OkHttpClient中
我的理解是,請求頭攔截器是為了讓服務端能更好的識別該請求,服務器那邊通過請求頭判斷該請求是否為有效請求等...
- 統(tǒng)一請求攔截器
- 使用addInterceptor()方法添加到OkHttpClient中蜗细,統(tǒng)一請求攔截器的功能跟請求頭攔截器相類似
/**
* 統(tǒng)一請求攔截器
* 統(tǒng)一的請求參數(shù)
*/
public static Interceptor commonParamsInterceptor() {
Interceptor commonParams = new Interceptor() {
@Override
public Response intercept(Chain chain) throws IOException {
Request originRequest = chain.request();
Request request;
HttpUrl httpUrl = originRequest.url().newBuilder()
.addQueryParameter("paltform", "android")
.addQueryParameter("version", "1.0.0")
.build();
request = originRequest.newBuilder()
.url(httpUrl)
.build();
return chain.proceed(request);
}
};
return commonParams;
}
- 緩存攔截器
- 使用okhttp緩存的話,先要創(chuàng)建Cache,然后在創(chuàng)建緩存攔截器
OkHttpClient.Builder builder = new OkHttpClient.Builder();
//添加緩存攔截器
//創(chuàng)建Cache
File httpCacheDirectory = new File("OkHttpCache");
Cache cache = new Cache(httpCacheDirectory, 10 * 1024 * 1024);
builder.cache(cache);
//設(shè)置緩存
builder.addNetworkInterceptor(InterceptorUtils.getCacheInterceptor());
builder.addInterceptor(InterceptorUtils.getCacheInterceptor());
- 緩存攔截器裆操, 緩存時間自己根據(jù)情況設(shè)定
/**
* 在無網(wǎng)絡(luò)的情況下讀取緩存,有網(wǎng)絡(luò)的情況下根據(jù)緩存的過期時間重新請求
* @return
*/
public static Interceptor getCacheInterceptor() {
Interceptor commonParams = new Interceptor() {
@Override
public okhttp3.Response intercept(Chain chain) throws IOException {
Request request = chain.request();
if (!NetworkUtils.isConnected()) {
//無網(wǎng)絡(luò)下強制使用緩存炉媒,無論緩存是否過期,此時該請求實際上不會被發(fā)送出去踪区。
request = request.newBuilder()
.cacheControl(CacheControl.FORCE_CACHE)
.build();
}
Response response = chain.proceed(request);
if (NetworkUtils.isConnected()) {
//有網(wǎng)絡(luò)情況下,根據(jù)請求接口的設(shè)置吊骤,配置緩存缎岗。
// 這樣在下次請求時,根據(jù)緩存決定是否真正發(fā)出請求白粉。
String cacheControl = request.cacheControl().toString();
//當然如果你想在有網(wǎng)絡(luò)的情況下都直接走網(wǎng)絡(luò)传泊,那么只需要
//將其超時時間這是為0即可:String cacheControl="Cache-Control:public,max-age=0"
int maxAge = 60 * 60;
// read from cache for 1 minute
return response.newBuilder()
.header("Cache-Control", cacheControl)
.header("Cache-Control", "public, max-age=" + maxAge)
.removeHeader("Pragma") .build();
} else { //無網(wǎng)絡(luò)
int maxStale = 60 * 60 * 24 * 28; // tolerate 4-weeks stale
return response.newBuilder()
.header("Cache-Control", "public,only-if-cached,max-stale=360000")
.header("Cache-Control", "public,only-if-cached,max-stale=" + maxStale)
.removeHeader("Pragma") .build();
}
}
};
return commonParams;
}
/**
* 自定義CookieJar
* @param builder
*/
public static void addCookie(OkHttpClient.Builder builder){
builder.cookieJar(new CookieJar() {
private final HashMap<HttpUrl, List<Cookie>> cookieStore = new HashMap<>();
@Override
public void saveFromResponse(HttpUrl url, List<Cookie> cookies) {
cookieStore.put(url, cookies);
//保存cookie //也可以使用SP保存
}
@Override
public List<Cookie> loadForRequest(HttpUrl url) {
List<Cookie> cookies = cookieStore.get(url);
//取出cookie
return cookies != null ? cookies : new ArrayList<Cookie>();
}
});
}
7.踩坑經(jīng)驗
http://api.mydemo.com/api%2Fnews%2FnewsList?
罪魁禍首@Url與@Path注解,我們開發(fā)過程中,肯定會需要動態(tài)的修改請求地址
兩種動態(tài)修改方式如下:
@POST()
Call<HttpResult<News>> post(@Url String url, @QueryMap Map<String, String> map);
@POST("api/{url}/newsList")
Call<HttpResult<News>> login(@Path("url") String url, @Body News post);
第一種是直接使用@Url,它相當于直接替換了@POST()里面的請求地址
第二種是使用@Path("url"),它只替換了@POST("api/{url}/newsList")中的{url}
如果你用下面這樣寫的話,就會出現(xiàn)url被轉(zhuǎn)義
@POST("{url}")
Call<HttpResult<News>> post(@Path("url") String url);
你如果執(zhí)意要用@Path,也不是不可以,需要這樣寫
@POST("{url}")
Call<HttpResult<News>> post(@Path(value = "url", encoded = true) String url);
8.Form表單提交與multipart/form-data
8.1 form表單常用屬性
- action:url 地址,服務器接收表單數(shù)據(jù)的地址
- method:提交服務器的http方法蜗元,一般為post和get
- name:最好好吃name屬性的唯一性
- enctype: 表單數(shù)據(jù)提交時使用的編碼類型或渤,默認使用"pplication/x-www-form-urlencoded"系冗,如果是使用POST請求奕扣,則請求頭中的content-type指定值就是該值。如果表單中有上傳文件掌敬,編碼類型需要使用"multipart/form-data"惯豆,類型,才能完成傳遞文件數(shù)據(jù)奔害。
8.2 瀏覽器提交表單時楷兽,會執(zhí)行如下步驟
- 識別出表單中表單元素的有效項,作為提交項
- 構(gòu)建一個表單數(shù)據(jù)集
- 根據(jù)form表單中的enctype屬性的值作為content-type對數(shù)據(jù)進行編碼
- 根據(jù)form表單中的action屬性和method屬性向指定的地址發(fā)送數(shù)據(jù)
8.3 提交方式
- get:表單數(shù)據(jù)會被encodeURIComponent后以參數(shù)的形式:name1=value1&name2=value2 附帶在url?后面华临,再發(fā)送給服務器芯杀,并在url中顯示出來。
- post:content-type 默認"application/x-www-form-urlencoded"對表單數(shù)據(jù)進行編碼雅潭,數(shù)據(jù)以鍵值對在http請求體重發(fā)送給服務器揭厚;如果enctype 屬性為"multipart/form-data",則以消息的形式發(fā)送給服務器扶供。
8.4 POST請求
- HTTP/1.1 協(xié)議規(guī)定的HTTP請求方法有OPTIONS筛圆、GET、HEAD椿浓、POST太援、PUT闽晦、DELETE、TRACE提岔、CONNECT 這幾種仙蛉。其中POST一般用于向服務器提交數(shù)據(jù)。
- 大家知道唧垦,HTTP協(xié)議是以ASCII 碼傳輸捅儒,建立在TCP/IP協(xié)議之上的應用層規(guī)范。規(guī)范把HTTP請求分為3大塊:狀態(tài)行振亮、請求頭巧还、消息體。類似于如下:
<method> <request-URL> <version>
<headers>
<entity-body>
- 協(xié)議規(guī)定POST提交的數(shù)據(jù)必須放在消息主題(entity-body)中坊秸,但協(xié)議并沒有規(guī)定數(shù)據(jù)必須使用什么編碼方式麸祷。實際上,開發(fā)者可以自己決定消息體的格式褒搔,只要后面發(fā)送的HTTP請求滿足上面的格式就可以了阶牍。
- 但是,數(shù)據(jù)發(fā)送出去后星瘾,還要服務器解析成功才有意義走孽。一般服務器都內(nèi)置了自動解析常見數(shù)據(jù)格式的功能。服務端通常是根據(jù)請求頭(headers)中的Content-Type字段來獲知請求中的消息主體是用何種方式編碼琳状,再對主體進行解析磕瓷。所以說到POST提交數(shù)據(jù)方法,包含了Content-Type和消息主題編碼方式兩部分念逞。
8.5 enctype指定的content-type
- application/x-www-form-urlencoded
- application/json
- text/xml
- multipart/form-data
9.content-type介紹
9.1 application/x-www-form-urlencoded
9.2 application/json
- application/json 這個Content-Type作為響應頭大家肯定不陌生垛贤。事實上現(xiàn)在已經(jīng)基本都是都是這種方式了,來通知服務器消息體是序列化后的JSON字符串痴晦。由于JSON規(guī)范的流行南吮,除了低版本的IE之外的現(xiàn)在主流瀏覽器都原生支持JSON。當然服務器也有處理JSON的函數(shù)誊酌。
- JSON格式支持比鍵值對更復雜的結(jié)構(gòu)化數(shù)據(jù)部凑,這樣點也很有用露乏,在需要提交數(shù)據(jù)層次非常深的數(shù)據(jù)時,用JSON序列化之后提交涂邀,非常方便瘟仿。
POST http://www.hao123.com/ HTTP/1.1
Content-Type: application/json;charset=utf-8
{"title":"test","sub":[1,2,3]}
- 這種方案,可以很方便的提交復雜的結(jié)構(gòu)化的數(shù)據(jù)比勉,特別適合RESTful的接口劳较。而且各大抓包工具如chrome自帶的開發(fā)者工具,F(xiàn)irebug浩聋、Fidder观蜗,都會以樹形結(jié)構(gòu)展示JSON數(shù)據(jù),非常友好衣洁。
9.3 text/xml
9.4 multipart/form-data
- 在最初的http協(xié)議中羽杰,沒有定義上傳文件的Method, 為了實現(xiàn)這個功能,http協(xié)議組改造了post請求瞭稼,添加一種post規(guī)范忽洛,設(shè)定這種規(guī)范的Content-Type為multipart/form-data;boundary=
{bound}是定義分割符环肘,用于分割各項內(nèi)容(文件,key-value對)集灌,不然服務器無法正確識別各項內(nèi)容悔雹。post body里需要用到,盡量保證隨機唯一欣喧。
- 這又是一個常見的POST數(shù)據(jù)提交的方式腌零。我們使用表單上傳文件時,必須讓form表單enctype等于multipart/form-data唆阿。
<form action="/upload" enctype="multipart/form-data" method="post">
Username: <input type="text" name="username">
Password: <input type="password" name="password">
File: <input type="file" name="file">
<input type="submit">
</form>
關(guān)于其他
參考博客
版本更新說明
- V1.0.1 更新2017年3月18日
- V1.0.2 更新2017年5月21日
- V1.0.3 更新2017年10月12日
- V2.0.0 更新2018年2月23日