Retrofit2已經面世很久了,有很多好的文章分析過,這篇文章我只想記錄自己閱讀Retrofit 2.3.0原始碼後的分析過程,如何閱讀原始碼以及分析我覺得是最重要的。
目錄
- Retrofit2 使用
- 使用步驟
- 原始碼分析
- 構建 Retrofit 物件
- 動態代理
- 呼叫流程
- 自定義 ConverterFactory
- 總結
一、Retrofit2使用
1. 使用步驟
之前做過Gank的客戶端,因此直接用Gank網站的請求了。
構建retrofit物件:
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://gank.io")
.build();
複製程式碼
定義請求interface, 可以把它內部的每一個介面方法看做是一個封裝了請求url及引數的方法,其返回值是可執行請求的物件,而在 Retrofit 中預設是 Call 可執行物件,也就是說 call 呼叫某個方法,如 enqueue 就可以非同步執行請求。
public interface ApiService {
@GET("api/data/{type}/{size}/{page}")
Call<ResponseBody> getArticles(@Path("type") String type, @Path("size") int size,
@Path("page") int page);
}
複製程式碼
生成代理物件:
ApiService apiService = retrofit.create(ApiService.class);
複製程式碼
獲取 call 這個可執行請求的物件,並enqueue非同步執行請求:
Call<ResponseBody> call = apiService.getArticles("Android",10, 1);
call.enqueue(new Callback<ResponseBody>() {
@Override
public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
try {
Log.d(TAG,response.body().string());
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void onFailure(Call<ResponseBody> call, Throwable t) {
}
});
複製程式碼
call.enqueue會執行真正的請求,並且內部會切換到主執行緒回撥到 onResponse 方法內可拿到最終結果。
Retrofit本質上是對okhttp3的封裝,其最大的優勢就是解耦做的非常好,接下來就從原始碼角度分析下。
二、原始碼分析
1. 構建Retrofit物件
構建Retrofit用到了 Builder 模式,很適合自定義一些東西。先走到 baseUrl 方法內看下:
public Builder baseUrl(String baseUrl) {
checkNotNull(baseUrl, "baseUrl == null");
HttpUrl httpUrl = HttpUrl.parse(baseUrl);
if (httpUrl == null)
throw new IllegalArgumentException("Illegal URL: " + baseUrl);
}
return baseUrl(httpUrl);
}
複製程式碼
將字串型別的url解析成 HttpUrl物件,這個物件可以理解為拆分了url的協議、域名、埠、路徑等變數並儲存在了一個物件中,需要哪個部分就可以隨時拿出,最後儲存下 httpUrl 變數。
最簡單的就是設定一個地址,然後直接呼叫 build 方法:
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);
}
複製程式碼
如果不在構建Retrofit時設定 client 那麼就會預設建立一個 OkHttpClient, 前面已經說過 Retrofit 是對 okhttp 的封裝而已,本質上還是okhttp 進行請求,而現在只是分析 Retrofit, 因此不打算深入OkHttpClient, 但是根據它的介面型別也能判斷是一個 生產okhttp中的Call物件的工廠, 而Call物件就是可執行任務的物件, Retrofit 中也有 Call 導致有點混亂。。需要好好辨別下。
若不設定 callbackExecutor 也會通過 platform.defaultCallbackExecutor() 建立一個預設的,通過看原始碼發現Retrofit也就支援兩個平臺,一個是Java8,一個就是Android :
static class Android extends Platform {
@Override public Executor defaultCallbackExecutor() {
return new MainThreadExecutor();
}
@Override CallAdapter.Factory defaultCallAdapterFactory(@Nullable Executor
callbackExecutor) {
if (callbackExecutor == null) throw new AssertionError();
return new ExecutorCallAdapterFactory(callbackExecutor);
}
static class MainThreadExecutor implements Executor {
private final Handler handler = new Handler(Looper.getMainLooper());
@Override public void execute(Runnable r) {
handler.post(r);
}
}
}
複製程式碼
它會返回一個 MainThreadExecutor 物件,而它的實現非常簡單,就是通過Handler將執行緒切換到主執行緒,記住這個 callbackExecutor , 它會在某個地方執行 execute 方法這個時候會切到主執行緒進行回撥。
adapterFactories列表物件可以把它理解為 生產 adapter 的工廠,而 adapter 從名字上來看是介面卡,它呼叫其中的 adapt 方法返回的就是可執行物件!在 Retrofit 裡預設的實現就是 Call 物件,並且由於 Retrofit 的神奇解耦,它可以自定義任何CallAdapter 和 Factory,最有名的就是RxJava,其返回的可執行物件變成了Observable而已 ,列表表示可以新增多個 callAdapter工廠, 根據你寫的介面方法的返回值判斷選擇哪個介面卡。
預設情況下呼叫 platform.defaultCallbackExecutor() 建立 ExecutorCallAdapterFactory ,這個類很關鍵,工廠顧名思義就是生產CallAdapter的,其中的 get 方法返回了一個 CallAdapter 物件,這個物件會在某個關鍵時刻呼叫 adapt 從而返回 Call 物件,這個呼叫流程之後再詳解。
converterFactories 轉換器工廠,其套路和 adapterFactories 非常類似,它的作用就是將請求後返回的資料轉換成你想要的資料結構,應用最廣的應該是 GsonConverterFactory , 可以將結果用 Gson 轉換成自定義好的資料結構。其預設實現是 BuiltInConverters ,這個預設的工廠返回的 Converter 基本沒做什麼事情,基本只是把 okhttp 返回給 Retrofit 的 ResponseBody 資料結構返回出去。因此暫時不需要太過在意。
最後以這些物件作為引數傳給 Retrofit 構造一個 Retrofit 例項。總結下關鍵的幾個物件:將 url 解析成 HttpUrl 物件並儲存供以後使用;在不自定義的情況下預設建立 callFactory, 這個 OkHttpClient 的例項,最終會通過這個工廠生成 okhttp 中的 call 物件來執行真正的請求;callbackExecutor預設實現是 MainThreadExecutor 是用來最後切換到主執行緒用的;adapterFactories用來生產adapter,預設是 ExecutorCallAdapterFactory,最後會返回 Call 物件;converterFactories預設實現是 BuiltInConverters, 基本上就是將 okhttp 返回的 ResponseBody 返回出去。
2. 動態代理
動態代理這個可以說是 Retrofit 最精華的地方了。再此之前需要理解反射和動態代理,推薦兩篇不錯的文章:反射、動態代理。
構建完 Retrofit 物件後,需要呼叫 retrofit.create(ApiService.class), 生成 ApiService 介面對應的動態代理物件。
public <T> T create(final Class<T> service) {
// ......
return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[] { service },
new InvocationHandler() {
// 獲取目前的平臺,對於我們來說就是Android平臺
private final Platform platform = Platform.get();
@Override public Object invoke(Object proxy, Method method, @Nullable Object[] args)
throws Throwable {
// ......
// 構建 介面方法 物件,此物件主要是解析註解並拼接成請求所需的引數。
// 它是最後用來給okhttp使用並真正傳送請求。
ServiceMethod<Object, Object> serviceMethod =
(ServiceMethod<Object, Object>) loadServiceMethod(method);
// 本質上就是Retrofit對Okhttp的Call物件進一步的封裝
OkHttpCall<Object> okHttpCall = new OkHttpCall<>(serviceMethod, args);
// 最後預設返回的也是一個Call可執行任務物件(當然RxJava返回的物件就不是預設的了),
// 其實類似於是OkHttpCall的代理類,只不過內部加了切換到主執行緒的操作。
return serviceMethod.callAdapter.adapt(okHttpCall);
}
});
}
複製程式碼
部分沒啥用的程式碼省略了,剩下的都是關鍵程式碼,並且加了註釋。這裡再說下動態代理相關的,create 方法返回值是第一部分的使用步驟中的 apiService, 它是動態代理生成的物件。呼叫動態代理物件的方法後會調到 invoke 方法,返回值對應使用步驟中的 call 物件。
進入 loadServiceMethod 方法,可以看到對 serviceMethod 會有一個快取,一個方法只會解析一次,之後重複利用。然後通過 build 構建一個 ServiceMethod 物件。
public ServiceMethod build() {
// 跟進 createCallAdapter 可以看到它返回的 CallAdapter 是根據方法的返回型別,
// 如本文使用步驟中返回型別是 Call,那麼就會返回之前已建立過的預設 CallAdapter
callAdapter = createCallAdapter();
// 返回結果型別,對於 Retrofit 預設返回結果型別是 ResponseBody,
// 因此在閱讀原始碼時直接把 responseType 看做 ResponseBody 型別。
responseType = callAdapter.responseType();
// ......
// 跟進 createResponseConverter 方法可以看到它是根據 responseType 返回對應的 Converter。
responseConverter = createResponseConverter();
// 遍歷解析方法上的註解如 @GET、@POST等(拆分註解中的字串,將引數名記錄)
for (Annotation annotation : methodAnnotations) {
parseMethodAnnotation(annotation);
}
// ......
for (int p = 0; p < parameterCount; p++) {
Type parameterType = parameterTypes[p];
// ......
Annotation[] parameterAnnotations = parameterAnnotationsArray[p];
// ......
// 遍歷解析形參的註解,如@Path、@Query等.
parameterHandlers[p] = parseParameter(p, parameterType, parameterAnnotations);
}
// ......
return new ServiceMethod<>(this);
}
複製程式碼
其實 ServiceMethod 不是理解 Retrofit 的關鍵,只需知道它封裝瞭解析註解邏輯和記錄引數。
直接看下 OkhttpCall 的enqueue方法
@Override public void enqueue(final Callback<T> callback) {
okhttp3.Call call;
synchronized (this) {
// ......
// 這裡的 call 就是 OkHttp 的 call
call = rawCall = createRawCall();
}
// ......
// okhttp 的 call 非同步執行並回撥,注意這裡依然在非同步執行緒中。
call.enqueue(new okhttp3.Callback() {
@Override public void onResponse(okhttp3.Call call, okhttp3.Response rawRespon
throws IOException {
Response<T> response;
try {
response = parseResponse(rawResponse);
} catch (Throwable e) {
callFailure(e);
return;
}
callSuccess(response);
}
// ....
private void callSuccess(Response<T> response) {
try {
callback.onResponse(OkHttpCall.this, response);
} catch (Throwable t) {
t.printStackTrace();
}
}
});
}
複製程式碼
最後一步通過 callAdapter.adapt(okHttpCall) 返回一個最終可執行物件,這裡我們都是看預設的,因此 callAdapter 是 ExecutorCallAdapterFactrory 中 get 獲取的 callAdapter, 其 adapter 方法返回的是 ExecutorCallbackCall 也是實現了 Call 介面,之前說過此物件是 okHttpCall 的代理物件,因此傳入 okHttpCall 例項,而內部乾的最關鍵的一件事就是在非同步執行請求完成後通過 callBackExecutor(之前早已準備好的Handler切換) 切換到主執行緒。
3. 呼叫流程
綠色線框代表我們使用 Retrofit 需要做的幾步。
Retrofit 內部有大量的設計模式,設計的非常巧妙,多看原始碼也能提高我們的程式碼設計能力。build 階段用了構造者模式,create 用了動態代理模式,CallAdapterFactory 和 ConverterFactory 用了介面卡模式,等等。
三、 自定義ConverterFactory
為了加深對Retrofit的理解以及體會它的好用程度,寫了一個自定義ConverterFactory。
public final class StringConverterFactory extends Converter.Factory {
private final static String TAG = "StringConverterFactory";
@Nullable
@Override
public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit) {
if(type == String.class){
return StringResponseConverter.INSTANCE;
}
return null;
}
@Nullable
@Override
public Converter<?, RequestBody> requestBodyConverter(Type type, Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit) {
if(type == RequestBody.class){
return StringRequestBodyConverter.INSTANCE;
}
return null;
}
final static class StringResponseConverter implements Converter<ResponseBody, String> {
final static StringResponseConverter INSTANCE = new StringResponseConverter();
@Override
public String convert(ResponseBody value) throws IOException {
Log.d(TAG, value.toString());
return value.string();
}
}
final static class StringRequestBodyConverter implements Converter<RequestBody, RequestBody> {
final static StringRequestBodyConverter INSTANCE = new StringRequestBodyConverter();
@Override
public RequestBody convert(RequestBody value) throws IOException {
Log.d(TAG, "no change, hahaha...");
return value;
}
}
}
複製程式碼
支援String的泛型引數,其實內部也就是把 ResponseBody 提前轉成 String 並列印而已。在使用的時候需要在構建 Retrofit 時新增:
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://gank.io")
.addConverterFactory(new StringConverterFactory())
.build();
複製程式碼
四、總結
最後對 Retrofit 做一個總結 。Retrofit 是對 okhttp3 的封裝,其最大的特性就是解耦,你可以自定義很多你想要的東西,最知名的就是 RxJava 的配合使用。
首先呼叫 build 方法建立生產 okhttp3 的 call 物件的callFactory、建立用來切換執行緒的 callBackExecutor、建立生產 CallAdapter 的 CallAdapterFactory、建立生產 Converter 的 ConverterFactory。
接著呼叫 retrofit.create 建立動態代理物件,呼叫介面方法會觸發 invoke 方法,invoke 內建立 ServiceMethod並做了註解解析、建立OkhttpCall(對okhttp3的call進一步封裝),最後通過 CallAdapter.adapt 方法返回可執行物件預設是 ExecutorCallBackCall。
最後呼叫 call.enqueue 後 okhttp3將請求 ServiceMethod 解析好的url和引數,最終返回結果會被 Converter.convert 解析成你想要的資料模型(預設是ResponseBody),最後通過 callBackExecutor.execute 切換到主執行緒將資料回撥給開發者。
ps:做完 Retrofit 原始碼分析後,還想看下 RxJava 的原始碼,為了更深入理解 RxJava 和 Retrofit 共同使用的原理。最近想到一句話感覺蠻有意思的:當你不知道做什麼的時候,去看看原始碼或官方文件吧。