Android 手把手教你使用Retrofit2
Android 手把手教你使用Retrofit2
前言:
1.我想通過本文,讓大家能更快的上手Retrofit2,跟上時代的步伐。
2.本文主要是寫給初級、中級開發人員閱讀,所以在這邊不會做太多的解釋說明,只針對一些坑以及使用方法進行說明。
3.本文只涉及到Retrofit2,并未與RxJava一起使用,RxJava+Retrofit2技術將會在下篇文章中呈現。
4.本人也是剛開始接觸Retrofit2,所以可能文章中會有一些錯誤或者不足,希望大家多多留言探討,共同進步
開啟Retrofit2之旅
添加依賴
以下是我用到的一些依賴
compile 'com.squareup.retrofit2:retrofit:2.1.0'
compile 'com.squareup.retrofit2:converter-gson:2.1.0'
compile 'com.squareup.retrofit2:adapter-rxjava:2.1.0'
//日志攔截器
compile 'com.squareup.okhttp3:logging-interceptor:3.5.0'
compile 'io.reactivex:rxjava:1.2.4'
compile 'io.reactivex:rxandroid:1.2.1'
compile 'org.ligboy.retrofit2:converter-fastjson-android:2.1.0'
注解
retrofit通過使用注解來簡化請求,大體分為以下幾類:
1.用于標注請求方式的注解
2.用于標記請求頭的注解
3.用于標記請求參數的注解
4.用于標記請求和響應格式的注解
請求方法注解
注解 | 說明 |
---|---|
@GET | get請求 |
@POST | post請求 |
@PUT | put請求 |
@DELETE | delete請求 |
@PATCH | patch請求,該請求是對put請求的補充,用于更新局部資源 |
@HEAD | head請求 |
@OPTIONS | option請求 |
@HTTP | 通用注解,可以替換以上所有的注解,其擁有三個屬性:method,path,hasBody |
請求頭注解
注解 | 說明 |
---|---|
@Headers | 用于添加固定請求頭,可以同時添加多個。通過該注解添加的請求頭不會相互覆蓋,而是共同存在 |
@Header | 作為方法的參數傳入,用于添加不固定值的Header,該注解會更新已有的請求頭 |
請求參數注解
名稱 | 說明 |
---|---|
@Body | 多用于post請求發送非表單數據,比如想要以post方式傳遞json格式數據 |
@Filed | 多用于post請求中表單字段,Filed和FieldMap需要FormUrlEncoded結合使用 |
@FiledMap | 和@Filed作用一致,用于不確定表單參數 |
@Part | 用于表單字段,Part和PartMap與Multipart注解結合使用,適合文件上傳的情況 |
@PartMap | 用于表單字段,默認接受的類型是Map<String,REquestBody>,可用于實現多文件上傳 |
@Path | 用于url中的占位符 |
@Query | 用于Get中指定參數 |
@QueryMap | 和Query使用類似 |
@Url | 指定請求路徑 |
請求和響應格式注解
名稱 | 說明 |
---|---|
@FormUrlEncoded | 表示請求發送編碼表單數據,每個鍵值對需要使用@Field注解 |
@Multipart | 表示請求發送multipart數據,需要配合使用@Part |
@Streaming | 表示響應用字節流的形式返回.如果沒使用該注解,默認會把數據全部載入到內存中.該注解在在下載大文件的特別有用 |
使用
這里不對注解進行過多的說明了,只展示幾個用法
/**
- Created by caihan on 2017/1/11.
*
- @GET 表明這是get請求
- @POST 表明這是post請求
- @PUT 表明這是put請求
- @DELETE 表明這是delete請求
- @PATCH 表明這是一個patch請求,該請求是對put請求的補充,用于更新局部資源
- @HEAD 表明這是一個head請求
- @OPTIONS 表明這是一個option請求
- @HTTP 通用注解, 可以替換以上所有的注解,其擁有三個屬性:method,path,hasBody
- @Headers 用于添加固定請求頭,可以同時添加多個。通過該注解添加的請求頭不會相互覆蓋,而是共同存在
- @Header 作為方法的參數傳入,用于添加不固定值的Header,該注解會更新已有的請求頭
- @Body 多用于post請求發送非表單數據, 比如想要以post方式傳遞json格式數據
- @Filed 多用于post請求中表單字段, Filed和FieldMap需要FormUrlEncoded結合使用
- @FiledMap 和@Filed作用一致,用于不確定表單參數
- @Part 用于表單字段, Part和PartMap與Multipart注解結合使用, 適合文件上傳的情況
- @PartMap 用于表單字段, 默認接受的類型是Map<String,REquestBody>,可用于實現多文件上傳
- <p>
- Part標志上文的內容可以是富媒體形勢,比如上傳一張圖片,上傳一段音樂,即它多用于字節流傳輸。
- 而Filed則相對簡單些,通常是字符串鍵值對。
- </p>
- Part標志上文的內容可以是富媒體形勢,比如上傳一張圖片,上傳一段音樂,即它多用于字節流傳輸。
- 而Filed則相對簡單些,通常是字符串鍵值對。
- @Path 用于url中的占位符,{占位符}和PATH只用在URL的path部分,url中的參數使用Query和QueryMap代替,保證接口定義的簡潔
- @Query 用于Get中指定參數
- @QueryMap 和Query使用類似
@Url 指定請求路徑
*/
public interface MyRetrofit2Service {
//使用@Headers添加多個請求頭
@Headers({
"User-Agent:android",
"apikey:123456789",
})
@POST()
Call<HttpResult<News>> post(@Url String url, @QueryMap Map<String, String> map);
@GET("mobile/active")
Call<HttpResult<News>> get(@Header("token") String token, @Query("id") int activeId);
@GET("active/list")
Call<HttpResult<News>> ActiveList();
@POST("api/news/newsList")
Call<HttpResult<News>> post2(@QueryMap Map<String, String> map);
/**
- 很多情況下,我們需要上傳json格式的數據。比如當我們注冊新用戶的時候,因為用戶注冊時的數據相對較多,
- 并可能以后會變化,這時候,服務端可能要求我們上傳json格式的數據。此時就要@Body注解來實現。
- 直接傳入實體,它會自行轉化成Json
*
- @param url
- @param post
@return
*/
@POST("api/{url}/newsList")
Call<HttpResult<News>> login(@Path("url") String url, @Body News post);
/**
- 單張圖片上傳
- retrofit 2.0的上傳和以前略有不同,需要借助@Multipart注解、@Part和MultipartBody實現。
*
- @param url
- @param file
@return
*/
@Multipart
@POST("{url}")
Call<HttpResult<News>> upload(@Path("url") String url, @Part MultipartBody.Part file);
/**
- 多張圖片上傳
*
- @param map
@return
*/
@Multipart
@POST("upload/upload")
Call<HttpResult<News>> upload(@PartMap Map<String, MultipartBody.Part> map);
/**
- 圖文混傳
*
- @param post
- @param map
@return
*/
@Multipart
@POST("")
Call<HttpResult<News>> register(@Body News post, @PartMap Map<String, MultipartBody.Part> map);
/**
- 文件下載
*
- @param fileUrl
@return
*/
@Streaming
@GET
Call<HttpResult<News>> downloadPicture(@Url String fileUrl);
/**
- 這里需要注意的是如果下載的文件較大,比如在10m以上,那么強烈建議你使用@Streaming進行注解,否則將會出現IO異常.
*
- @param fileUrl
@return
*/
@Streaming
@GET
Observable<HttpResult<News>> downloadPicture2(@Url String fileUrl);
@POST()
@FormUrlEncoded
Observable<HttpResult<News>> executePost(@FieldMap Map<String, Object> maps);
}</code></pre>
踩坑
1.url被轉義
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);</code></pre>
第一種是直接使用@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);
OkHttpClient
攔截器
addNetworkInterceptor添加的是網絡攔截器Network Interfacetor它會在request和response時分別被調用一次;
addInterceptor添加的是應用攔截器Application Interceptor他只會在response被調用一次。
1.日志攔截器
使用 addNetworkInterceptor() 方法添加到 OkHttpClient 中
日志攔截器我這有兩種創建方式:
一種是使用HttpLoggingInterceptor,需要使用到依賴
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;
}</code></pre>
另一種是
/**
- 創建日志攔截器
*
@return
*/
private class LogInterceptor implements Interceptor {
@Override
public okhttp3.Response intercept(Chain chain) throws IOException {
Request request = chain.request();
long t1 = System.nanoTime();
Log.d("OkHttp", "HttpHelper1" + String.format("Sending request %s on %s%n%s",
request.url(), chain.connection(), request.headers()));
okhttp3.Response response = chain.proceed(request);
long t2 = System.nanoTime();
Log.d("OkHttp", "HttpHelper2" + String.format("Received response for %s in %.1fms%n%s",
response.request().url(), (t2 - t1) / 1e6d, response.headers()));
return response;
}
}</code></pre>
2.請求頭攔截器
使用 addInterceptor() 方法添加到 OkHttpClient 中
我的理解是,請求頭攔截器是為了讓服務端能更好的識別該請求,服務器那邊通過請求頭判斷該請求是否為有效請求等...
/**
- 攔截器Interceptors
- 使用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;
}</code></pre>
3.統一請求攔截器
使用 addInterceptor() 方法添加到 OkHttpClient 中
統一請求攔截器的功能跟請求頭攔截器相類似
/**
- 攔截器Interceptors
統一的請求參數
*/
private Interceptor commonParamsInterceptor() {
Interceptor commonParams = new Interceptor() {
@Override
public okhttp3.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;
}</code></pre>
4.時間攔截器
使用 addInterceptor() 方法添加到 OkHttpClient 中
從響應中獲取服務器返回的時間
/**
- 攔截器Interceptors
- 通過響應攔截器實現了從響應中獲取服務器返回的time
*
@return
*/
public static Interceptor getResponseHeader() {
Interceptor interceptor = new Interceptor() {
@Override
public okhttp3.Response intercept(Chain chain) throws IOException {
okhttp3.Response response = chain.proceed(chain.request());
String timestamp = response.header("time");
if (timestamp != null) {
//獲取到響應header中的time
}
return response;
}
};
return interceptor;
}</code></pre>
緩存
使用okhttp緩存的話,先要創建Cache,然后在創建緩存攔截器
//創建Cache
File httpCacheDirectory = new File(MyApp.getContext().getCacheDir(), "OkHttpCache");
Cache cache = new Cache(httpCacheDirectory, 10 * 1024 * 1024);
httpClientBuilder.cache(cache);
//設置緩存
httpClientBuilder.addNetworkInterceptor(getCacheInterceptor2());
httpClientBuilder.addInterceptor(getCacheInterceptor2());
緩存攔截器
緩存時間自己根據情況設定
/**
- 在無網絡的情況下讀取緩存,有網絡的情況下根據緩存的過期時間重新請求,
*
@return
*/
public Interceptor getCacheInterceptor2() {
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();
}
okhttp3.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;
}</code></pre>
自定義CookieJar
httpClientBuilder.cookieJar(new CookieJar() {
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>();
}
});</code></pre>
啟動Retrofit2
到了這里,基本上準備工作都做好了,可以啟動Retrofit2了
retrofit = new Retrofit.Builder()
.client(httpClientBuilder.build())
.baseUrl(BASE_URL)
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.addConverterFactory(FastJsonConverterFactory.create())
.build();
mApi = retrofit.create(MyRetrofit2Service.class);</code></pre>
界面上通過getEnqueue()方法調用
/**
異步調用
*/
public void getEnqueue() {
Call<HttpResult<News>> call = mApi.post(NEWS_URI, params);
call.enqueue(new Callback<HttpResult<News>>() {
@Override
public void onResponse(Call<HttpResult<News>> call, Response<HttpResult<News>> response) {
//處理請求成功
Log.e("OkHttp", "處理成功請求 response = " + response.body().toString());
}
@Override
public void onFailure(Call<HttpResult<News>> call, Throwable t) {
//處理請求失敗
Log.e("OkHttp", "處理失敗請求");
}
});
// cancelCall(call);
}</code></pre>
結束
相信到這邊應該能滿足Demo的要求了吧,至少用是可以用了,不過在實際開發中,這還是太糙了點,哎..沒時間打磨,慢慢來吧,有興趣的可以留言討論下優化方案,大家拓展下思維也是不錯的嘛
來自:http://www.jianshu.com/p/73216939806a