從架構角度看Retrofit的作用、原理和啓示

轉載地址:https://www.jianshu.com/p/f57b7cdb1c99

 

Retrofit是squareup公司的開源力作,和同屬squareup公司開源的OkHttp,一個負責網絡調度,一個負責網絡執行,爲Android開發者提供了即方便又高效的網絡訪問框架。

不過,對於Retrofit這樣設計精妙、代碼簡潔、使用方便的優秀開源項目,不能僅知道如何擴展和使用,或者僅研究它採用的技術或模式,“技”當然重要,但不能忽視了背後的“道”。

對於Retrofit,我們還應該看到的,是她在優化App架構方面的努力,以及她在提升開發效率方面的借鑑和啓示。

本文試圖通過一個具體場景,先總結Retrofit在架構中起到的作用,再分析其實現原理,最後探討Retrofit給我們帶來的啓示。

我們先通過一個簡單的應用場景來回顧Retrofit的使用過程。

基本場景

通常來說,使用Retrofit要經過這樣幾個步驟

  1. 引用
    在gradle文件中引用retrofit
    compile 'com.squareup.retrofit2:retrofit:2.3.0'
    compile 'com.squareup.retrofit2:retrofit-converters:2.3.0'
    compile 'com.squareup.retrofit2:retrofit-adapters:2.3.0'

如果需要使用更多擴展功能,比如gson轉換,rxjava適配等,可以視自己需要繼續添加引用

    compile 'com.squareup.retrofit2:converter-gson:2.3.0'
    compile 'com.squareup.retrofit2:adapter-rxjava2:2.3.0'

如果現有的擴展包不能滿足需要,還可以自己擴展converter,adapter等。

  1. 定義接口
    Retrofit要求定義一個網絡請求的接口,接口函數裏要定義url路徑、請求參數、返回類型。
public interface INetApiService {
    @GET("/demobiz/api.php")
    Call<BizEntity> getBizInfo(@Query("id") String id);
}

在這個接口定義中,用註解@GET("/demobiz/api.php")聲明瞭url路徑,用註解@Query("id") 聲明瞭請求參數。
最重要的是,用Call<BizEntity>聲明瞭返回值是一個Retrofit的Call對象,並且聲明瞭這個對象處理的數據類型爲BizEntity,BizEntity是我們自定義的數據模型。

  1. 依次獲得Retrofit對象、接口實例對象、網絡工作對象
    首先,需要新建一個retrofit對象。
    然後,根據上一步的接口,實現一個retrofit加工過的接口對象。
    最後,調用接口函數,得到一個可以執行網絡訪問的網絡工作對象。
//新建一個Retrofit對象
Retrofit retrofit=new Retrofit.Builder()
.baseUrl(Config.DOMAIN)//要訪問的網絡地址域名,如http://www.zhihu.com
.addConverterFactory(GsonConverterFactory.create())
.build();
...

//用retrofit加工出對應的接口實例對象
INetApiService netApiService= retrofit.create(INetApiService.class);
//可以繼續加工出其他接口實例對象
IOtherService otherService= retrofit.create(IOtherService.class);
···

//調用接口函數,獲得網絡工作對象
Call<BizEntity> callWorker= netApiService.getBizInfo("id001");

這個複雜的過程下來,最終得到的callWorker對象,纔可以執行網絡訪問。

  1. 訪問網絡數據
    用上一步獲取的worker對象,執行網絡請求
callWorker.enqueue(new Callback<BizEntity>() {
            @Override
            public void onResponse(Call<BizEntity> call, Response<BizEntity> response) {...}
            @Override
            public void onFailure(Call<BizEntity> call, Throwable t) {...}
        });

在回調函數裏,取得我們需要的BizEntity數據對象。
網絡訪問結束。

角色與作用

我們從上面的應用場景可以看出,Retrofit並不做網絡請求,只是生成一個能做網絡請求的對象。
Retrofit的作用是按照接口去定製Call網絡工作對象

