3.註解的種類java
4.Retrofit相關請求參數android
5.Retrofit與RxJava結合git
6.OkHttpClientgithub
7.踩坑經驗chrome
8.Form表單提交與multipart/form-datajson
9.content-type介紹設計模式
10.Retrofit源碼深刻分析api
N.關於其餘數組
RxJava + Retrofit + okHttp組合,流行的網絡請求框架瀏覽器
爲何要使用Retrofit?
優勢
其餘說明
在處理HTTP請求的時候,由於不一樣場景或者邊界狀況等比較難處理。你須要考慮網絡狀態,須要在請求失敗後重試,須要處理HTTPS等問題,二這些事情讓你很苦惱,而Retrofit能夠將你從這些頭疼的事情中解放出來。
public interface DouBookApi { /** * 根據tag獲取圖書 * @param tag 搜索關鍵字 * @param count 一次請求的數目 最多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 避免同時調用多個接口,致使線程併發 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) { } });
@GET get請求 @POST post請求 @PUT put請求 @DELETE delete請求 @PATCH patch請求,該請求是對put請求的補充,用於更新局部資源 @HEAD head請求 @OPTIONS option請求 @HTTP 通用註解,能夠替換以上全部的註解,其擁有三個屬性:method,path,hasBody
@Headers 用於添加固定請求頭,能夠同時添加多個。經過該註解添加的請求頭不會相互覆蓋,而是共同存在 @Header 做爲方法的參數傳入,用於添加不固定值的Header,該註解會更新已有的請求頭
@FormUrlEncoded 表示請求發送編碼表單數據,每一個鍵值對須要使用@Field註解 用於修飾Fiedl註解 和FileldMap註解 使用該註解,表示請求正文將使用表單網址編碼。字段應該聲明爲參數,並用@Field 註解和 @FieldMap 註解,使用@FormUrlEncoded 註解的請求將具備"application/x-www-form-urlencoded" MIME類型。字段名稱和值將先進行UTF-8進行編碼,再根據RFC-3986進行URI編碼。 @Multipart 做用於方法 表示請求發送multipart數據,使用該註解,表示請求體是多部分的,每一個部分做爲一個參數,且用Part註解聲明。 @Streaming 做用於方法 未使用@Straming 註解,默認會把數據所有載入內存,以後經過流獲取數據也是讀取內存中數據,因此返回數據較大時,須要使用該註解。 處理返回Response的方法的響應體,用於下載大文件 提醒:若是是下載大文件必須加上@Streaming 不然會報OOM @Streaming @GET Call<ResponseBody> downloadFileWithDynamicUrlAsync(@Url String fileUrl);
參數註解:@Query 、@QueryMap、@Body、@Field、@FieldMap、@Part、@PartMap
@Path、@Url
@Query:做用於方法參數,用於添加查詢參數,即請求參數 用於在url後拼接上參數,例如: @GET("book/search") Call<Book> getSearchBook(@Query("q") String name);//name由調用者傳入 至關於 @GET("book/search?q=name") Call<Book> getSearchBook(); 用於Get中指定參數
@QueryMap:做用於方法的參數。以map的形式添加查詢參數,即請求參數,參數的鍵和值都經過String.valueOf()轉換爲String格式。默認map的值進行URL編碼,map中的每一項發鍵和值都不能爲空,不然跑出IllegalArgumentException異常。 固然若是入參比較多,就能夠把它們都放在Map中,例如: @GET("book/search") Call<Book> getSearchBook(@QueryMap Map<String, String> options);
/** * 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後面還須要傳入參數的話,就能夠用Query拼接上,好比: @GET("group/{id}/users") Call<Book> groupList(@Path("id") int groupId, @Query("sort") String sort); * 當咱們調用這個方法時,假設咱們groupId傳入1,sort傳入「2」,那麼它拼接成的url就是group/1/users?sort=2,固然最後請求的話還會加上前面的baseUrl
使用@Body 註解定義的參數不能爲null 。當你發送一個post或put請求,可是又不想做爲請求參數或表單的方式發送請求時,使用該註解定義的參數能夠直接傳入一個實體類,retrofit會經過convert把該實體序列化並將序列化的結果直接做爲請求體發送出去。 能夠指定一個對象做爲HTTP請求體,好比: @POST("users/new") Call<User> createUser(@Body User user); 它會把咱們傳入的User實體類轉換爲用於傳輸的HTTP請求體,進行網絡請求。 多用於post請求發送非表單數據,好比想要以post方式傳遞json格式數據
用於傳送表單數據: @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結合使用
@FormUrlEncoded @POST("user/login") Call<User> login(@FieldMap Map<String,String> map);
用於動態添加請求頭部: @GET("user") Call<User> getUser(@Header("Authorization") String authorization) 表示將頭部Authorization屬性設置爲你傳入的authorization;固然你還能夠用@Headers表示,做用是同樣的好比: @Headers("Cache-Control: max-age=640000") @GET("user") Call<User> getUser() 固然你能夠多個設置: @Headers({ "Accept: application/vnd.github.v3.full+json", "User-Agent: Retrofit-Sample-App" }) @GET("user") Call<User> getUser()
使用該註解定義的參數,參數值能夠爲空,爲空時,則忽略。使用該註解定義的參數類型有以下3中方式可選: 1 okhttp2.MulitpartBody.Part,內容將被直接使用。省略part中的名稱,即@Part MultipartBody.Part part 2 若是類型是RequestBody,那麼該值直接與其內容類型一塊兒使用。在註釋中提供part名稱(例如,@Part("foo") RequestBody foo) 3 其它對象類型將經過使用轉換器轉換爲適當的格式。在註釋中提供part名稱(例如,@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 註解定義的參數類型有一下兩種: 1 若是類型是RequestBody,那麼該值將直接與其內容類型與其使用。 2 其它對象類型將經過使用轉換器轉換爲適當的格式。
完整代碼 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; }
能夠看到訂閱者
DouBookModel model = DouBookModel.getInstance(activity); model.getHotMovie(mType,start,count) .subscribeOn(Schedulers.io()) //請求數據的事件發生在io線程 .observeOn(AndroidSchedulers.mainThread()) //請求完成後在主線程更顯UI .subscribe(new Observer<DouBookBean>() { //訂閱 @Override public void onCompleted() { //全部事件都完成,能夠作些操做。。 } @Override public void onError(Throwable e) { e.printStackTrace(); //請求過程當中發生錯誤 } @Override public void onNext(DouBookBean bookBean) { //這裏的book就是咱們請求接口返回的實體類 } });
攔截器說明
日誌攔截器
compile 'com.squareup.okhttp3:logging-interceptor:3.5.0' /** * 建立日誌攔截器 * @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; }
另外一種是建立自定義日誌攔截器
/** * 請求頭攔截器 * 使用addHeader()不會覆蓋以前設置的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中 個人理解是,請求頭攔截器是爲了讓服務端能更好的識別該請求,服務器那邊經過請求頭判斷該請求是否爲有效請求等...
統一請求攔截器
/** * 統一請求攔截器 * 統一的請求參數 */ 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; }
緩存攔截器
OkHttpClient.Builder builder = new OkHttpClient.Builder(); //添加緩存攔截器 //建立Cache File httpCacheDirectory = new File("OkHttpCache"); Cache cache = new Cache(httpCacheDirectory, 10 * 1024 * 1024); builder.cache(cache); //設置緩存 builder.addNetworkInterceptor(InterceptorUtils.getCacheInterceptor()); builder.addInterceptor(InterceptorUtils.getCacheInterceptor());
/** * 在無網絡的狀況下讀取緩存,有網絡的狀況下根據緩存的過時時間從新請求 * @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()) { //無網絡下強制使用緩存,不管緩存是否過時,此時該請求實際上不會被髮送出去。 request = request.newBuilder() .cacheControl(CacheControl.FORCE_CACHE) .build(); } Response response = chain.proceed(request); if (NetworkUtils.isConnected()) { //有網絡狀況下,根據請求接口的設置,配置緩存。 // 這樣在下次請求時,根據緩存決定是否真正發出請求。 String cacheControl = request.cacheControl().toString(); //固然若是你想在有網絡的狀況下都直接走網絡,那麼只須要 //將其超時時間這是爲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 { //無網絡 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>(); } }); }
http://api.mydemo.com/api%2Fnews%2FnewsList? 罪魁禍首@Url與@Path註解,咱們開發過程當中,確定會須要動態的修改請求地址 兩種動態修改方式以下: @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} 若是你用下面這樣寫的話,就會出現url被轉義 @POST("{url}") Call<HttpResult<News>> post(@Path("url") String url); 你若是執意要用@Path,也不是不能夠,須要這樣寫 @POST("{url}") Call<HttpResult<News>> post(@Path(value = "url", encoded = true) String url);
<method> <request-URL> <version> <headers> <entity-body>
這應該是最多見的POST提交數據的方式了。瀏覽器的原生<form>表單,若是不設置enctype屬性,那麼最終會以application/x-www-form-urlencoded方法提交數據。請求相似於以下內容(省略了部分無關的內容):
POST http://www.hao123.com/ HTTP/1.1 Content-Type: application/x-www-form-urlencoded;charset=utf-8 title=test&sub%5B%5D=1&sub%5B%5D=2&sub%5B%5D=3
POST http://www.hao123.com/ HTTP/1.1 Content-Type: application/json;charset=utf-8 {"title":"test","sub":[1,2,3]}
它是一種使用HTTP做爲傳輸協議,XML做爲編碼方式的遠程調用規範。典型的XML-RPC是這樣的:
POST http://www.example.com HTTP/1.1 Content-Type: text/xml <?xml version="1.0"?> <methodCall> <methodName>examples.getStateName</methodName> <params> <param> <value><i4>41</i4></value> </param> </params> </methodCall>
<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>
案例以下所示
header Content-Type: multipart/form-data; boundary={boundary}\r\n body 普通 input 數據 --{boundary}\r\n Content-Disposition: form-data; name="username"\r\n \r\n Tom\r\n 文件上傳 input 數據 --{boundary}\r\n Content-Disposition: form-data; name="file"; filename="myfile.txt"\r\n Content-Type: text/plain\r\n Content-Transfer-Encoding: binary\r\n \r\n hello word\r\n 結束標誌 --{boundary}--\r\n 數據示例 POST /upload HTTP/1.1 Host: 172.16.100.128:5000 Content-Length: 394 Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryLumpDpF3AwbRwRBn Referer: http://172.16.100.128:5000/ ------WebKitFormBoundaryUNZIuug9PIVmZWuw Content-Disposition: form-data; name="username" Tom ------WebKitFormBoundaryUNZIuug9PIVmZWuw Content-Disposition: form-data; name="password" passwd ------WebKitFormBoundaryUNZIuug9PIVmZWuw Content-Disposition: form-data; name="file"; filename="myfile.txt" Content-Type: text/plain hello world ------WebKitFormBoundaryUNZIuug9PIVmZWuw--