從架構(gòu)角度看Retrofit的作用、原理和啟示

Retrofit是squareup公司的開源力作,和同屬squareup公司開源的OkHttp,一個負責(zé)網(wǎng)絡(luò)調(diào)度,一個負責(zé)網(wǎng)絡(luò)執(zhí)行,為Android開發(fā)者提供了即方便又高效的網(wǎng)絡(luò)訪問框架。

不過,對于Retrofit這樣設(shè)計精妙、代碼簡潔、使用方便的優(yōu)秀開源項目,不能僅知道如何擴展和使用,或者僅研究它采用的技術(shù)或模式,“技”當(dāng)然重要,但不能忽視了背后的“道”。

對于Retrofit,我們還應(yīng)該看到的,是她在優(yōu)化App架構(gòu)方面的努力,以及她在提升開發(fā)效率方面的借鑒和啟示。

本文試圖通過一個具體場景,先總結(jié)Retrofit在架構(gòu)中起到的作用,再分析其實現(xiàn)原理,最后探討Retrofit給我們帶來的啟示。

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

基本場景

通常來說,使用Retrofit要經(jīng)過這樣幾個步驟

  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轉(zhuǎn)換,rxjava適配等,可以視自己需要繼續(xù)添加引用

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

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

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

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

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

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

//調(diào)用接口函數(shù),獲得網(wǎng)絡(luò)工作對象
Call<BizEntity> callWorker= netApiService.getBizInfo("id001");

這個復(fù)雜的過程下來,最終得到的callWorker對象,才可以執(zhí)行網(wǎng)絡(luò)訪問。

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

在回調(diào)函數(shù)里,取得我們需要的BizEntity數(shù)據(jù)對象。
網(wǎng)絡(luò)訪問結(jié)束。

角色與作用

我們從上面的應(yīng)用場景可以看出,Retrofit并不做網(wǎng)絡(luò)請求,只是生成一個能做網(wǎng)絡(luò)請求的對象。
Retrofit的作用是按照接口去定制Call網(wǎng)絡(luò)工作對象

什么意思?就是說:
Retrofit不直接做網(wǎng)絡(luò)請求
Retrofit不直接做網(wǎng)絡(luò)請求
Retrofit不直接做網(wǎng)絡(luò)請求

重要的事情說三遍。

網(wǎng)絡(luò)請求的目標(biāo)雖然是數(shù)據(jù),但是我們需要為這個數(shù)據(jù)寫大量的配套代碼,發(fā)起請求的對象Call,接收數(shù)據(jù)的對象CallBack,做數(shù)據(jù)轉(zhuǎn)換的對象Converter,以及檢查和處理異常的對象等。
這對于一個項目的開發(fā)、擴展和維護來說,都是成本和風(fēng)險。

而Retrofit做的事情,就是為開發(fā)者節(jié)省這部分的工作量,Retrofit一方面從底層統(tǒng)一用OkHttp去做網(wǎng)絡(luò)處理;另一方面在外層靈活提供能直接融入業(yè)務(wù)邏輯的Call網(wǎng)絡(luò)訪問對象。

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


Retrofit的作用

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

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

這個接口并不是傳統(tǒng)意義上的網(wǎng)絡(luò)請求接口,這個接口不是用來獲取數(shù)據(jù)的接口,而是用來生產(chǎn)對象的接口,這個接口相當(dāng)于一個工廠,接口中每個函數(shù)的返回值不是網(wǎng)絡(luò)數(shù)據(jù),而是一個能進行網(wǎng)絡(luò)請求的工作對象,我們要先調(diào)用函數(shù)獲得工作對象,再用這個工作對象去請求網(wǎng)絡(luò)數(shù)據(jù)。

所以Retrofit的實用價值意義在于,他能根據(jù)你的接口定義,靈活地生成對應(yīng)的網(wǎng)絡(luò)工作對象,然后你再擇機去調(diào)用這個對象訪問網(wǎng)絡(luò)。
理解了這一點,我們才能去擴展Retrofit,并理解Retrofit的設(shè)計思想。