什麼意思?就是說:
Retrofit不直接做網絡請求
Retrofit不直接做網絡請求
Retrofit不直接做網絡請求

重要的事情說三遍。

網絡請求的目標雖然是數據,但是我們需要爲這個數據寫大量的配套代碼,發起請求的對象Call,接收數據的對象CallBack,做數據轉換的對象Converter,以及檢查和處理異常的對象等。
這對於一個項目的開發、擴展和維護來說,都是成本和風險。

而Retrofit做的事情,就是爲開發者節省這部分的工作量,Retrofit一方面從底層統一用OkHttp去做網絡處理;另一方面在外層靈活提供能直接融入業務邏輯的Call網絡訪問對象。

具體來說,Retrofit只負責生產對象,生產能做網絡請求的工作對象,他有點像一個工廠,只提供產品,工廠本身不處理網絡請求,產品才能處理網絡請求。
Retrofit在網絡請求中的作用大概可以這樣理解:

 

Retrofit的作用

 

我們看到,從一開始,Retrofit要提供的就是個Call工作對象。
換句話說,對於給Retrofit提供的那個接口

public interface INetApiService {
    @GET("/demobiz/api.php")
    Call<BizEntity> getBizInfo(@Query("id") String id);
}

這個接口並不是傳統意義上的網絡請求接口,這個接口不是用來獲取數據的接口,而是用來生產對象的接口,這個接口相當於一個工廠,接口中每個函數的返回值不是網絡數據,而是一個能進行網絡請求的工作對象,我們要先調用函數獲得工作對象,再用這個工作對象去請求網絡數據。

所以Retrofit的實用價值意義在於,他能根據你的接口定義,靈活地生成對應的網絡工作對象,然後你再擇機去調用這個對象訪問網絡。
理解了這一點,我們才能去擴展Retrofit,並理解Retrofit的設計思想。

功能擴展

我們先來看Retrofit能擴展哪些功能,然後再去理解Retrofit的工作原理。
Retrofit主要可以擴展三個地方:

  1. OkHttpClient
    Retrofit使用OkHttpClient來實現網絡請求,這個OkHttpClient雖然不能替換爲其他的網絡執行框架比如Volley,但是Retrofit允許我們使用自己擴展OkHttpClient,一般最常擴展的就是Interceptor攔截器了
