Retrofit2分析

磊少工作室_CTO發表於2019-01-03

Retrofit2已經面世很久了,有很多好的文章分析過,這篇文章我只想記錄自己閱讀Retrofit 2.3.0原始碼後的分析過程,如何閱讀原始碼以及分析我覺得是最重要的。

目錄

  1. Retrofit2 使用
    • 使用步驟
  2. 原始碼分析
    • 構建 Retrofit 物件
    • 動態代理
    • 呼叫流程
  3. 自定義 ConverterFactory
  4. 總結

一、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使用文件

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 共同使用的原理。最近想到一句話感覺蠻有意思的:當你不知道做什麼的時候,去看看原始碼或官方文件吧。

相關文章