使用 Retrofit 引入
implementation 'com.squareup.retrofit2:retrofit:2.4.0'
retrofit 使用示例
retrofit 在使用時吞鸭,需要定義一個接口對象
public interface IUserBiz
{
@GET("users")
Call<List<User>> getUsers();
}
1. 一般的 get 請求
上述接口中酌呆,可以看到一個 getUsers() 方法,通過 @GET 注解標識為 get 請求吊圾,@GET 中所填寫的 value 和 baseUrl 組成完整的路徑蝶念,baseUrl 在構(gòu)造 retrofit 對象時給出。
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("http://192.168.31.242:8080/springmvc_users/user/")
.addConverterFactory(GsonConverterFactory.create())
.build();
IUserBiz userBiz = retrofit.create(IUserBiz.class);
Call<List<User>> call = userBiz.getUsers();
call.enqueue(new Callback<List<User>>()
{
@Override
public void onResponse(Call<List<User>> call, Response<List<User>> response)
{
Log.e(TAG, "normalGet:" + response.body() + "");
}
@Override
public void onFailure(Call<List<User>> call, Throwable t)
{
}
});
依然是構(gòu)造者模式堤尾,指定了 baseUrl 和 Converter.Factory肝劲,該對象通過名稱可以看出是用于對象轉(zhuǎn)化的,本例因為服務(wù)器返回的是json格式的數(shù)組郭宝,所以這里設(shè)置了 GsonConverterFactory 完成對象的轉(zhuǎn)化辞槐。
ok,這里可以看到很神奇粘室,我們通過 Retrofit.create 就可以拿到我們定義的 IUserBiz 的實例榄檬,調(diào)用其方法即可拿到一個 Call 對象,通過 call.enqueue 即可完成異步的請求衔统。
具體 retrofit 怎么得到我們接口的實例的丙号,以及對象的返回結(jié)果是如何轉(zhuǎn)化的,我們后面具體分析缰冤。
這里需要指出的是:
接口中的方法必須有返回值犬缨,且比如是 Call<T> 類型
.addConverterFactory(GsonConverterFactory.create()) 這里如果使用 gson,需要額外導入:
compile 'com.squareup.retrofit2:converter-gson:2.0.2'
除了 gson 以外棉浸,還可以選擇如下:
Gson: com.squareup.retrofit2:converter-gson
Jackson: com.squareup.retrofit2:converter-jackson
Moshi: com.squareup.retrofit2:converter-moshi
Protobuf: com.squareup.retrofit2:converter-protobuf
Wire: com.squareup.retrofit2:converter-wire
Simple XML: com.squareup.retrofit2:converter-simplexml
Scalars (primitives, boxed, and String): com.squareup.retrofit2:converter-scalars
既然 call.enqueue 是異步的訪問數(shù)據(jù)怀薛,那么同步的訪問方式為 call.execute,這一點非常類似 okhttp 的 API迷郑,實際上默認情況下內(nèi)部也是通過 okhttp3.Call 實現(xiàn)枝恋。
2. 動態(tài)的 url 訪問 @PATH
如下的 url
//用于訪問zhy的信息
http://192.168.1.102:8080/springmvc_users/user/zhy
//用于訪問lmj的信息
http://192.168.1.102:8080/springmvc_users/user/lmj
即通過不同的 username 訪問不同用戶的信息创倔,返回數(shù)據(jù)為 json 字符串。
那么可以通過 retrofit 提供的 @PATH 注解非常方便的完成上述需求焚碌。
我們再定義一個方法:
public interface IUserBiz
{
@GET("{username}")
Call<User> getUser(@Path("username") String username);
}
可以看到我們定義了一個 getUser 方法畦攘,方法接收一個 username 參數(shù),并且我們的 @GET 注解中使用 {username} 聲明了訪問路徑十电,這里你可以把 {username} 當做占位符知押,而實際運行中會通過 @PATH("username") 所標注的參數(shù)進行替換。
那么訪問的代碼很類似:
//省略了retrofit的構(gòu)建代碼
Call<User> call = userBiz.getUser("zhy");
//Call<User> call = userBiz.getUser("lmj");
call.enqueue(new Callback<User>()
{
@Override
public void onResponse(Call<User> call, Response<User> response)
{
Log.e(TAG, "getUsePath:" + response.body());
}
@Override
public void onFailure(Call<User> call, Throwable t)
{
}
});
3. 查詢參數(shù)的設(shè)置 @Query
看下面的 url
http://baseurl/users?sortby=username
http://baseurl/users?sortby=id
即一般的傳參鹃骂,我們可以通過@Query注解方便的完成台盯,我們再次在接口中添加一個方法:
public interface IUserBiz
{
@GET("users")
Call<List<User>> getUsersBySort(@Query("sortby") String sort);
}
訪問的代碼
//省略retrofit的構(gòu)建代碼
Call<List<User>> call = userBiz.getUsersBySort("username");
//Call<List<User>> call = userBiz.getUsersBySort("id");
//省略call執(zhí)行相關(guān)代碼
ok,這樣我們就完成了參數(shù)的指定畏线,當然相同的方式也適用于 POST静盅,只需要把注解修改為 @POST 即可。
4. POST 請求體的方式向服務(wù)器傳入 json 字符串 @Body
大家都清楚寝殴,我們 app 很多時候跟服務(wù)器通信蒿叠,會選擇直接使用 POST 方式將 json 字符串作為請求體發(fā)送到服務(wù)器,那么我們看看這個需求使用 retrofit 該如何實現(xiàn)蚣常。
public interface IUserBiz
{
@POST("add")
Call<List<User>> addUser(@Body User user);
}
提交的代碼其實基本都是一致的:
//省略retrofit的構(gòu)建代碼
Call<List<User>> call = userBiz.addUser(new User(1001, "jj", "123,", "jj123", "jj@qq.com"));
//省略call執(zhí)行相關(guān)代碼
5. 表單的方式傳遞鍵值對 @FormUrlEncoded
模擬一下登錄的方法
public interface IUserBiz
{
@POST("login")
@FormUrlEncoded
Call<User> login(@Field("username") String username, @Field("password") String password);
}
訪問的代碼
//省略retrofit的構(gòu)建代碼
Call<User> call = userBiz.login("zhy", "123");
//省略call執(zhí)行相關(guān)代碼
看起來也很簡單市咽,通過 @POST 指明 url,添加 FormUrlEncoded史隆,然后通過 @Field 添加參數(shù)即可。
6. 單文件上傳 @Multipart
下面看下單文件上傳
public interface IUserBiz
{
@Multipart
@POST("register")
Call<User> registerUser(@Part MultipartBody.Part photo, @Part("username") RequestBody username, @Part("password") RequestBody password);
}
這里 @MultiPart 的意思就是允許多個 @Part 了曼验,我們這里使用了3個 @Part泌射,第一個我們準備上傳個文件,使用了 MultipartBody.Part 類型鬓照,其余兩個均為簡單的鍵值對熔酷。
File file = new File(Environment.getExternalStorageDirectory(), "icon.png");
RequestBody photoRequestBody = RequestBody.create(MediaType.parse("image/png"), file);
MultipartBody.Part photo = MultipartBody.Part.createFormData("photos", "icon.png", photoRequestBody);
Call<User> call = userBiz.registerUser(photo, RequestBody.create(null, "abc"), RequestBody.create(null, "123"));
這里感覺略為麻煩。不過還是蠻好理解~~多個 @Part豺裆,每個 Part 對應(yīng)一個 RequestBody拒秘。
7. 多文件上傳 @PartMap
public interface IUserBiz
{
@Multipart
@POST("register")
Call<User> registerUser(@PartMap Map<String, RequestBody> params, @Part("password") RequestBody password);
}
這里使用了一個新的注解 @PartMap,這個注解用于標識一個 Map臭猜,Map 的 key 為 String 類型躺酒,代表上傳的鍵值對的 key (與服務(wù)器接受的 key 對應(yīng)),value 即為 RequestBody,有點類似 @Part 的封裝版本蔑歌。
File file = new File(Environment.getExternalStorageDirectory(), "messenger_01.png");
RequestBody photo = RequestBody.create(MediaType.parse("image/png", file);
Map<String,RequestBody> photos = new HashMap<>();
photos.put("photos\"; filename=\"icon.png", photo);
photos.put("username", RequestBody.create(null, "abc"));
Call<User> call = userBiz.registerUser(photos, RequestBody.create(null, "123"));
可以看到羹应,可以在 Map 中 put 進一個或多個文件,鍵值對等次屠,當然你也可以分開园匹,單獨的鍵值對也可以使用 @Part雳刺,這里又看到設(shè)置文件的時候,相對應(yīng)的 key 很奇怪裸违,例如上例 "photos"; filename="icon.png", 前面的 photos 就是與服務(wù)器對應(yīng)的 key掖桦,后面 filename 是服務(wù)器得到的文件名,ok供汛,參數(shù)雖然奇怪枪汪,但是也可以動態(tài)的設(shè)置文件名,不太影響使用~~
8. 下載文件
下載直接使用okhttp就好了紊馏,這邊簡單介紹下
@GET("download")
Call<ResponseBody> downloadTest();
調(diào)用:
Call<ResponseBody> call = userBiz.downloadTest();
call.enqueue(new Callback<ResponseBody>()
{
@Override
public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response)
{
InputStream is = response.body().byteStream();
//save file
}
@Override
public void onFailure(Call<ResponseBody> call, Throwable t)
{
}
});
可以看到可以返回 ResponseBody料饥,那么很多事都能干了~~
but,也看出這種方式下載感覺非常雞肋朱监,并且 onReponse 回調(diào)雖然在UI線程岸啡,但是你還是要處理 io 操作,也就是說你在這里還要另外開線程操作赫编,或者你可以考慮同步的方式下載巡蘸。
配置 OkHttpClient
這個需要簡單提一下,很多時候擂送,比如你使用 retrofit 需要統(tǒng)一的 log 管理悦荒,給每個請求添加統(tǒng)一的 header 等,這些都應(yīng)該通過 okhttpclient 去操作嘹吨,比如 addInterceptor
例:
OkHttpClient client = new OkHttpClient.Builder().addInterceptor(new Interceptor()//log搬味,統(tǒng)一的header等
{
@Override
public okhttp3.Response intercept(Chain chain) throws IOException
{
return null;
}
}).build();
或許你需要更多的配置,你可以單獨寫一個 OkhttpClient 的單例生成類蟀拷,在這個里面完成你所需的所有的配置碰纬,然后將 OkhttpClient 實例通過方法公布出來,設(shè)置給 retrofit问芬。
設(shè)置方式:
Retrofit retrofit = new Retrofit.Builder()
.callFactory(OkHttpUtils.getClient())
.build();
callFactory方法接受一個okhttp3.Call.Factory對象悦析,OkHttpClient即為一個實現(xiàn)類。
retrofit 源碼解析
1. retrofit 如何為我們的接口實現(xiàn)實例
通過上文的學習此衅,我們發(fā)現(xiàn)使用 retrofit 需要去定義一個接口强戴,然后可以通過調(diào)用 retrofit.create(IUserBiz.class); 方法,得到一個接口的實例挡鞍,最后通過該實例執(zhí)行我們的操作骑歹,那么 retrofit 如何實現(xiàn)我們指定接口的實例呢?
其實原理是:動態(tài)代理墨微。但是不要被動態(tài)代理這幾個詞嚇唬到陵刹,Java 中已經(jīng)提供了非常簡單的 API 幫助我們來實現(xiàn)動態(tài)代理。
看源碼前先看一個例子:
public interface ITest
{
@GET("/heiheihei")
public void add(int a, int b);
}
public static void main(String[] args)
{
ITest iTest = (ITest) Proxy.newProxyInstance(ITest.class.getClassLoader(), new Class<?>[]{ITest.class}, new InvocationHandler()
{
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
{
Integer a = (Integer) args[0];
Integer b = (Integer) args[1];
System.out.println("方法名:" + method.getName());
System.out.println("參數(shù):" + a + " , " + b);
GET get = method.getAnnotation(GET.class);
System.out.println("注解:" + get.value());
return null;
}
});
iTest.add(3, 5);
}
輸出結(jié)果為:
方法名:add
參數(shù):3 , 5
注解:/heiheihei
可以看到我們通過 Proxy.newProxyInstance 產(chǎn)生的代理類,當調(diào)用接口的任何方法時衰琐,都會調(diào)用 InvocationHandler#invoke 方法也糊,在這個方法中可以拿到傳入的參數(shù),注解等羡宙。
試想狸剃,retrofit 也可以通過同樣的方式,在 invoke 方法里面狗热,拿到所有的參數(shù)钞馁,注解信息然后就可以去構(gòu)造 RequestBody,再去構(gòu)建 Request匿刮,得到 Call 對象封裝后返回僧凰。
ok,下面看 retrofit#create 的源碼:
public <T> T create(final Class<T> service) {
return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[] { service },
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object... args) throws Throwable {
});
}
哈熟丸,和上面對應(yīng)训措。到這里,你應(yīng)該明白 retrofit 為我們接口生成實例對象并不神奇光羞,僅僅是使用了 Proxy 這個類的 API 而已绩鸣,然后在 invoke 方法里面拿到足夠的信息去構(gòu)建最終返回的 Call 而已。
哈纱兑,其實真正的動態(tài)代理一般是有具體的實現(xiàn)類的呀闻,只是在這個類調(diào)用某個方法的前后去執(zhí)行一些別的操作,比如開事務(wù)潜慎,打 log 等等捡多。當然,本博文并不需要涉及這些詳細的內(nèi)容铐炫,如果你希望詳細去了解垒手,可以搜索關(guān)鍵字: Proxy InvocationHandler。
2. retrofit 整體實現(xiàn)流程
4.2.1 Retrofit 的構(gòu)建
這里依然是通過構(gòu)造者模式進行構(gòu)建 retrofit 對象驳遵,好在其內(nèi)部的成員變量比較少淫奔,我們直接看 build() 方法山涡。
public Builder() {
this(Platform.get());
}
public Retrofit build() {
if (baseUrl == null) {
throw new IllegalStateException("Base URL required.");
}
okhttp3.Call.Factory callFactory = this.callFactory;
if (callFactory == null) {
callFactory = new OkHttpClient();
}
Executor callbackExecutor = this.callbackExecutor;
if (callbackExecutor == null) {
callbackExecutor = platform.defaultCallbackExecutor();
}
// Make a defensive copy of the adapters and add the default Call adapter.
List<CallAdapter.Factory> adapterFactories = new ArrayList<>(this.adapterFactories);
adapterFactories.add(platform.defaultCallAdapterFactory(callbackExecutor));
// Make a defensive copy of the converters.
List<Converter.Factory> converterFactories = new ArrayList<>(this.converterFactories);
return new Retrofit(callFactory, baseUrl, converterFactories, adapterFactories,
callbackExecutor, validateEagerly);
}
- baseUrl 必須指定堤结,這個是理所當然的;
- 然后可以看到如果不著急設(shè)置 callFactory鸭丛,則默認直接 new OkHttpClient()竞穷,可見如果你需要對 okhttpclient 進行詳細的設(shè)置,需要構(gòu)建 OkHttpClient 對象鳞溉,然后傳入瘾带;
- 接下來是 callbackExecutor,這個想一想大概是用來將回調(diào)傳遞到 UI 線程了熟菲,當然這里設(shè)計的比較巧妙看政,利用 platform 對象朴恳,對平臺進行判斷,判斷主要是利用 Class.forName("") 進行查找允蚣,具體代碼已經(jīng)被放到文末于颖,如果是 Android 平臺,會自定義一個 Executor 對象嚷兔,并且利用 Looper.getMainLooper() 實例化一個 handler 對象森渐,在 Executor 內(nèi)部通過 handler.post(runnable),ok冒晰,整理憑大腦應(yīng)該能構(gòu)思出來同衣,暫不貼代碼了。
- 接下來是 adapterFactories壶运,這個對象主要用于對 Call 進行轉(zhuǎn)化耐齐,基本上不需要我們自己去自定義。
- 最后是 converterFactories前弯,該對象用于轉(zhuǎn)化數(shù)據(jù)蚪缀,例如將返回的 responseBody 轉(zhuǎn)化為對象等;當然不僅僅是針對返回的數(shù)據(jù)恕出,還能用于一般備注解的參數(shù)的轉(zhuǎn)化例如 @Body 標識的對象做一些操作询枚,后面遇到源碼詳細再描述。
4.2.2 具體Call構(gòu)建流程
我們構(gòu)造完成 retrofit浙巫,就可以利用 retrofit.create 方法去構(gòu)建接口的實例了金蜀,上面我們已經(jīng)分析了這個環(huán)節(jié)利用了動態(tài)代理,而且我們也分析了具體的 Call 的構(gòu)建流程在 invoke 方法中的畴,下面看代碼:
public <T> T create(final Class<T> service) {
Utils.validateServiceInterface(service);
//...
return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[] { service },
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object... args){
//...
ServiceMethod serviceMethod = loadServiceMethod(method);
OkHttpCall okHttpCall = new OkHttpCall<>(serviceMethod, args);
return serviceMethod.callAdapter.adapt(okHttpCall);
}
});
}
主要也就三行代碼渊抄,第一行是根據(jù)我們的 method 將其包裝成 ServiceMethod,第二行是通過 ServiceMethod 和方法的參數(shù)構(gòu)造 retrofit2.OkHttpCall 對象丧裁,第三行是通過 serviceMethod.callAdapter.adapt() 方法护桦,將 OkHttpCall 進行代理包裝;
下面一個一個介紹:
- ServiceMethod應(yīng)該是最復(fù)雜的一個類了煎娇,包含了將一個method轉(zhuǎn)化為Call的所有的信息二庵。
#Retrofit class
ServiceMethod loadServiceMethod(Method method) {
ServiceMethod result;
synchronized (serviceMethodCache) {
result = serviceMethodCache.get(method);
if (result == null) {
result = new ServiceMethod.Builder(this, method).build();
serviceMethodCache.put(method, result);
}
}
return result;
}
#ServiceMethod
public ServiceMethod build() {
callAdapter = createCallAdapter();
responseType = callAdapter.responseType();
if (responseType == Response.class || responseType == okhttp3.Response.class) {
throw methodError("'"
+ Utils.getRawType(responseType).getName()
+ "' is not a valid response body type. Did you mean ResponseBody?");
}
responseConverter = createResponseConverter();
for (Annotation annotation : methodAnnotations) {
parseMethodAnnotation(annotation);
}
int parameterCount = parameterAnnotationsArray.length;
parameterHandlers = new ParameterHandler<?>[parameterCount];
for (int p = 0; p < parameterCount; p++) {
Type parameterType = parameterTypes[p];
if (Utils.hasUnresolvableType(parameterType)) {
throw parameterError(p, "Parameter type must not include a type variable or wildcard: %s",
parameterType);
}
Annotation[] parameterAnnotations = parameterAnnotationsArray[p];
if (parameterAnnotations == null) {
throw parameterError(p, "No Retrofit annotation found.");
}
parameterHandlers[p] = parseParameter(p, parameterType, parameterAnnotations);
}
return new ServiceMethod<>(this);
}
直接看 build 方法,首先拿到這個 callAdapter 最終拿到的是我們在構(gòu)建 retrofit 里面時 adapterFactories 時添加的缓呛,即為:new ExecutorCallbackCall<>(callbackExecutor, call)催享,該 ExecutorCallbackCall 唯一做的事情就是將原本 call 的回調(diào)轉(zhuǎn)發(fā)至 UI 線程。
接下來通過 callAdapter.responseType() 返回的是我們方法的實際類型哟绊,例如: Call<User>,則返回 User 類型因妙,然后對該類型進行判斷。
接下來是 createResponseConverter 拿到 responseConverter 對象,其當然也是根據(jù)我們構(gòu)建 retrofit 時,addConverterFactory 添加的 ConverterFactory 對象來尋找一個合適的返回攀涵,尋找的依據(jù)主要看該 converter 能否處理你編寫方法的返回值類型铣耘,默認實現(xiàn)為 BuiltInConverters,僅僅支持返回值的實際類型為 ResponseBody 和 Void以故,也就說明了默認情況下涡拘,是不支持 Call<User> 這類類型的。
接下來就是對注解進行解析了据德,主要是對方法上的注解進行解析鳄乏,那么可以拿到 httpMethod 以及初步的 url(包含占位符)。
后面是對方法中參數(shù)中的注解進行解析棘利,這一步會拿到很多的 ParameterHandler 對象橱野,該對象在 toRequest() 構(gòu)造 Request 的時候調(diào)用其 apply 方法。
ok善玫,這里我們并沒有去一行一行查看代碼水援,其實意義也不太大,只要知道 ServiceMethod 主要用于將我們接口中的方法轉(zhuǎn)化為一個 Request 對象茅郎,于是根據(jù)我們的接口返回值確定了 responseConverter, 解析我們方法上的注解拿到初步的 url,解析我們參數(shù)上的注解拿到構(gòu)建 RequestBody 所需的各種信息蜗元,最終調(diào)用 toRequest 的方法完成 Request 的構(gòu)建。
- 接下來看 OkHttpCall 的構(gòu)建系冗,構(gòu)造函數(shù)僅僅是簡單的賦值
OkHttpCall(ServiceMethod<T> serviceMethod, Object[] args) {
this.serviceMethod = serviceMethod;
this.args = args;
}
- 最后一步是 serviceMethod.callAdapter.adapt(okHttpCall)
我們已經(jīng)確定這個 callAdapter 是 ExecutorCallAdapterFactory.get() 對應(yīng)代碼為:
final class ExecutorCallAdapterFactory extends CallAdapter.Factory {
final Executor callbackExecutor;
ExecutorCallAdapterFactory(Executor callbackExecutor) {
this.callbackExecutor = callbackExecutor;
}
@Override
public CallAdapter<Call<?>> get(Type returnType, Annotation[] annotations, Retrofit retrofit) {
if (getRawType(returnType) != Call.class) {
return null;
}
final Type responseType = Utils.getCallResponseType(returnType);
return new CallAdapter<Call<?>>() {
@Override public Type responseType() {
return responseType;
}
@Override public <R> Call<R> adapt(Call<R> call) {
return new ExecutorCallbackCall<>(callbackExecutor, call);
}
};
}
可以看到 adapt 返回的是 ExecutorCallbackCall 對象奕扣,繼續(xù)往下看:
static final class ExecutorCallbackCall<T> implements Call<T> {
final Executor callbackExecutor;
final Call<T> delegate;
ExecutorCallbackCall(Executor callbackExecutor, Call<T> delegate) {
this.callbackExecutor = callbackExecutor;
this.delegate = delegate;
}
@Override public void enqueue(final Callback<T> callback) {
if (callback == null) throw new NullPointerException("callback == null");
delegate.enqueue(new Callback<T>() {
@Override public void onResponse(Call<T> call, final Response<T> response) {
callbackExecutor.execute(new Runnable() {
@Override public void run() {
if (delegate.isCanceled()) {
// Emulate OkHttp's behavior of throwing/delivering an IOException on cancellation.
callback.onFailure(ExecutorCallbackCall.this, new IOException("Canceled"));
} else {
callback.onResponse(ExecutorCallbackCall.this, response);
}
}
});
}
@Override public void onFailure(Call<T> call, final Throwable t) {
callbackExecutor.execute(new Runnable() {
@Override public void run() {
callback.onFailure(ExecutorCallbackCall.this, t);
}
});
}
});
}
@Override public Response<T> execute() throws IOException {
return delegate.execute();
}
}
可以看出 ExecutorCallbackCall 僅僅是對 Call 對象進行封裝,類似裝飾者模式掌敬,只不過將其執(zhí)行時的回調(diào)通過 callbackExecutor 進行回調(diào)到UI線程中去了惯豆。
4.2.3 執(zhí)行Call
在 4.2.2 我們已經(jīng)拿到了經(jīng)過封裝的 ExecutorCallbackCall 類型的 call 對象,實際上就是我們實際在寫代碼時拿到的 call 對象奔害,那么我們一般會執(zhí)行 enqueue 方法楷兽,看看源碼是怎么做的
首先是 ExecutorCallbackCall.enqueue 方法,代碼在 4.2.2华临,可以看到除了將 onResponse 和 onFailure 回調(diào)到 UI 線程芯杀,主要的操作還是 delegate 完成的,這個 delegate 實際上就是 OkHttpCall 對象雅潭,我們看它的 enqueue 方法
@Override
public void enqueue(final Callback<T> callback)
{
okhttp3.Call call;
Throwable failure;
synchronized (this)
{
if (executed) throw new IllegalStateException("Already executed.");
executed = true;
try
{
call = rawCall = createRawCall();
} catch (Throwable t)
{
failure = creationFailure = t;
}
}
if (failure != null)
{
callback.onFailure(this, failure);
return;
}
if (canceled)
{
call.cancel();
}
call.enqueue(new okhttp3.Callback()
{
@Override
public void onResponse(okhttp3.Call call, okhttp3.Response rawResponse)
throws IOException
{
Response<T> response;
try
{
response = parseResponse(rawResponse);
} catch (Throwable e)
{
callFailure(e);
return;
}
callSuccess(response);
}
@Override
public void onFailure(okhttp3.Call call, IOException e)
{
try
{
callback.onFailure(OkHttpCall.this, e);
} catch (Throwable t)
{
t.printStackTrace();
}
}
private void callFailure(Throwable e)
{
try
{
callback.onFailure(OkHttpCall.this, e);
} catch (Throwable t)
{
t.printStackTrace();
}
}
private void callSuccess(Response<T> response)
{
try
{
callback.onResponse(OkHttpCall.this, response);
} catch (Throwable t)
{
t.printStackTrace();
}
}
});
}
沒有任何神奇的地方揭厚,內(nèi)部實際上就是 okhttp 的 Call 對象吃谣,也是調(diào)用 okhttp3.Call.enqueue 方法。
中間對于 okhttp3.Call 的創(chuàng)建代碼為:
private okhttp3.Call createRawCall() throws IOException
{
Request request = serviceMethod.toRequest(args);
okhttp3.Call call = serviceMethod.callFactory.newCall(request);
if (call == null)
{
throw new NullPointerException("Call.Factory returned null.");
}
return call;
}
可以看到桐款,通過 serviceMethod.toRequest 完成對 request 的構(gòu)建域仇,通過 request 去構(gòu)造 call 對象,然后返回.
中間還涉及一個 parseResponse 方法使碾,如果順利的話慰安,執(zhí)行的代碼如下:
Response<T> parseResponse(okhttp3.Response rawResponse) throws IOException
{
ResponseBody rawBody = rawResponse.body();
ExceptionCatchingRequestBody catchingBody = new ExceptionCatchingRequestBody(rawBody);
T body = serviceMethod.toResponse(catchingBody);
return Response.success(body, rawResponse);
通過 serviceMethod 對 ResponseBody 進行轉(zhuǎn)化狸臣,然后返回轰绵,轉(zhuǎn)化實際上就是通過 responseConverter 的 convert 方法粉寞。
#ServiceMethod
T toResponse(ResponseBody body) throws IOException {
return responseConverter.convert(body);
}
ok,關(guān)于 responseConverter 后面還會細說左腔,不用擔心唧垦。
到這里,我們整個源碼的流程分析就差不多了液样,目的就掌握一個大體的原理和執(zhí)行流程振亮,了解下幾個核心的類。
那么總結(jié)一下:
- 首先構(gòu)造retrofit鞭莽,幾個核心的參數(shù)呢坊秸,主要就是 baseurl,callFactory(默認okhttpclient),converterFactories,adapterFactories,excallbackExecutor。
- 然后通過 create 方法拿到接口的實現(xiàn)類澎怒,這里利用 Java 的 Proxy 類完成動態(tài)代理的相關(guān)代理
- 在 invoke 方法內(nèi)部褒搔,拿到我們所聲明的注解以及實參等,構(gòu)造 ServiceMethod喷面,ServiceMethod 中解析了大量的信息星瘾,最痛可以通過 toRequest 構(gòu)造出 okhttp3.Request 對象。有了 okhttp3.Request 對象就可以很自然的構(gòu)建出 okhttp3.call惧辈,最后 calladapter 對 Call 進行裝飾返回琳状。
- 拿到 Call 就可以執(zhí)行 enqueue 或者 execute 方法了
ok,了解這么多足以。
下面呢盒齿,有幾個地方需要注意算撮,一方面是一些特殊的細節(jié);另一方面就是 Converter县昂。
Retrofit 中的各類細節(jié)
1. 上傳文件中使用的奇怪 value 值
第一個問題涉及到文件上傳肮柜,還記得我們在單文件上傳那里所說的嗎?有種類似于hack的寫法倒彰,上傳文件是這么做的审洞?
public interface ApiInterface {
@Multipart
@POST ("/api/Accounts/editaccount")
Call<User> editUser (@Part("file_key\"; filename=\"pp.png"),@Part("username") String username);
}
首先我們一點明確,因為這里使用了 @ Multipart待讳,那么我們認為 @Part 應(yīng)當支持普通的 key-value芒澜,以及文件。
對于普通的 key-value 是沒問題的创淡,只需要這樣 @Part("username") String username痴晦。
那么對于文件,為什么需要這樣呢琳彩? @Part("file_key"; filename="pp.png")
這個 value 設(shè)置的值不用看就會覺得特別奇怪誊酌,然而卻可以正常執(zhí)行部凑,原因是什么呢?
原因是這樣的:
當上傳 key-value 的時候碧浊,實際上對應(yīng)這樣的代碼:
builder.addPart(Headers.of("Content-Disposition", "form-data; name=\"" + key + "\""),
RequestBody.create(null, params.get(key)));
也就是說涂邀,我們的 @Part 轉(zhuǎn)化為了
Headers.of("Content-Disposition", "form-data; name=\"" + key + "\"")
這么一看,很隨意箱锐,只要把key放進去就可以了比勉。
但是,retrofit2 并沒有對文件做特殊處理驹止,文件的對應(yīng)的字符串應(yīng)該是這樣的
Headers.of("Content-Disposition", "form-data; name="filekey";filename="filename.png");
與鍵值對對應(yīng)的字符串相比浩聋,多了個 ;filename="filename.png,就因為 retrofit 沒有做特殊處理臊恋,所以你現(xiàn)在看這些 hack 的做法
@Part("file_key\"; filename=\"pp.png")
拼接:==>
Content-Disposition", "form-data; name=\"" + key + "\"
結(jié)果:==>
Content-Disposition", "form-data; name=file_key\"; filename=\"pp.png\"
ok赡勘,到這里我相信你已經(jīng)理解了,為什么要這么做捞镰,而且為什么這么做可以成功闸与!
恩,值得一提的事岸售,因為這種方式文件名寫死了践樱,我們上文使用的的是 @Part MultipartBody.Part file,可以滿足文件名動態(tài)設(shè)置,這個方式貌似也是 2.0.1 的時候支持的凸丸。
上述相關(guān)的源碼:
#ServiceMethod
if (annotation instanceof Part) {
if (!isMultipart) {
throw parameterError(p, "@Part parameters can only be used with multipart encoding.");
}
Part part = (Part) annotation;
gotPart = true;
String partName = part.value();
Headers headers =
Headers.of("Content-Disposition", "form-data; name=\"" + partName + "\"",
"Content-Transfer-Encoding", part.encoding());
}
可以看到呢拷邢,并沒有對文件做特殊處理,估計下個版本說不定 @Part 會多個 isFile=true|false 屬性屎慢,甚至修改對應(yīng)形參瞭稼,然后在這里做簡單的處理。
ok腻惠,最后來到關(guān)鍵的 ConverterFactory 了~
自定義 Converter.Factory
1. responseBodyConverter
關(guān)于 Converter.Factory环肘,肯定是通過 addConverterFactory 設(shè)置的
Retrofit retrofit = new Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create())
.build();
該方法接受的是一個 Converter.Factory factory 對象
該對象呢,是一個抽象類集灌,內(nèi)部包含3個方法:
abstract class Factory {
public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations,
Retrofit retrofit) {
return null;
}
public Converter<?, RequestBody> requestBodyConverter(Type type,
Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit) {
return null;
}
public Converter<?, String> stringConverter(Type type, Annotation[] annotations,
Retrofit retrofit) {
return null;
}
}
可以看到呢悔雹,3 個方法都是空方法而不是抽象的方法,也就表明了我們可以選擇去實現(xiàn)其中的 1 個或多個方法欣喧,一般只需要關(guān)注 requestBodyConverter 和 responseBodyConverter 就可以了腌零。
ok,我們先看如何自定義唆阿,最后再看 GsonConverterFactory.create 的源碼益涧。
先來個簡單的,實現(xiàn) responseBodyConverter 方法驯鳖,看這個名字很好理解闲询,就是將 responseBody 進行轉(zhuǎn)化就可以了久免。
ok,這里呢嘹裂,我們先看一下上述中我們使用的接口:
package com.zhy.retrofittest.userBiz;
public interface IUserBiz
{
@GET("users")
Call<List<User>> getUsers();
@POST("users")
Call<List<User>> getUsersBySort(@Query("sort") String sort);
@GET("{username}")
Call<User> getUser(@Path("username") String username);
@POST("add")
Call<List<User>> addUser(@Body User user);
@POST("login")
@FormUrlEncoded
Call<User> login(@Field("username") String username, @Field("password") String password);
@Multipart
@POST("register")
Call<User> registerUser(@Part("photos") RequestBody photos, @Part("username") RequestBody username, @Part("password") RequestBody password);
@Multipart
@POST("register")
Call<User> registerUser(@PartMap Map<String, RequestBody> params, @Part("password") RequestBody password);
@GET("download")
Call<ResponseBody> downloadTest();
}
不知不覺,方法還蠻多的摔握,假設(shè)哈寄狼,我們這里去掉 retrofit 構(gòu)造時的 GsonConverterFactory.create,自己實現(xiàn)一個 Converter.Factory 來做數(shù)據(jù)的轉(zhuǎn)化工作氨淌。
首先我們解決 responseBodyConverter泊愧,那么代碼很簡單,我們可以這么寫:
public class UserConverterFactory extends Converter.Factory
{
@Override
public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit)
{
//根據(jù)type判斷是否是自己能處理的類型盛正,不能的話删咱,return null ,交給后面的Converter.Factory
return new UserConverter(type);
}
}
public class UserResponseConverter<T> implements Converter<ResponseBody, T>
{
private Type type;
Gson gson = new Gson();
public UserResponseConverter(Type type)
{
this.type = type;
}
@Override
public T convert(ResponseBody responseBody) throws IOException
{
String result = responseBody.string();
T users = gson.fromJson(result, type);
return users;
}
}
使用的時候呢,可以
Retrofit retrofit = new Retrofit.Builder()
.callFactory(new OkHttpClient()) .baseUrl("http://example/springmvc_users/user/")
//.addConverterFactory(GsonConverterFactory.create())
.addConverterFactory(new UserConverterFactory())
.build();
ok豪筝,這樣的話痰滋,就可以完成我們的 ReponseBody 到 List<User> 或者 User 的轉(zhuǎn)化了。
可以看出续崖,我們這里用的依然是 Gson敲街,那么有些同學肯定不希望使用 Gson 就能實現(xiàn),如果不使用 Gson 的話严望,一般需要針對具體的返回類型多艇,比如我們針對返回 List<User> 或者 User
你可以這么寫:
package com.zhy.retrofittest.converter;
/**
* Created by zhy on 16/4/30.
*/
public class UserResponseConverter<T> implements Converter<ResponseBody, T>
{
private Type type;
Gson gson = new Gson();
public UserResponseConverter(Type type)
{
this.type = type;
}
@Override
public T convert(ResponseBody responseBody) throws IOException
{
String result = responseBody.string();
if (result.startsWith("["))
{
return (T) parseUsers(result);
} else
{
return (T) parseUser(result);
}
}
private User parseUser(String result)
{
JSONObject jsonObject = null;
try
{
jsonObject = new JSONObject(result);
User u = new User();
u.setUsername(jsonObject.getString("username"));
return u;
} catch (JSONException e)
{
e.printStackTrace();
}
return null;
}
private List<User> parseUsers(String result)
{
List<User> users = new ArrayList<>();
try
{
JSONArray jsonArray = new JSONArray(result);
User u = null;
for (int i = 0; i < jsonArray.length(); i++)
{
JSONObject jsonObject = jsonArray.getJSONObject(i);
u = new User();
u.setUsername(jsonObject.getString("username"));
users.add(u);
}
} catch (JSONException e)
{
e.printStackTrace();
}
return users;
}
}
這里簡單讀取了一個屬性,大家肯定能看懂像吻,這樣就能滿足返回值是 Call<List<User>> 或者 Call<User>.
這里鄭重提醒:如果你針對特定的類型去寫 Converter峻黍,一定要在 UserConverterFactory#responseBodyConverter 中對類型進行檢查,發(fā)現(xiàn)不能處理的類型 return null拨匆,這樣的話姆涩,可以交給后面的 Converter.Factory處理,比如本例我們可以按照下列方式檢查:
public class UserConverterFactory extends Converter.Factory
{
@Override
public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit)
{
//根據(jù)type判斷是否是自己能處理的類型惭每,不能的話阵面,return null ,交給后面的Converter.Factory
if (type == User.class)//支持返回值是User
{
return new UserResponseConverter(type);
}
if (type instanceof ParameterizedType)//支持返回值是List<User>
{
Type rawType = ((ParameterizedType) type).getRawType();
Type actualType = ((ParameterizedType) type).getActualTypeArguments()[0];
if (rawType == List.class && actualType == User.class)
{
return new UserResponseConverter(type);
}
}
return null;
}
}
好了,到這呢 responseBodyConverter 方法告一段落了洪鸭,謹記就是將 reponseBody-> 返回值返回中的實際類型样刷,例如 Call<User> 中的 User;還有對于該 converter 不能處理的類型一定要返回 null。
2. requestBodyConverter
ok览爵,上面接口一大串方法呢置鼻,使用了我們的 Converter 之后,有個方法我們現(xiàn)在還是不支持的蜓竹。
@POST("add")
Call<List<User>> addUser(@Body User user);
ok箕母,這個 @Body 需要用到這個方法储藐,叫做 requestBodyConverter,根據(jù)參數(shù)轉(zhuǎn)化為 RequestBody嘶是,下面看下我們?nèi)绾翁峁┲С帧?/p>
public class UserRequestBodyConverter<T> implements Converter<T, RequestBody>
{
private Gson mGson = new Gson();
@Override
public RequestBody convert(T value) throws IOException
{
String string = mGson.toJson(value);
return RequestBody.create(MediaType.parse("application/json; charset=UTF-8"),string);
}
}
然后在 UserConverterFactory 中復(fù)寫 requestBodyConverter 方法钙勃,返回即可:
public class UserConverterFactory extends Converter.Factory
{
@Override
public Converter<?, RequestBody> requestBodyConverter(Type type, Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit)
{
return new UserRequestBodyConverter<>();
}
}
這里偷了個懶,使用 Gson 將對象轉(zhuǎn)化為 json 字符串了聂喇,如果你不喜歡使用框架辖源,你可以選擇拼接字符串,或者反射寫一個支持任何對象的希太,反正就是對象 ->json 字符串的轉(zhuǎn)化克饶。最后構(gòu)造一個 RequestBody 返回即可。
ok,到這里誊辉,我相信如果你看的細致矾湃,自定義 Converter.Factory 是干嘛的,但是我還是要總結(jié)下:
- responseBodyConverter 主要是對應(yīng)@Body注解堕澄,完成 ResponseBody 到實際的返回類型的轉(zhuǎn)化邀跃,這個類型對應(yīng) Call<XXX> 里面的泛型 XXX,其實 @Part 等注解也會需要 responseBodyConverter蛙紫,只不過我們的參數(shù)類型都是 RequestBody坞嘀,由默認的 converter 處理了。
- requestBodyConverter 完成對象到 RequestBody 的構(gòu)造惊来。
- 一定要注意丽涩,檢查type如果不是自己能處理的類型,記得 return null (因為可以添加多個裁蚁,你不能處理 return null ,還會去遍歷后面的 converter).
值得學習的API
其實一般情況下看源碼呢矢渊,可以讓我們更好的去使用這個庫,當然在看的過程中如果發(fā)現(xiàn)了一些比較好的處理方式呢枉证,是非常值得記錄的矮男。如果每次看別人的源碼都能吸取一定的精華,比你單純的去理解會好很多室谚,因為你的記憶力再好毡鉴,源碼解析你也是會忘的,而你記錄下來并能夠使用的優(yōu)越的代碼秒赤,可能用久了就成為你的代碼了猪瞬。
我舉個例子:比如 retrofit2 中判斷當前運行的環(huán)境代碼如下,如果下次你有這樣的需求入篮,你也可以這么寫陈瘦,甚至源碼中根據(jù)不同的運行環(huán)境還提供了不同的 Executor 都很值得記錄:
class Platform {
private static final Platform PLATFORM = findPlatform();
static Platform get() {
return PLATFORM;
}
private static Platform findPlatform() {
try {
Class.forName("android.os.Build");
if (Build.VERSION.SDK_INT != 0) {
return new Android();
}
} catch (ClassNotFoundException ignored) {
}
try {
Class.forName("java.util.Optional");
return new Java8();
} catch (ClassNotFoundException ignored) {
}
try {
Class.forName("org.robovm.apple.foundation.NSObject");
return new IOS();
} catch (ClassNotFoundException ignored) {
}
return new Platform();
}
原文鏈接 :http://blog.csdn.net/lmj623565791/article/details/51304204