OkHttpClient mClient = new OkHttpClient.Builder()
                .addInterceptor(new Interceptor() {
                    @Override
                    public Response intercept(Chain chain) throws IOException {
                        try {
                            Request.Builder builder = chain.request().newBuilder();
                            builder.addHeader("Accept-Charset", "UTF-8");
                            builder.addHeader("Accept", " application/json");
                            builder.addHeader("Content-type", "application/json");
                            Request request = builder.build();
                            return chain.proceed(request);
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                        return null;
                    }
                }).build();

Retrofit retrofit = new Retrofit.Builder()
                .baseUrl(Config.DOMAIN)
                .addConverterFactory(GsonConverterFactory.create())
                .client(mClient)
                .build();
  1. addConverterFactory

擴展的是對返回的數據類型的自動轉換,把一種數據對象轉換爲另一種數據對象。
在上述場景中,GsonConverterFactory可以把Http訪問得到的json字符串轉換爲Java數據對象BizEntity,這個BizEntity是在INetApiService接口中要求的的。
這種轉換我們自己也經常做,很好理解。
如果現有的擴展包不能滿足需要,可以繼承Retrofit的接口。retrofit2.Converter<F,T>,自己實現Converter和ConverterFactory。
在創建Retrofit對象時,可以插入我們自定義的ConverterFactory。

//retrofit對象
Retrofit retrofit=new Retrofit.Builder()
.baseUrl(Config.DOMAIN)
.addConverterFactory(GsonConverterFactory.create())
.addConverterFactory(YourConverterFactory.create())//添加自定義Converter
.build();
  1. addCallAdapterFactory

擴展的是對網絡工作對象callWorker的自動轉換,把Retrofit中執行網絡請求的Call對象,轉換爲接口中定義的Call對象。
這個轉換不太好理解,我們可以對照下圖來理解:

 

callAdapter轉換Call對象

 

Retrofit本身用一個OkHttpCall的類負責處理網絡請求,而我們在接口中定義需要定義很多種Call,例如Call<BizEntity>,或者Flowable<BizEntity>等,接口裏的Call和Retrofit裏的OkHttpCall並不一致,所以我們需要用一個CallAdapter去做一個適配轉換。
(Retrofit底層雖然使用了OkHttpClient去處理網絡請求,但她並沒有使用okhttp3.call這個Call接口,而是自己又建了一個retrofit2.Call接口,OkHttpCall繼承的是retrofit2.Call,與okhttp3.call只是引用關係。
這樣的設計符合依賴倒置原則,可以儘可能的與OkHttpClient解耦。)

這其實是Retrofit非常核心,也非常好用的一個設計,如果我們在接口中要求的函數返回值是個RxJava的Flowable對象

public interface INetApiService {
    @GET("/demobiz/api.php")
    Flowable<BizEntity> getBizInfo(@Query("id") String id);
}

那麼我們只需要爲Retrofit添加對應的擴展

//retrofit對象
Retrofit retrofit=new Retrofit.Builder()
.baseUrl(Config.DOMAIN)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.build();

就能得到Flowable類型的callWorker對象

//用retrofit加工出對應的接口實例對象
INetApiService netApiService= retrofit.create(INetApiService.class);
···
//調用接口函數,獲得網絡工作對象
Flowable<BizEntity> callWorker= netApiService.getBizInfo("id001");

在這裏,callAdapter做的事情就是把retrofit2.Call對象適配轉換爲Flowable<T>對象。
同樣,如果現有的擴展包不能滿足需要,可以繼承Retrofit的接口retrofit2.CallAdapter<R,T>,自己實現CallAdapter和CallAdapterFactory。

Retrofit實現原理

Retrofit固然設計精妙,代碼簡潔,使用方便,但相應的,我們要理解Retrofit的實現原理也不太容易,這麼精妙的設計是極佳的研究素材,我們不能僅僅停留在知道怎麼使用,怎麼擴展的階段,那實在是對這個優秀開源項目的浪費。
其實,Retrofit使用的,就是動態代理,方法註解、建造者和適配器等成熟的技術或模式,但是由於她的設計緊湊,而且動態代理屏蔽了很多過程上的細節,所以比較難以理解。

Retrofit實現原理——從動態代理開始

從前面的使用場景可知,retrofit會生成一個接口實例。

//用retrofit加工出對應的接口實例對象
INetApiService netApiService= retrofit.create(INetApiService.class);

到Retrofit源碼裏看create函數,是一個動態代理。

 public <T> T create(final Class<T> service) {
    ...
    return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[] { service },
        new InvocationHandler() {
            ...
            ServiceMethod<Object, Object> serviceMethod =
                (ServiceMethod<Object, Object>) loadServiceMethod(method);
            OkHttpCall<Object> okHttpCall = new OkHttpCall<>(serviceMethod, args);
            return serviceMethod.callAdapter.adapt(okHttpCall);
          }
        });
  }

要理解動態代理,最好要看到動態生成的代理類。

由於動態代理是在運行時動態生成的代理類,用常規的反編譯方法無法查看,一般要使用Java提供的sun.misc.ProxyGenerator.generateProxyClass(String proxyName,class[] interfaces)函數生成代理類,函數會返回byte[]字節碼,然後對字節碼反編譯得到Java代碼。
有一個小問題是,AndroidStudio並不提供sun.misc這個包,我們需要用IntelliJ或者Eclipse建立一個Java工程,在Java環境裏調用這個函數。

拿到的代理類,大概是這樣的:

public final class INetApiService extends Proxy implements INetApiService {
  ...//一些Object自帶方法
  private static Method m3;//接口定義的方法
  static {
    try {
      //Object自帶方法的初始化
      m0,m1,m2 = ...
      //接口中定義的方法
      m3 = Class.forName("com.demo.net$INetApiService")//反射接口類
          .getMethod("getBizInfo",//反射函數
              new Class[] { Class.forName("java.lang.String") });//反射參數
      //接口中定義的其他方法
      ...
    } 
    ...
  }
//返回接口實例對象
public INetApiService (InvocationHandler invocationHandler){
  super(invocationHandler);
}
//
public final Call getBizInfo(String str){
  ...
  try{//用Handler去調用
    return (Call)this.h.invoke(this, m3, new Object[]{str});
  }
}

}

我們可以看到,代理類生成的是一個INetApiService接口的實例對象,該對象的getBizInfo函數返回的是接口中定義的Call網絡工作對象,這也體現了Retrofit的核心價值,生成接口定義的Call網絡工作對象。

那麼,這個Call網絡工作對象是如何生成的呢,上面動態代理生成的代碼是這樣的:

 return (Call)this.h.invoke(this, m3, new Object[]{str});

也就是說,這個Call網絡工作對象是在InvocationHandler中實現的,也就是在Retrofit.create函數中,由InvocationHandler實現的。

這樣我們就明白了,Retrofit使用動態代理,其實是爲了開發者在寫代碼時方便調用,而真正負責生產Call網絡工作對象的,還是Retrofit.create函數中定義的這個InvocationHandler,這個InvocationHandler的代碼我們再貼一遍:

        new InvocationHandler() {
            ...
            ServiceMethod<Object, Object> serviceMethod =
                (ServiceMethod<Object, Object>) loadServiceMethod(method);
            OkHttpCall<Object> okHttpCall = new OkHttpCall<>(serviceMethod, args);
            return serviceMethod.callAdapter.adapt(okHttpCall);
          }

ServiceMethod能讓我們準確解析到INetApiService中定義的函數,爲最後的適配轉換提供轉換目標,詳細分析我們後面再說,先看適配轉換的過程。

我們看到,Retrofit內部默認使用OkHttpCall對象去處理網絡請求,但是返回的網絡工作對象是經過適配器轉換的,轉換成接口定義的那種Call網絡工作對象。

這個適配轉換,就是Retrofit能按照接口去定製Call網絡工作對象的祕密。

Retrofit實現原理——適配轉換Call對象

我們在初始化Retrofit對象時,好像不添加CallAdapterFactory也能實現適配轉換。

//retrofit對象
Retrofit retrofit=new Retrofit.Builder()
.baseUrl(Config.DOMAIN)
.addConverterFactory(GsonConverterFactory.create())
//可以不添加CallAdapterFactory
.build();

這是怎麼回事呢,我們知道Retrofit使用了建造者模式,建造者模式的特定就是實現了建造和使用的分離,所以建造者模式的建造函數裏,一般會有很複雜的對象創建和初始化過程,所以我們要看一下Retrofit的build函數。

public Retrofit build() {
      ...
      okhttp3.Call.Factory callFactory = this.callFactory;
      if (callFactory == null) {
        callFactory = new OkHttpClient();//使用OkHttpClient處理網絡請求
      }
      ...
      //根據當前運行平臺,設置默認的callAdapterFactory
      adapterFactories.add(platform.defaultCallAdapterFactory(callbackExecutor));
      ...
      return new Retrofit(callFactory, baseUrl, converterFactories, adapterFactories,
          callbackExecutor, validateEagerly);
    }