功能擴展

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

  1. OkHttpClient
    Retrofit使用OkHttpClient來實現(xiàn)網(wǎng)絡(luò)請求,這個OkHttpClient雖然不能替換為其他的網(wǎng)絡(luò)執(zhí)行框架比如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

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

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

擴展的是對網(wǎng)絡(luò)工作對象callWorker的自動轉(zhuǎn)換,把Retrofit中執(zhí)行網(wǎng)絡(luò)請求的Call對象,轉(zhuǎn)換為接口中定義的Call對象。
這個轉(zhuǎn)換不太好理解,我們可以對照下圖來理解:


callAdapter轉(zhuǎn)換Call對象

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

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

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

那么我們只需要為Retrofit添加對應(yīng)的擴展

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

就能得到Flowable類型的callWorker對象

//用retrofit加工出對應(yīng)的接口實例對象
INetApiService netApiService= retrofit.create(INetApiService.class);
···
//調(diào)用接口函數(shù),獲得網(wǎng)絡(luò)工作對象
Flowable<BizEntity> callWorker= netApiService.getBizInfo("id001");

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

Retrofit實現(xiàn)原理

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

Retrofit實現(xiàn)原理——從動態(tài)代理開始

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

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

到Retrofit源碼里看create函數(shù),是一個動態(tài)代理。

 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);
          }
        });
  }

要理解動態(tài)代理,最好要看到動態(tài)生成的代理類。

由于動態(tài)代理是在運行時動態(tài)生成的代理類,用常規(guī)的反編譯方法無法查看,一般要使用Java提供的sun.misc.ProxyGenerator.generateProxyClass(String proxyName,class[] interfaces)函數(shù)生成代理類,函數(shù)會返回byte[]字節(jié)碼,然后對字節(jié)碼反編譯得到Java代碼。
有一個小問題是,AndroidStudio并不提供sun.misc這個包,我們需要用IntelliJ或者Eclipse建立一個Java工程,在Java環(huán)境里調(diào)用這個函數(shù)。

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

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",//反射函數(shù)
              new Class[] { Class.forName("java.lang.String") });//反射參數(shù)
      //接口中定義的其他方法
      ...
    } 
    ...
  }
//返回接口實例對象
public INetApiService (InvocationHandler invocationHandler){
  super(invocationHandler);
}
//
public final Call getBizInfo(String str){
  ...
  try{//用Handler去調(diào)用
    return (Call)this.h.invoke(this, m3, new Object[]{str});
  }
}

}

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

那么,這個Call網(wǎng)絡(luò)工作對象是如何生成的呢,上面動態(tài)代理生成的代碼是這樣的:

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

也就是說,這個Call網(wǎng)絡(luò)工作對象是在InvocationHandler中實現(xiàn)的,也就是在Retrofit.create函數(shù)中,由InvocationHandler實現(xiàn)的。

這樣我們就明白了,Retrofit使用動態(tài)代理,其實是為了開發(fā)者在寫代碼時方便調(diào)用,而真正負責(zé)生產(chǎn)Call網(wǎng)絡(luò)工作對象的,還是Retrofit.create函數(shù)中定義的這個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中定義的函數(shù),為最后的適配轉(zhuǎn)換提供轉(zhuǎn)換目標(biāo),詳細分析我們后面再說,先看適配轉(zhuǎn)換的過程。

我們看到,Retrofit內(nèi)部默認使用OkHttpCall對象去處理網(wǎng)絡(luò)請求,但是返回的網(wǎng)絡(luò)工作對象是經(jīng)過適配器轉(zhuǎn)換的,轉(zhuǎn)換成接口定義的那種Call網(wǎng)絡(luò)工作對象。

這個適配轉(zhuǎn)換,就是Retrofit能按照接口去定制Call網(wǎng)絡(luò)工作對象的秘密。

Retrofit實現(xiàn)原理——適配轉(zhuǎn)換Call對象

我們在初始化Retrofit對象時,好像不添加CallAdapterFactory也能實現(xiàn)適配轉(zhuǎn)換。

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

這是怎么回事呢,我們知道Retrofit使用了建造者模式,建造者模式的特定就是實現(xiàn)了建造和使用的分離,所以建造者模式的建造函數(shù)里,一般會有很復(fù)雜的對象創(chuàng)建和初始化過程,所以我們要看一下Retrofit的build函數(shù)。

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

