本片文章是Retrofit 2.1.0的學習筆記,以下簡稱Retrofit 2,Retrofit 2 的源碼只有37個文件,其中註解文件的個數是22個。所以我們平時使用的過程中重點使用其中的15個類。22個註解我們會使用即可(如果有朋友想詳細的學習註解的東西建議先看一些註解相關的資料,再回頭看裏面的設計思想)。下面我們開始學習Retrofit 2.
1、Retrofit 2 入門:
創建實例和Retrofit1.1的區別:
Retrofit 1.1 創建實例:
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("http://localhost:8848") // url的結尾可以不是/(斜線)
.build();
Retrofit 2 創建實例:
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("http://localhost:8848/") // url 的結尾必須以/(斜線)結束, 不然會拋出IllegalArgumentException,所以需要注意下。
.build();
2、定義接口
官方文檔:http://square.github.io/retrofit/
引用 :官方的一句話:Retrofit turns your HTTP API into a Java interface.
定義接口示例:
public interface GitHubService{
@GET("users/{user}/repos")
Call<List<Repo>> listRepos(@Path("user") String user);
}
2.1 示例中的註解
@GET("users/{user}/repos") 代表請求方式是get請求。請求參數是listRepos()方法中傳的參數。這裏我們暫時先簡單瞭解一下,除了@GET 方式,另外還有@POST、@PUT、@DELETE、@PATCH、@HEAD、@OPTIONS以及@HTTP 這幾種方式(這裏簡單瞭解一下,HTTP方式可以替換前面的7種方式)。
2.2 接口中的方法
示例代碼中方法的返回類型是一個Call類型,這種類型一般用於簡單的邏輯實現,實現Call類型的回調監聽即可。除Call類型之外,實際項目開發中使用Observable類型的情況居多,畢竟使用Retrofit也是爲了使代碼更加優雅,邏輯更加清晰(訂閱者/觀察者模式,當然說觀察者模式有些不太合乎定義),Observable也就是可訂閱者(相當於被觀察者),用於響應式編程(這個之後再說)。
2.3 接口方法的參數
@Path註解作用於方法參數,用於URL。除Path之外還有:
@Headers(用於添加請求頭)、
@Header(用於添加不固定值的Header,請求頭)、
@Body(用於非表單請求體,之後再詳細說)、
@Field(用於表單字段,和@FiledMap配合@FormUrlEncoded註解配合)、
@FieldMap(和@FieldMap類似,接收的參數類型是Map<String, String>,如果傳的參數類型不是String類型,會調用參數的toString()方法)、
@Part和@PartMap(配合@Multipart註解使用,適合有文件上傳的情況。@PartMap的默認接受的參數類型是Map<String, RequestBody>類型,非RequestBody類型會通過Converter轉換,Converter轉換之後會細說)、
@Query(用於URL,@Query和@QueryMap 類似 @Field和@FieldMap功能,不同的是@Query和@QueryMap中的數據體現在Url上,而@Field和@FieldMap的數據是請求體,但是生成的數據形式是一樣的)、
Url(用於URL)
注:{佔位符}和Path儘量只用在URL的path部分,url中的參數使用Query和QueryMap代替保證接口定義的簡潔
注:Query、Field和Part這三者都是支持數組和實現了Iterable接口的類型,比如List、Set等,方便向後臺傳遞數組
3、使用接口(這裏說的接口是interface,不是後臺接口)
這裏我們以示例接口爲例。
// 3.1 Retrofit中調用接口方法需要我們先創建一個代理對象。
Retrofit retrofit = new Retrofit.Builder().baseUrl("http://localhost:8848").build();
GitHubService gitHubService = retrofit.create(GitHubService.class);
// 3.2 得到代理對象之後,就可以調用定義的接口方法了
// 3.2.1 接口方法的返回類型是Call
Call<ResponseBody> call = gitHubService.listRepos("uana_777");
// 這一步和OkHttp的Call類似,不同的是如果是Android系統回調方法執行在主線程
call.enqueue(new Callback<ResponseBody>(){
@Override
public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response){
try{
System.out.println(response.body().toString());
} catch(Exception e){
// 處理異常
}
}
@Override
public void onFilure(Call<ResponseBody> call, Throwable t){
// 失敗後的處理
t.printStackTrace();
}
});
打印出來的結果:
{“code”:200, "msg":"success", "data":{"id":2,"date":"2016-11-04","userName":"uana_777"}, "count":1, "page":0}
注:上面說到了Retrofit 的註解:@XX。所以下面我們詳細介紹一下這方面。
4、Retrofit 的註解
開始我們提到Retrofit中的註解文件個數是22個。下面我們就詳細介紹這22個註解。爲了幫助大家更好的理解和記憶,我們把這22個註解分爲3個大類。
第一類:Http請求註解 8個
第二類:標誌類 3個
第三類:參數類 11個
下面開始詳細介紹:
4.1 Http 請求註解
-------------------------------------------------------------------------------------------------------------------------------------------------------------
@GET 對應Http請求方法,get請求,接受字符串格式的參數,與baseUrl組成一個完整的Url,例如:
public interface GitHubService{
@GET("users/uana_777/repos") // 和創建Retrofit 時baseUrl("http://localhost:8848/")中的參數拼接爲一個完整的 url
Call<List<Repo>> listRepos();
}
也可以像上面的接口中寫的:和@PATH註解的參數一起使用,定義一個靈活的請求地址。例如:
public interface GitHubService{
@GET("users/{user_name}/repos") // 對應@PATH("XXX") 參數名稱,和baseUrl("http://localhost:8848/")中的參數拼接爲一個完整的 url
Call<List<Repo>> listRepos(@PATH("user_name") String userName);
}
-------------------------------------------------------------------------------------------------------------------------------------------------------------
@POST 對應Http請求方法,post請求,和GET註解類似,接受的參數類型是String類型,
-------------------------------------------------------------------------------------------------------------------------------------------------------------@PUT 對應Http請求方法,put請求
-------------------------------------------------------------------------------------------------------------------------------------------------------------@DELETE 對應Http請求方法,delete請求
-------------------------------------------------------------------------------------------------------------------------------------------------------------@PATCH 對應Http請求方法,patch請求
-------------------------------------------------------------------------------------------------------------------------------------------------------------@HEAD 對應Http請求方法,head請求
-------------------------------------------------------------------------------------------------------------------------------------------------------------@OPTIONS 對應Http請求方法,options請求
-------------------------------------------------------------------------------------------------------------------------------------------------------------@HTTP 注:這個可以代替上面7種註解中的任意一種註解,有三個屬性:method = “Http請求方式”、path = "請求地址"、hasBody = true Or false
public interface GitHubService{
/**
* method 表示請求方法,不區分大小寫
* path 表示請求地址(路徑)
* hasBody 表示是否有請求體
*/
@HTTP(method = "get", path = "{user_name}/repo", hasBody = false)
Call<ResponseBody> getUserInfo(@Path("user_name") String userName);
@HTTP(method = "get", path = "{user_id}/repo", hasBody = false)
Call<ResponseBody> getUserInfo(@Path("user_id") int uerId);
}
-------------------------------------------------------------------------------------------------------------------------------------------------------------
4.2 標誌類
-------------------------------------------------------------------------------------------------------------------------------------------------------------
@FormUrlEncoded 用於表單請求,表示請求體是一個Form表單,比如平時網頁登錄的時候使用的就是表單請求,Content-Type:multipart/form-data
-------------------------------------------------------------------------------------------------------------------------------------------------------------
@MultiPart 用於表單請求,表示請求體是一個支持文件上傳的Form表單,Content-Tpye:multipart/form-data
-------------------------------------------------------------------------------------------------------------------------------------------------------------
@Streaming 標記,表示響應提的數據用流的形式返回,如果使用該註解,默認會把返回的數據全部載入內存,之後你通過流獲取數據,其實也就是
讀取內存中的數據。所以如果你返回的數據比較大,你就需要使用這個註解
-------------------------------------------------------------------------------------------------------------------------------------------------------------
注:示例代碼
package com.github.uana;
import java.util.HashMap;
import java.util.Map;
import okhttp3.MediaType;
import okhttp3.MultipartBody;
import okhttp3.RequestBody;
import okhttp3.ResponseBody;
import retrofit2.Call;
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;
import retrofit2.http.Field;
import retrofit2.http.FieldMap;
import retrofit2.http.FormUrlEncoded;
import retrofit2.http.Multipart;
import retrofit2.http.POST;
import retrofit2.http.Part;
import retrofit2.http.PartMap;
/**
* Retrofit 2 標誌類註解
*/
public class Example{
public interface GitHubService{
/**
* @FormUrlEncoded 表示是一個表單格式的請求
* (Content-Type:application/x-www-form-urlencoded)
* Field("user_name") 表示將後面的 String name 參數值作爲user_name的值,同樣的Field("age") 表示 將後面的 int age 參數值作爲age的值
*/
@POST("/form") // url地址
@FormUrlEncoded
Call<ResponseBody> testFormUrlEncoded(@Field("user_name") String name, @Field("age") int age);
/**
* Map的key作爲表單的鍵
*/
@POST("/form")
@FormUrlEncoded
Call<ResponseBody> testFormUrlEncoded(@FieldMap Map<String, Object> map);
/**
*{@link Part}註解,後面的參數類型支持三種:RequestBody、okhttp3.MultipartBody.Part、除以上兩種類型之外其他類型的必須帶上表單字段(okhttp3.MultipartBody.Part 中已經包含了表單字段的信息)
*
*/
@POST("/form")
@Multipart
Call<ResponseBody> testFileUpLoad(@Part("name") RequestBody name, @Part("age") RequestBody age, @Part MultipartBody.Part file);
/**
* PartMap 註解支持一個Map作爲參數,也支持RequestBody類型,如果有其他的類型,會被retrofit2.Converter 轉換。
* 後面會介紹如何轉換,使用com.google.gson.Gson 的retrofit2.Converter。文件只能用 @Part MultipartBody.Part
*/
@POST("/form")
@Multipart
Call<ResponseBody> testFileUpload(@PartMap<String, RequestBody> args, @Part MultipartBody.Part file);
}
/**
* 調用
*/
public void test(){
// 獲取Retrofit 對象
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("http://localhost:8848/")
.addConverterFactory(GsonConverterFactory.create())
.build();
// 獲取接口實例
GitHubService service = retrofit.create(GitHubService.class);
// 演示 @FormUrlEncoded 和 @Field
Call<ResponseBody> call_one = service.testFormUrlEncoded("uana_777", 25);
// 打印響應結果
// ResponseBodyPrinter.printRespinseBody(call_one);
// 自定義處理
call_one.enqueue(new CallBack<ResponseBody>(){
@Override
public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response){
try{
// 自己處理響應結果
}catch(IOException e){
// 異常處理
}
}
@Override
public void onFailure(Call<ResponseBody> call, Throwable t){
// 請求失敗的處理
}
});
// =========================//
// 演示@FormUrlEncoded 和 @FieldMap
Map<String, Object> params = new HashMap<>();
params.put("user_name", "uana_777");
params.put("age", 25);
Call<ResponseBody> call_two = service.testFormUrlEncoded(params);
// 打印響應結果
// ResponseBodyPrintResponseBody(call_two);
//============================//
MediaType textType = MediaType.parse("text/plain");
RequestBody userName = RequestBody.create(textType, "uana_777");
RequestBody age = RequestBody.create(textType, "25");
RequestBody file = RequestBody.create(MediaType.parse("application/octet-stream"), "這裏是模擬文件的內容");
// 演示@Multipart 和 @Part
MultipartBody.Part filePart = MultipartBody.Part.createFormData("file", "test.txt", file);
Call<ResponseBody> call_three = service.testFileUpload(userName, age, filePart);
// 打印響應結果
// ResponseBodyPrinter.printResponseBody(call_three);
//===========================//
// 演示@Multipart 和 @Part
MediaType textType = MediaType.parse("text/plain");
RequestBody uName = RequestBody.create(textType, "uana_777");
RequesBody uAge = RequestBody.create(textType, "25");
RequestBody uFile = RequestBody.create(MediaType.parse("application/octet-stream"), "這是測試數據");
Map<String, RequestBody> fileUploadParams = new HashMap();
fileUploadParams.put("user_name", uName);
fileUploadParams.put("age", uAge);
// 這裏並不會被當成文件處理, 因爲沒有文件名
// fileUploadParams.put("file", uFile);
// 通過part轉換一下
MultipartBody.Part uFilePart = MultipartBody.Part.createFormData("file", "test.txt", uFile);
Call<ResponseBody> call_four = service.testFileUpload(fileUploadParams, uFilePart);
// 打印響應的結果
ResponseBodyPrintResponseBody(call_four);
}
}
4.3 參數類 (作用於方法參數)
@Part 和@PartMap註解配合@MultiPart註解使用,適合有文件上傳的情況
-------------------------------------------------------------------------------------------------------------------------------------------------------------
@Path 用於URL
-------------------------------------------------------------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------------------------------------------------------------
import okhttp3.ResponseBody;
import retrofit2.Call;
import retrofit2.Retrofit;
import retrofit2.http.GET;
import retrofit2.http.Header;
import retrofit2.http.Headers;
import retrofit2.http.Query;
/**
* Retrofit註解詳解 Headers & Header
*/
public class Example {
public interface GitHubService {
@GET("/headers?showAll=true")
@Headers({"CustomHeader1: customHeaderValue1", "CustomHeader2: customHeaderValue2"})
Call<ResponseBody> testHeader(@Header("CustomHeader3") String customHeaderValue3);
}
public void test() {
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("http://localhost:8848/")
.build();
GitHubService service = retrofit.create(<span style="font-family: Arial, Helvetica, sans-serif;">GitHubService.class</span><span style="font-family: Arial, Helvetica, sans-serif;">);</span>
// 演示 @Headers 和 @Header
Call<ResponseBody> call = service.testHeader("uana_777");
// 打印響應結果
ResponseBodyPrinter.printResponseBody(call);
}
}
import okhttp3.ResponseBody; import retrofit2.Call; import retrofit2.Retrofit; import retrofit2.http.GET; import retrofit2.http.Query; import retrofit2.http.Url; /** * [Retrofit註解詳解 之 Query & QueryMap & Url 註解 */ public class Example { public interface GitHubService{ /** * 當GET、POST...HTTP等方法中沒有設置Url時,則必須使用 {@link Url}提供 * 對於Query和QueryMap,如果不是String(或Map的第個泛型參數不是String)時 * 會被調用toString * Url支持的類型有 okhttp3.HttpUrl, String, java.net.URI, android.net.Uri * {@link retrofit2.http.QueryMap} 用法和{@link retrofit2.http.FieldMap} 用法一樣,不再說明 */ @GET //當有URL註解時,這裏的URL就省略了 Call<ResponseBody> testUrlAndQuery(@Url String url, @Query("showAll") boolean showAll); @GET //當有URL註解時,這裏的URL就省略了 Call<ResponseBody> testUrlAndQueryMap(@Url String url, @QueryMap("params") Map<String, Object> params); @GET("headers") Call<ResponseBody> testQueryMap(@QueryMap("params") Map<String, Object> params); @GET("headers") Call<ResponseBody> testQuery(@Query("showall") boolean showAll); } public void test() {
Retrofit retrofit = new Retrofit.Builder() .baseUrl("http://localhost:8848/") .build();
GitHubService service = retrofit.create(GitHubService.class);
// 調用
Call<ResponseBody> call1 = service.testUrlAndQuery("headers",false);ResponseBodyPrinter.printResponseBody(call1);
}
}
注:{佔位符}和@PATH儘量只在URL的path部分,url中的參數使用@Query和@QueryMap代替,這樣做能保證接口定義的簡潔。
注:@Query、@Field 和 @Part 這三者都支持數組和實現了 Iterable 接口類型的參數,比如:List、Set 等。
比如:Call<ResponseBody> test(@Query("ids[]") List<Integer> ids);
5、Gson與Converter
在默認情況下Retrofit只支持將HTTP 的響應體轉換爲ResponseBody,這也是爲什麼前面的例子中接口方法的返回值都是Call<ResponseBody>,泛型寫的一直是ResponseBody,但是如果響應體只支持ResponseBody 的話爲什麼設計的時候要設計爲泛型的方式呢??(這句話可能有些童鞋不理解,詳細說一下,上面這句話意思就是說:如果Retrofit的請求響應體只支持ResponseBody的話,爲什麼設計Call<T> 爲這種格式)。 既然這樣設置,就說明Retrofit提供給我們的有類似轉換器一類的東西,而這個東西就是Converter,Converter就是Retrofit爲我們提供的用於將ResponseBody轉換爲我們需要的類型的工具。
下面我們寫一個示例,看看Converter是如何使用的:
public interface GitHubService{
@GET("user_info/{user_id}") // 使用GET註解
Call<Result<User>> getUserById(@Path("user_id") int uerId); // 設置返回值類型是Result<User> 類型
@POST("create_user")
Call<Result<User>> createUser(@Body User user); // 被@Body註解的User會被Gson轉換成RequesBody發送到服務器
}
注:只改變泛型的類型是不行的,我們在創建Retrofit 的時候要明確的告知用於將ResponseBody轉換爲我們想要的類型的時候需要使用到的Converter.
上面的這句話有點拗口,不過不妨事,下面我們通過示例代碼看一下:
5.1 如果是AndroidStudio開發的話:在model 的 gradle文件中引入 : (其他的開發工具請自己搜索,這個很簡單就不詳細說了)
compile 'com.squareup.retrofit2:converter-gson:2.0.2'
5.2 通過GsonConverterFactory爲Retrofit添加Gson支持:
// Gson 如果能做到全局只用一個最好(這個只是建議) 一般gson的創建會放在Application類中創建。
Gson gson = new GsonBuilder()
// 配置Gson
.setDataFormat("yyyy-MM-dd hh:mm:ss")
.create();
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("http://localhost:8848/")
// 在Retrofit 1 的時候可以設置這一步,1的時候默認有gson,2的時候就必須設置了。 這一步其實就是提供給Retrofit 使用這個gson對象將
// ResponseBody轉換爲我們想要的類型
.addConverterFactory(GsonConverterFactory.create(gson))
.build();
5.3 調用
GitHubService service = retrofit.create(GitHubService.class);
User user = new User();
user.setName("uana_888");
user.setAge(25);
Call<Result<User>> call = service.createUser(user);
6、RxJava 與 CallAdapter
6.1 引入RxJava 庫
compile 'com.squareup.retrofit2:adapter-rxjava:2.0.2'
6.2 使用RxJavaCallAdapterFactory爲Retrofit添加RxJava支持
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("http://localhost:8848/")
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJavaCallAdapterFactory.create()) // 添加RxJava支持, 這一行一定要放在最後添加
.build();
6.3 接口設計
public interface GitHubService{
@POST("/user")
Observable<Result<List<User>>> getUsers(); // 返回值類型爲 Observable 泛型爲 List<User>類型
}
6.4 使用示例
GitHubService service = retrofit.create(GitHubService.class);
service.getUsers()
.subscribeOn(Schedulers.io())
.subscribe(new Subscriber<Result<List<User>>>(){
@Override
public void onCompleted(){
// 完成
}
@Override
public void onError(Throwable e){
// 失敗
}
@Override
public void onNext(Result<List<User>> responseStr){
// 成功
}
});
注:上面的這種方式,我們是拿不到後臺返回的Header和響應碼如果我們想使用響應的Header和響應碼。可以使用下面的兩種解決方案
01. 使用Observable<Response<T>>,這裏的Response指的是retrofit2.Response。
02.使用Observable<Result<T>> 代替Observable<T>,這裏的Result是retrofit2.adapter.rxjava.Result,這個Result中包含的是Response的實例。
7、自定義Converter
7.1 Converter接口及其作用:
public interface Converter<F, T>{
// 實現從F類型到T類型的轉換,F(from) T(To)
T convert(F value) throws IOException;
// 用於向Retrofit提供相應的Converter的工廠
abstract class Factory {
// 這裏創建從ResponseBody其他類型的Converter,如果不能處理,就返回null
// 主要用於響應體的處理
public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit){
return null;
}
// 在這裏創建 從自定義類型到ResponseBody 的 Converter, 不能處理就返回null,
// 主要用於對Part、 PartMap、Body註解的處理
public Converter<?, RequestBody> requestBodyConverter(Type type, Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit){
return null;
}
// 這裏用於對Field、FieldMap、 Header、Path、Query、QueryMap註解的處理
public Converter<?, String> stringConverter(Type type, Annotation[] annotations, Retrofit retrofit){
return null;
}
}
}
注:Retrofit 默認返回的類型是Call<T>類型。上面的接口,如果我們想從Call<ResponseBody>轉換爲Call<String> 那麼我們需要將對應的F和T,分別寫爲ResponseBody和String,下面我們自定義一個StringConverter 。7.2 自定義StringConverter
public class StringConverter implements Converter<ResponseBody, String> {
// 創建單一實例
public static final StringConverter instance = new StringConverter();
// 重寫轉換的方法
@Override
public String convert(ResponseBody resBody) throws IOException{
// resBody 非空
return resBody.string();
}
}
7.3 使用自定義的轉換器
首先,我們需要一個StringConverterFactory 來向Retrofit註冊StringConverter。我們先創建一個StringConverterFactory繼承我們寫的Converter接口中的抽象類Factorypublic class StringConverterFactory extends Converter.Factory {
public static final StringConverterFactory INSTANCE = new StringConverterFactory();
public static StringConverterFactory create(){
return INSTANCE;
}
// 我們只關心實現從ResponseBody到String類型 的轉換,所以其他的方法可以不用重寫
// 這個方法 可以把ResponseBody類型的轉換到其他類型
@Override
public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit){
if(type == String.class){
return StringConverter.INSTANCE; // 將StringConverter註冊進來
}
// 其他的不處理,我們直接返回null
return null;
}
}
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("http://localhost:8848/")
.addConverterFactory(StringConverterFactory.create()) // 這行代碼一定要放在 添加Gson類型相關的Converter之前
.addConverterFactory(GsonConverterFactory.create())
.build();
注:addConverterFactory(**) 是有先後順序的,如果有多個ConverterFactory都支持同一個類型,那麼就只有第一個有效(也就是說添加多個同一類型的轉換器,只有第一個有效、會被使用)。 而 GsonConverterFactory是不判斷是否支持的,所以上面的代碼中的順序,如果顛倒的話會有一個類型不匹配的異常。import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import okhttp3.ResponseBody;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Converter;
import retrofit2.Response;
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;
import retrofit2.http.GET;
import retrofit2.http.Path;
/**
* 自定義 Converter
*/
public class ExampleTest{
// 我們還使用GitHubService,GitHubService中的方法的返回值假設是Call<String>
private GitHubService service;
public void test(){
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("http://localhost:8848/")
.addConverterFactory(StringConverterFactory.create())
.addConverterFactory(GsonConverterFactory.create())
.build();
service = retrofit.create(GitHubService.class);
Call<String> call = service.getUsers();
call.enqueue(new Callback<String>(){
@Override
public void onResponse(Call<String> call, Response<String> response){
// 成功
response.body();
}@Overridepublic void onFailure(Call<String> call, Throwable t){//
失敗}});}}
8、自定義CallAdapter
public interface CallAdapter<T> {
// 真正的數據類型是 我們實現Adapter的時候 設置的泛型
// 這個T會作爲Converter.Factory.responseBodyConverter 的第一個參數
// 可以參照上面定義的Converter
Type responseType();
<R> T adapt(Call<R> call);
/**
* 用於向Retrofit提供CallAdapter的工廠類
*/
abstract class Factory {
// 在這個方法中判斷是否是我們支持的類型,returnType 即Call<RequestBody> 和 Observable<RequestBody>中的ResponseBody
// RxJavaCallAdapterFactory 就是判斷returnType 是不是 Observable<?> 類型
// 不支持返回null
public abstract CallAdapter<?> get(Type returnType, Annotation[] annotations, Retrofit retrofit);
// 用於獲取泛型的參數, 比如:Call<RequestBody> 中的RequestBody
protected static Type getParameterUpperBound(int index, ParameterizedType type) {
return Utils.getParameterUpperBound(index, type);
}
// 獲取泛型的原始類型, 比如:Call<RequestBody> 中的Call
protected static Class<?> getRawType(Type type) {
return Utils.getRawType(type);
}
}
}
public class CustomCall<R> {
public final Call<R> call;
public CustomCall(Call<R> call){
this.call = call;
}
public R get () throws IOException {
return call.execute().body();
}
}
public class CustomCallAdapter implements CallAdapter<CustomCall<?>> {
private final type responseType;
// 下面的 responseType 方法需要數據的類型
CustomCallAdapter(Type responseType){
this.responseType = responseType;
}
/*
* 重寫responseType() 方法
*/
@Override
public Type responseType(){
return responseType;
}
/**
* 重寫adapt()方法
*/
@Override
public <R> CustomCall<R> adapt(Call<R> call) {
// 由 CustomCall 決定如何使用
return new CustomCall<>(call);
}
}
adapter創建好了之後,使用之前我們需要創建一個CustomCallAdapterFactory 用於向Retrofit提供CustomCallAdapter:
public class CustomCallAdapterFactory extends CallAdapter.Factory {
public static final CustomCallAdapterFactory INSTANCE = new CustomCallAdapterFactory();
@Override
public CallAdapter<?> get(Type returnType, Annotation[] annotations, Retrofit retrofit){
// 獲取原始類型
Class<?> rawType = getRawType(returnType);
// 返回值必須是CustomCall並且帶有泛型
if(rawType == CustomCall.class && returnType instanceof ParameterizedType){
Type callReturnType = getParameterUpperBound(0, (ParameterizedType) returnType);
return new CustomCallAdapter(callReturnType);
}
return null;
}
}
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("http://localhost:8848/")
.addConverterFactory(StringConverterFactory.create())
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(CustomCallAdapterFactory.INSTANCE) // 註冊 CustomCallAdapter
.build();
public class ExampleCallAdapteTest{
private GitHubService service; // 別忘記在接口中新增一個返回值類型是CustomCall<String> 類型的方法getUsers
public void test () {
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("http://localhost:8848/")
.addConverterFactory(StringConverterFactory.create())
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(CustomCallAdapterFactory.INSTANCE)
.build();
service = retrofit .create(GitHubService.class);
// Call<String> call = service.getUsers();
CustomCall<String> call = service.getUsers();
try{
// String result = call.execute().body();
String result = call.get();
} catch (Exception e) {
e.printStackTrace();
}
}
}
下面我們在補充點東西:
我們先介紹一下:Retrofit
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'
使用Call實例完成同步和異步請求
異步請求:
call.enqueue(new Callback<BookSearchResponse>(){
@Override
public void onResponse(Call<String> call, Response<String> response){
// 獲取請求結果
String resultStr = response.body();
}
@Override
public void onFailure(Call<BookSearchResponse> call, Throwable t){
}
});
取消請求:調用取消方法的前提條件是該請求還沒有執行。
call.cancel();
添加請求參數的方式:
private static OkHttpClient getNewClient(){
HttpLoggingInterceptor logging = new HttpLoggingInterceptor(); // 網絡請求日誌
logging.setLevel(HttpLoggingInterceptor.Level.BODY);
return new OkHttpClient.Builder()
.addInterceptor(new CustomInterceptor())
.addInterceptor(logging)
.connectTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS)
.build();
}
compile 'com.squareup.okhttp3:logging-interceptor:3.4.1'
添加請求頭的方式:
@Header("Content-type : audio/wav")
@Headers("Content-type : audio/wav", "Authorization : token")
Observable<Result<List<User>>> getUser();
public class RequestInterceptor implements Interceptor {
@Override
public Response intercept(Chain chain) throws IOException {
Request original = chain.request();
Request request = original.newBuilder()
.header("User_Agent", "app_name")
.header("Accept", "application/vnd.yourapi.v1.full+json")
.method(original.method(), original.body())
.build();
return chain.proceed(request);
}
}
private static OkHttpClient getNewClient(){
return new OkHttpClient.Builder()
.addInterceptor(new RequestInterceptor())
.connectTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS)
.build();
}
上傳操作:
public class MultiplePartUtil {
private static final String MULTIPART_FORM_DATA = "mutilpart/form-data";
private Context context;
public MultiplePartUtil(Context context){
this.context = context;
}
@NonNull
private RequestBody createPartFormString(String descriptionString){
return RequestBody.create(MediaType.parse(MULTIPART_FORM_DATA), descriptionString);
}
@NonNull
private MultipartBody.Part prepareFilePart(String partName, Uri fileUri) {
File file = FileUtils.getFile(context, fileUri);
// 爲file建立RequestBody實例
RequestBody requestFile = RequestBody.create(MediaType.parse(MULTIPART_FORM_DATA), file);
// MultipartBody.Part藉助文件名完成最終的上傳
return MultipartBody.Part.createFormData(partName, file.getName(), requestFile);
}
}
3、最後我們,上傳一下文件:public class TestActivity extends BaseActivity {
private Uri file01Url = ...; // 從文件選擇器或者攝像頭中獲取
private Uri file02Url = ...; //
private MultiplePartUtil partUtil = new MultiplePartUtil(this);
// 創建上傳的service實例
private FileUploadService service = ServiceGenerator.createService(FileUploadService.class);
// 創建文件 Part (可以是photo、video、doc、amr等)
MultipartBody.Part body1, body2;
//
private RequestBody requestBody;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
body1 = partUtil.prepareFilePart("video", file01Uri);
body2 = partUtil.prepareFilePart("thumbnail", file02Uri);
requestBody = partUtil.createPartFromString("hello, test");
Call<ResponseBody> call = service.uploadMultipleFiles(description, body1, body2);
call.enqueue(new Callback<ResponseBody>(){
@Override
public void onResponse(Call<ResponseBody> call,Response<ResponseBody> response) {
Log.v("Upload", "success");
}
@Override
public void onFailure(Call<ResponseBody> call, Throwable t) {
Log.e("Upload error:", t.getMessage());
}
});
}
}