這段代碼裏,我們看到Retrofit使用OkHttpClient處理網絡請求,並且會添加默認的callAdapterFactory,這個platform是一個簡單工廠,能根據當前系統平臺去生成對應的callAdapterFactory

  private static Platform findPlatform() {
    try {
      Class.forName("android.os.Build");
      if (Build.VERSION.SDK_INT != 0) {
        return new Android();//根據當前系統平臺返回相應的對象
      }
    ...
  }
  ...
  static class Android extends Platform {
    ...
    @Override CallAdapter.Factory defaultCallAdapterFactory(@Nullable Executor callbackExecutor) {
      if (callbackExecutor == null) throw new AssertionError();
      return new ExecutorCallAdapterFactory(callbackExecutor);
    }
    ...
  }

這個Platform是Retrofit在Builder的構造函數裏初始化的。

所以,在Retrofit.build()函數中,我們爲Retrofit默認添加的callAdapterFactory,是在Platform中爲Android系統設定的ExecutorCallAdapterFactory。
我們看ExecutorCallAdapterFactory的代碼,這是一個工廠類,可以返回CallAdapter對象:

  @Override
  public CallAdapter<?, ?> get(Type returnType, Annotation[] annotations, Retrofit retrofit) {
    ...
    return new CallAdapter<Object, Call<?>>() {
      ...
      //               轉換後              轉換前,也就是OkHttpCall
      @Override public Call<Object> adapt(Call<Object> call) {
        return new ExecutorCallbackCall<>(callbackExecutor, call);
      }
    };
  }

在adapt函數中,適配器會把Retrofit中用來訪問網絡的OkHttpCall,轉換爲一個ExecutorCallbackCall(繼承了INetApiService接口裏要求返回的網絡工作對象retrofit2.Call),
這個例子裏面,由於OkHttpCall和ExecutorCallbackCall都實現了retrofit2.Call接口,結果出現了從Call<Object>轉換爲Call<Object>的情況,這可能不容易理解,我們換個RxJava2CallAdapterFactory來看看

  //RxJava2CallAdapterFactory中
  @Override
  public CallAdapter<?, ?> get(Type returnType, Annotation[] annotations, Retrofit retrofit) {
    ...
    return new RxJava2CallAdapter(responseType, scheduler, isAsync, isResult, isBody, isFlowable,
        isSingle, isMaybe, false);
}
  //RxJava2CallAdapter中
  //               轉換後        轉換前,也就是OkHttpCall
  @Override public Object adapt(Call<R> call) {
   ...
   Observable<?> observable;
   ...
   return observable;
  }

這個CallAdapter的轉換就比較明顯了,把retrofit2.Call對象通過適配器轉換爲了一個實爲Observable<?>的Object對象。

至此,我們可以理解Retrofit根據接口定義動態生產Call網絡請求工作對象的原理了,其實就是通過適配器把retrofit2.Call對象轉換爲目標對象。

至於適配器轉換過程中,如何實現的對象轉換,就可以根據需求來自由實現了,比如利用靜態代理等,如有必要,我們可以自行開發擴展,Retrofit框架並不限制我們對於適配器的實現方式。

Retrofit實現原理——函數解析、網絡請求和數據轉換

在前面分析中,我們知道了Retrofit的整體工作流程,就是Retrofit用動態代理生成Call網絡請求對象,在這個過程中,用適配器把Retrofit底層的retrofit2.Call對象轉換爲INetApiService中定義的Call網絡請求對象(如Flowable)。

問題是,Retrofit具體是如何知道了INetApiService中定義的Call網絡請求對象,如何實現網絡請求,以及如何執行的數據轉換呢?

具體過程如下;
首先,根據INetApiService中定義的函數,解析函數,得到函數的具體定義,並生成對應的ServiceMethod。
然後,根據這個ServiceMethod,實現一個OkHttpCall的Call對象,負責在Retrofit底層實現網絡訪問。
其中,在網絡訪問返回了網絡數據時,根據ServiceMethod實現數據轉換。
最後,利用上一小節中匹配的適配器,把OkHttpCall對象轉換爲INetApiService要求的Call網絡請求對象。

所以,我們要了解的就是函數解析、網絡請求和數據轉換這三個動作,至於最後的適配轉換,在上一節中已經分析過了。

1. 函數解析
在接口函數裏,用註解描述了輸入參數,用Java對象定義了返回值類型,所以對輸入參數和返回值,ServiceMethod採取了不同的方式去處理。
輸入參數
輸入參數是用來描述url的,它的處理相對簡單,ServiceMethod會根據反射得到的Method,取得Annotation註解信息,這些註解是Retrofit自己預定義好的(retrofit2.http.*),ServiceMethod根據預先的定義,直接判斷註解所屬的邏輯分支,在有網絡請求時分情況進行處理,就能得到目標url,http請求頭等數據。
返回值
返回值是需要用CallAdapter去適配的,所以核心在於生成對應的CallAdapter。
在Retrofit生成Call網絡工作對象時,她通過動態代理獲取到了接口函數的Method定義,從這個Method中可以獲取函數定義的返回對象類型,由於這個轉換是需要CallAdapterFactory生產CallAdapter對象去實現,而Retrofit事先並不知道要使用哪個Factory,所以她是遍歷所有的CallAdapterFactory,根據目標函數的返回值類型,讓每個Factory都去嘗試生產一個CallAdapter,哪個成功就用哪個。

2. 網絡請求
OkHttpCall繼承的retrofit2.Call接口是爲了依賴倒置解耦的,真正的網絡請求是由OkHttpCall內部引用的okhttp3.call處理的,這個okhttp3.call是
借道ServiceMethod獲取的Retrofit中的callFactory,也就是Retrofit中的OkHttpClient。

整個引用鏈條是這樣的:
OkHttpCall--okhttp3.call
-->
ServiceMethod--callFactory
-->
Retrofit.build()--callFactory//(如未擴展賦值)new OkHttpClient();
-->
Retrofit.Builder().client(mClient)//(可能有擴展賦值)擴展過的OkHttpClient

最終的網絡請求是由OkHttpCall調用OkHttpClient發出的,調用和回調等過程,也就是在OkHttpCall中處理的。

網絡請求的生成過程中,爲了使用接口函數中定義的參數,OkHttpCall會調用ServiceMethod來生成Request請求對象,再交給OkHttpCall去處理。

3. 數據轉換
因爲回調是在OkHttpCall中處理的,所以對回調數據的轉換也在OkHttpCall中觸發,爲了符合接口函數中定義的返回數據類型,OkHttpCall會調用ServiceMethod來轉換Response返回數據對象。

OkHttpCall對返回的網絡數據,會調用一個serviceMethod.toResponse(ResponseBody body)函數,函數中執行的是:

  R toResponse(ResponseBody body) throws IOException {
    return responseConverter.convert(body);
  }

這個函數可以把原始的okhttp3. ResponseBody數據轉換爲INetApiService接口中要求的數據類型(如BizEntity類型)。
從代碼可以看出,實現數據轉換的核心對象其實是responseConverter,這個Converter實際上要依次經過Retrofit的建造和ServiceMethod的建造後,才能確定下來的。

Retrofit建造時添加數據轉換工廠
Retrofit裏有converterFactries列表,這是在我們初始化Retrofit實例時添加的

//retrofit對象
Retrofit retrofit=new Retrofit.Builder()
.baseUrl(Config.DOMAIN)
.addConverterFactory(GsonConverterFactory.create())
.addConverterFactory(YourConverterFactory.create())//添加自定義Converter
.build();

ServiceMethod建造時設定數據轉換器
ServiceMethod在建造時,就已經確定了對應的是INetApiService中的哪個函數,所以需要明確設定自己的Converter<R,T>轉換對象

  public ServiceMethod build() {
      ...
      responseConverter = createResponseConverter();
      ...
  }

這需要調用Retrofit

    private Converter<ResponseBody, T> createResponseConverter() {
      ...
      retrofit.responseBodyConverter(responseType, annotations);
    }

Retrofit會在自己的轉換器工廠列表中遍歷每個ConverterFactory,嘗試根據ServiceMethod所對應的目標數據類型,找到Converter數據轉換類

    for (int i = start, count = converterFactories.size(); i < count; i++) {
      Converter<ResponseBody, ?> converter =
          converterFactories.get(i).responseBodyConverter(type, annotations, this);
      if (converter != null) {
        //noinspection unchecked
        return (Converter<ResponseBody, T>) converter;
      }
    }

以Gson轉換爲例,GsonConverterFactory會通過getAdapter來嘗試匹配目標數據類型:

public <T> TypeAdapter<T> getAdapter(TypeToken<T> type) {...}

如果可以匹配,那麼前面調用serviceMethod.toResponse(ResponseBody body)函數時,會調用

  R toResponse(ResponseBody body) throws IOException {
    return responseConverter.convert(body);
  }

在調用這段代碼時,其實就是調用了Gson中最終執行數據轉換的代碼:

  @Override public T convert(ResponseBody value) throws IOException {
    JsonReader jsonReader = gson.newJsonReader(value.charStream());
    try {
      return adapter.read(jsonReader);
    } finally {
      value.close();
    }
  }

總結來說,Retrofit在類的單一職責方面分隔的很好,OkHttpCall類只負責網絡交互,凡是需要知道函數定義的,都交給ServiceMethod類去處理,而ServiceMethod類對使用者不公開,因爲Retrofit是個外觀模式,而所有需要擴展的都在Retrofit的建造者中實現,他們的分工大概是這樣的:

 

三個類的分工

 

這三個類分工合作,共同實現了函數解析、網絡訪問和數據轉換,並保留了良好的可擴展性。

Retrofit實現原理——整體結構與分工實現

至此,Retrofit的實現細節就已經基本清楚了,他用動態代理去定製接口定義的Call網絡工作對象,用適配器去把底層的Call對象轉換爲目標Call對象,用函數解析/OkHttpClient/數據轉換等實現對Call對象的適配轉換,並能處理真正的網絡請求。
這裏面涉及的整體結構和角色分工,大概可以這樣表示:

 

整體結構與角色分工

 

其中,擴展適配器、擴展數據轉換和擴展OkHttpClient,雖然都是通過Retrofit實現擴展,但真正的使用者是Retrofit內部的ServiceMethod、OkHttpCall和okhttp3.call等類或對象。

反推Retrofit的設計過程

如果我們不直接正面分析Retrofit的結構設計和技術細節,而是先從Retrofit的功能和作用入手,倒過來推測Retrofit的目標,進而分析其架構和搭建細節,Retrofit爲什麼會設計成這樣就很好理解了。

Retrofit的功能是按照接口定義,自動定製Call網絡工作對象,所以Retrofit的目標應該就是避免爲網絡訪問開發大量的配套代碼。

爲了實現這一目標,Retrofit需要分析哪些是易變的,哪些是不變的,然後分別處理。

由於Retrofit提供網絡訪問的工作對象,又是服務於具體業務,所以可以分網絡訪問和具體業務兩部分來分析。

網絡訪問的不變性
對於網絡訪問來說,不變的是一定有一個實現網絡訪問的對象,Retrofit選用了自家的OkHttpClient,不過爲了把Retrofit和OkHttp兩個項目解耦合,Retrofit根據依賴倒置原則,定義了Retrofit自己的Call即retrofit2.call,並定義了操作網絡請求的OkHttpCall

網絡訪問的易變性
對於網絡訪問來說,易變的是網絡訪問的url、請求方式(get/post等)、Http請求的Header設置與安全設置等,以及返回的數據類型。

針對易變的url和請求方式,Retrofit使用了方法註解的方式,可讀性良好,擴展性優異,但這需要實現對接口函數中註解的解析,這樣就有了ServiceMethod
針對Http請求的各種設置,其實Retrofit沒做什麼,因爲Retrofit使用的OkHttp有攔截器機制,可以應付這種變化。
針對返回的數據類型,由於目標數據類型與業務有關,是不確定的,Retrofit無法提供一個萬能的轉換類,所以Retrofit提供了擴展接口,允許開發者自己定義ConverterFactory和Converter,去實現潛在的數據類型轉換。

具體業務的不變性
對於具體業務來說,不變的是一定要有一個Call網絡工作對象,所以Retrofit可以有一個生產對象的機制(像工廠一樣)

具體業務的易變性
對於具體業務來說,易變的就是這個Call網絡工作對象的類型,不僅有CallBacl回調、可能還有Flowable工作流、或者其他潛在的對象類型。

針對這種Call對象的易變性,Retrofit也是無法提供一個萬能的實現類,所以也是提供了擴展解耦,允許開發者自己定義CallAdapterFactory和CallAdapter,去實現潛在的Call類型轉換。

因爲這種Call對象的生產需要有大量的配套代碼,爲了簡化代碼,Retrofit使用動態代理來生產這個對象。

最後,因爲需要處理的方法和對象太多太複雜,需要使用建造者模式來把建造過程和使用過程分離開。

這樣倒着走一遍之後,我們再看Retrofit的設計和實現原理,就會覺得水到渠成,對於Retrofit精妙的設計更會有一種切身體會。

借鑑與啓示

在上文的反推過程中,我們可窺見(瞎猜)Jake大神的一些思路:

  1. 萬物皆對象
    網絡訪問後,回調數據是個對象;網絡訪問本身也是個對象。
  2. 依賴倒置
    哪怕是使用自家的OkHttp,哪怕底層調用的始終是OkHttpClient,也需要依賴一個抽象的retrofit2.Call接口,依賴於抽象,而不是依賴於具體。
  3. 單一職責
    類的職責需要維持單一,流程需要但是超出自己職責的功能,去調用相關的類實現,比如OkHttpClient和ServiceMethod的各自職責與調用關係。
  4. 迪米特法則
    內部實現再複雜,對於外部調用者也只展示他需要的那些功能,例如Retrofit。
  5. 自動>人工
    動態代理的使用,可以用自動生成的模板代碼,減輕人工編寫配套代碼的工作量,成本更低,風險更低。
  6. 利用工廠類開放擴展
    對於流程確定,但方法不能確定的,利用工廠類,對調用者開放擴展能力。
  7. 利用多個工廠類組成擴展列表
    如果1個工廠類不能實現兼得,何不設置一個工廠類列表,在多個工廠類中,看哪個工廠類能解決問題。
  8. 利用建造者模式把建造和使用分離
    這樣使用者不需要關係複雜的建造過程,例如Retrofit和ServiceMethod。
  9. 利用外觀模式減少對複雜子系統的操作
    雖然有複雜的子系統協同工作,調用者只需要調用最外層的Retrofit即可。
  10. 其他
    開放封閉、接口隔離、裏式替換、靜態代理等設計原則或設計模式都有體現也都很熟悉了,就不再囉嗦。

最後感嘆一下。

對於網絡訪問的抽象與優化,實際上是個非常難的課題,在Retrofit之前,大家努力的方向基本上都是Volley/OkHttp這種圍繞底層網絡訪問的工作。
因爲越底層的東西越容易抽象,越上升到接近業務層,就越容易在紛擾的業務層中迷失。
Retrofit能精準地抓到Call網絡工作對象這個關鍵點,並能通過一系列精巧的設計實現對這種類型“飄忽不定”的對象的自動化定製生產,着實令人讚歎。

參考

Retrofit
你真的會用Retrofit2嗎?Retrofit2完全教程
Retrofit2 源碼解析
Retrofit 框架源碼學習
拆輪子系列:拆 Retrofit
Android 動態代理以及利用動態代理實現 ServiceHook

作者:藍灰_q
鏈接:https://www.jianshu.com/p/f57b7cdb1c99
來源:簡書
簡書著作權歸作者所有,任何形式的轉載都請聯繫作者獲得授權並註明出處。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章