這段代碼里,我們看到Retrofit使用OkHttpClient處理網(wǎng)絡(luò)請求,并且會添加默認的callAdapterFactory,這個platform是一個簡單工廠,能根據(jù)當(dāng)前系統(tǒng)平臺去生成對應(yīng)的callAdapterFactory

  private static Platform findPlatform() {
    try {
      Class.forName("android.os.Build");
      if (Build.VERSION.SDK_INT != 0) {
        return new Android();//根據(jù)當(dāng)前系統(tǒng)平臺返回相應(yīng)的對象
      }
    ...
  }
  ...
  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的構(gòu)造函數(shù)里初始化的。

所以,在Retrofit.build()函數(shù)中,我們?yōu)镽etrofit默認添加的callAdapterFactory,是在Platform中為Android系統(tǒng)設(shè)定的ExecutorCallAdapterFactory。
我們看ExecutorCallAdapterFactory的代碼,這是一個工廠類,可以返回CallAdapter對象:

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

在adapt函數(shù)中,適配器會把Retrofit中用來訪問網(wǎng)絡(luò)的OkHttpCall,轉(zhuǎn)換為一個ExecutorCallbackCall(繼承了INetApiService接口里要求返回的網(wǎng)絡(luò)工作對象retrofit2.Call),
這個例子里面,由于OkHttpCall和ExecutorCallbackCall都實現(xiàn)了retrofit2.Call接口,結(jié)果出現(xiàn)了從Call<Object>轉(zhuǎn)換為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中
  //               轉(zhuǎn)換后        轉(zhuǎn)換前,也就是OkHttpCall
  @Override public Object adapt(Call<R> call) {
   ...
   Observable<?> observable;
   ...
   return observable;
  }

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

至此,我們可以理解Retrofit根據(jù)接口定義動態(tài)生產(chǎn)Call網(wǎng)絡(luò)請求工作對象的原理了,其實就是通過適配器把retrofit2.Call對象轉(zhuǎn)換為目標(biāo)對象。

至于適配器轉(zhuǎn)換過程中,如何實現(xiàn)的對象轉(zhuǎn)換,就可以根據(jù)需求來自由實現(xiàn)了,比如利用靜態(tài)代理等,如有必要,我們可以自行開發(fā)擴展,Retrofit框架并不限制我們對于適配器的實現(xiàn)方式。

Retrofit實現(xiàn)原理——函數(shù)解析、網(wǎng)絡(luò)請求和數(shù)據(jù)轉(zhuǎn)換

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

問題是,Retrofit具體是如何知道了INetApiService中定義的Call網(wǎng)絡(luò)請求對象,如何實現(xiàn)網(wǎng)絡(luò)請求,以及如何執(zhí)行的數(shù)據(jù)轉(zhuǎn)換呢?

具體過程如下;
首先,根據(jù)INetApiService中定義的函數(shù),解析函數(shù),得到函數(shù)的具體定義,并生成對應(yīng)的ServiceMethod。
然后,根據(jù)這個ServiceMethod,實現(xiàn)一個OkHttpCall的Call對象,負責(zé)在Retrofit底層實現(xiàn)網(wǎng)絡(luò)訪問。
其中,在網(wǎng)絡(luò)訪問返回了網(wǎng)絡(luò)數(shù)據(jù)時,根據(jù)ServiceMethod實現(xiàn)數(shù)據(jù)轉(zhuǎn)換。
最后,利用上一小節(jié)中匹配的適配器,把OkHttpCall對象轉(zhuǎn)換為INetApiService要求的Call網(wǎng)絡(luò)請求對象。

所以,我們要了解的就是函數(shù)解析、網(wǎng)絡(luò)請求和數(shù)據(jù)轉(zhuǎn)換這三個動作,至于最后的適配轉(zhuǎn)換,在上一節(jié)中已經(jīng)分析過了。

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

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

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

最終的網(wǎng)絡(luò)請求是由OkHttpCall調(diào)用OkHttpClient發(fā)出的,調(diào)用和回調(diào)等過程,也就是在OkHttpCall中處理的。

網(wǎng)絡(luò)請求的生成過程中,為了使用接口函數(shù)中定義的參數(shù),OkHttpCall會調(diào)用ServiceMethod來生成Request請求對象,再交給OkHttpCall去處理。

3. 數(shù)據(jù)轉(zhuǎn)換
因為回調(diào)是在OkHttpCall中處理的,所以對回調(diào)數(shù)據(jù)的轉(zhuǎn)換也在OkHttpCall中觸發(fā),為了符合接口函數(shù)中定義的返回數(shù)據(jù)類型,OkHttpCall會調(diào)用ServiceMethod來轉(zhuǎn)換Response返回數(shù)據(jù)對象。

OkHttpCall對返回的網(wǎng)絡(luò)數(shù)據(jù),會調(diào)用一個serviceMethod.toResponse(ResponseBody body)函數(shù),函數(shù)中執(zhí)行的是:

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

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

Retrofit建造時添加數(shù)據(jù)轉(zhuǎn)換工廠
Retrofit里有converterFactries列表,這是在我們初始化Retrofit實例時添加的

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

ServiceMethod建造時設(shè)定數(shù)據(jù)轉(zhuǎn)換器
ServiceMethod在建造時,就已經(jīng)確定了對應(yīng)的是INetApiService中的哪個函數(shù),所以需要明確設(shè)定自己的Converter<R,T>轉(zhuǎn)換對象

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

這需要調(diào)用Retrofit

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

Retrofit會在自己的轉(zhuǎn)換器工廠列表中遍歷每個ConverterFactory,嘗試根據(jù)ServiceMethod所對應(yīng)的目標(biāo)數(shù)據(jù)類型,找到Converter數(shù)據(jù)轉(zhuǎn)換類

    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轉(zhuǎn)換為例,GsonConverterFactory會通過getAdapter來嘗試匹配目標(biāo)數(shù)據(jù)類型:

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

如果可以匹配,那么前面調(diào)用serviceMethod.toResponse(ResponseBody body)函數(shù)時,會調(diào)用

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

在調(diào)用這段代碼時,其實就是調(diào)用了Gson中最終執(zhí)行數(shù)據(jù)轉(zhuǎn)換的代碼:

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

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


三個類的分工

這三個類分工合作,共同實現(xiàn)了函數(shù)解析、網(wǎng)絡(luò)訪問和數(shù)據(jù)轉(zhuǎn)換,并保留了良好的可擴展性。

Retrofit實現(xiàn)原理——整體結(jié)構(gòu)與分工實現(xiàn)

至此,Retrofit的實現(xiàn)細節(jié)就已經(jīng)基本清楚了,他用動態(tài)代理去定制接口定義的Call網(wǎng)絡(luò)工作對象,用適配器去把底層的Call對象轉(zhuǎn)換為目標(biāo)Call對象,用函數(shù)解析/OkHttpClient/數(shù)據(jù)轉(zhuǎn)換等實現(xiàn)對Call對象的適配轉(zhuǎn)換,并能處理真正的網(wǎng)絡(luò)請求。
這里面涉及的整體結(jié)構(gòu)和角色分工,大概可以這樣表示:


整體結(jié)構(gòu)與角色分工

其中,擴展適配器、擴展數(shù)據(jù)轉(zhuǎn)換和擴展OkHttpClient,雖然都是通過Retrofit實現(xiàn)擴展,但真正的使用者是Retrofit內(nèi)部的ServiceMethod、OkHttpCall和okhttp3.call等類或?qū)ο蟆?/p>

反推Retrofit的設(shè)計過程

如果我們不直接正面分析Retrofit的結(jié)構(gòu)設(shè)計和技術(shù)細節(jié),而是先從Retrofit的功能和作用入手,倒過來推測Retrofit的目標(biāo),進而分析其架構(gòu)和搭建細節(jié),Retrofit為什么會設(shè)計成這樣就很好理解了。

Retrofit的功能是按照接口定義,自動定制Call網(wǎng)絡(luò)工作對象,所以Retrofit的目標(biāo)應(yīng)該就是避免為網(wǎng)絡(luò)訪問開發(fā)大量的配套代碼。

為了實現(xiàn)這一目標(biāo),Retrofit需要分析哪些是易變的,哪些是不變的,然后分別處理。

由于Retrofit提供網(wǎng)絡(luò)訪問的工作對象,又是服務(wù)于具體業(yè)務(wù),所以可以分網(wǎng)絡(luò)訪問和具體業(yè)務(wù)兩部分來分析。

網(wǎng)絡(luò)訪問的不變性
對于網(wǎng)絡(luò)訪問來說,不變的是一定有一個實現(xiàn)網(wǎng)絡(luò)訪問的對象,Retrofit選用了自家的OkHttpClient,不過為了把Retrofit和OkHttp兩個項目解耦合,Retrofit根據(jù)依賴倒置原則,定義了Retrofit自己的Call即retrofit2.call,并定義了操作網(wǎng)絡(luò)請求的OkHttpCall。

網(wǎng)絡(luò)訪問的易變性
對于網(wǎng)絡(luò)訪問來說,易變的是網(wǎng)絡(luò)訪問的url、請求方式(get/post等)、Http請求的Header設(shè)置與安全設(shè)置等,以及返回的數(shù)據(jù)類型。

針對易變的url和請求方式,Retrofit使用了方法注解的方式,可讀性良好,擴展性優(yōu)異,但這需要實現(xiàn)對接口函數(shù)中注解的解析,這樣就有了ServiceMethod
針對Http請求的各種設(shè)置,其實Retrofit沒做什么,因為Retrofit使用的OkHttp有攔截器機制,可以應(yīng)付這種變化。
針對返回的數(shù)據(jù)類型,由于目標(biāo)數(shù)據(jù)類型與業(yè)務(wù)有關(guān),是不確定的,Retrofit無法提供一個萬能的轉(zhuǎn)換類,所以Retrofit提供了擴展接口,允許開發(fā)者自己定義ConverterFactory和Converter,去實現(xiàn)潛在的數(shù)據(jù)類型轉(zhuǎn)換。

具體業(yè)務(wù)的不變性
對于具體業(yè)務(wù)來說,不變的是一定要有一個Call網(wǎng)絡(luò)工作對象,所以Retrofit可以有一個生產(chǎn)對象的機制(像工廠一樣)

具體業(yè)務(wù)的易變性
對于具體業(yè)務(wù)來說,易變的就是這個Call網(wǎng)絡(luò)工作對象的類型,不僅有CallBacl回調(diào)、可能還有Flowable工作流、或者其他潛在的對象類型。

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

因為這種Call對象的生產(chǎn)需要有大量的配套代碼,為了簡化代碼,Retrofit使用動態(tài)代理來生產(chǎn)這個對象。

最后,因為需要處理的方法和對象太多太復(fù)雜,需要使用建造者模式來把建造過程和使用過程分離開。

這樣倒著走一遍之后,我們再看Retrofit的設(shè)計和實現(xiàn)原理,就會覺得水到渠成,對于Retrofit精妙的設(shè)計更會有一種切身體會。

借鑒與啟示

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

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

最后感嘆一下。

對于網(wǎng)絡(luò)訪問的抽象與優(yōu)化,實際上是個非常難的課題,在Retrofit之前,大家努力的方向基本上都是Volley/OkHttp這種圍繞底層網(wǎng)絡(luò)訪問的工作。
因為越底層的東西越容易抽象,越上升到接近業(yè)務(wù)層,就越容易在紛擾的業(yè)務(wù)層中迷失。
Retrofit能精準地抓到Call網(wǎng)絡(luò)工作對象這個關(guān)鍵點,并能通過一系列精巧的設(shè)計實現(xiàn)對這種類型“飄忽不定”的對象的自動化定制生產(chǎn),著實令人贊嘆。

參考

Retrofit
你真的會用Retrofit2嗎?Retrofit2完全教程
Retrofit2 源碼解析
Retrofit 框架源碼學(xué)習(xí)
拆輪子系列:拆 Retrofit
Android 動態(tài)代理以及利用動態(tài)代理實現(xiàn) ServiceHook

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容