Retrofit源碼解析(一)

一、什么是Retrofit

A type-safe HTTP client for Android and Java

以上是官網給出的答案,翻譯成中文:Retrofit是Android和Java上類型安全的http客戶端。

簡而言之,Retrofit是對http網絡請求框架的封裝,與Volley\AsyncHttpClient等類似,簡化了用戶的http請求流程。

它支持DELETE\GET\POST\PUT\HEADER\OPTION\PATCH等請求方式,默認使用okhttp作為網絡請求引擎。

二、Retrofit的一些重要類及接口

  • Call

該接口代表一個實際的http請求,它的主要作用有三個:

  1. 調用createRawCall創建請求;
  2. 調用okhttp發送請求;
  3. 將okhttp返回的響應解析成Retrofit對應的Response
  4. 對于異步的enqueue操作,還有執行相應的onResponse和onFailure回調操作

拋開mock測試相關,該類的實現類主要有:OkHttpCall\ExecutorCallbackCall。


  • CallAdapter

顧名思義,該接口的主要作用是適配,將一個帶有響應類型為R的Call請求適配成需要的類型T。所以該接口的定義是CallAdapter<R,T>。其核心方法T adapt(Call<R> call)的作用就在于此。

該類內部還封裝了一個抽象工廠類Factory。通過Retrofit.create會產生一個接口對象,該接口對象內部的方法的返回值都具有返回類型。Factory正是用于創建基于該返回類型而生的CallAdapter。

拋開mock測試相關,CallAdapter的實現類主要有:BodyCallAdapter\ResponseCallAdapter\RxJavaCallAdapter\RxJava2CallAdapter。

拋開mock測試相關,CallAdapter.Factory的實現類分布在retrofit-adapters包中,有GuavaCallAdapterFactory\Java8CallAdapterFactory\RxJavaCallAdapterFactory\RxJava2CallAdapterFactory\ScalaCallAdapterFactory。


  • Converter

Converter主要有三個方法,對應三個作用。

  1. 當請求帶有請求體時,將請求體轉換成OkHttp對應的RequestBody
  2. 將響應解析成需要的實體對象T
  3. 將Field\FieldMap\Header\HeaderMap\Path\Query\QueryMap等的值轉換成String類型

當需要自定義數據轉換機制時,可以實現該接口,并注冊到Retrofit中。目前默認的數據轉換采用Gson,也可根據需要自定義成xml\protoBuf\fastJson等。


三、根據設計模式分析源碼

首先放一張stay大神畫的Retrofit請求的流程圖


image

接下來我們將根據這張流程圖的走勢,結合遇到的設計模式,分析Retrofit的設計源碼。對于一些常見的、簡單的設計模式,本文不做分析。


  • 外觀(門面)模式

    我們知道,在使用Retrofit庫時,基本是圍繞著Retrofit這個類本身來使用的。該類內部封裝了不同的組件,如CallAdapter\ServiceMethod\Converter等。

    我們在使用Retrofit時,并沒有直接與CallAdapter\ServiceMethod\Converter等打交道,而是通過Retrofit提供的接口間接去使用這些組件。這種對外提供簡單統一的接口、隱蔽內部子系統的具體實現、將變化隔離的設計方式稱為“外觀模式”,也叫“門面模式”。

    //不同組件被封裝在Retrofit內部,外部無法直接訪問
     private final Map<Method, ServiceMethod<?, ?>> serviceMethodCache = new ConcurrentHashMap<>();
    
     final okhttp3.Call.Factory callFactory;
     final HttpUrl baseUrl;
     final List<Converter.Factory> converterFactories;
     final List<CallAdapter.Factory> adapterFactories;
     final @Nullable Executor callbackExecutor;
     final boolean validateEagerly;
    

  • 建造者模式

    在使用Retrofit前,需要先創建出Retrofit對象。Retrofit的構造方法的訪問權限是protected級別的,非繼承類無法直接通過new Retrofit的方式創建Retrofit對象,同時Retrofit類又是final類型的,因此無法被繼承。也就是說,直接通過new Retrofit來構造Retrofit對象的方式是行不通的。

    幸運的是,Retrofit內部提供了Builder建造者類,用于構建Retrofit對象,也就出現了我們使用Retrofit時的通用寫法:

    Retrofit retrofit = new Retrofit.Builder()
        .baseUrl("https://api.github.com/")
        .build();
    

    通過Builder的build方法,最終生成一個Retrofit對象。這里需要詳細分析一下構建流程,以便我們理解Builder中幾個重要對象的默認賦值,也便于更好地理解后續文章的分析。

    //無參的構造方法默認調用帶有platform參數的構造方法
    public Builder() {
      this(Platform.get());
    }
    
    Builder(Platform platform) {
      this.platform = platform;
    }
    
    

    在創建Builder對象時,最終會調用帶有platForm參數的構造方法。Platform又是什么呢?顧名思義,它代表的是平臺的意思,我們跟蹤進PlatForm源碼查看,會發現內部封裝了一個靜態私有變量PLATFORM。毫無疑問,這就是我們在Builder構造時傳入的參數了。該參數通過findPlatform靜態方法獲取。

    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) {
    }
    return new Platform();
    }
    

    當ClassLoader可以查找到android.os.Build,則代表該平臺是Android;
    當ClassLoader可以查找到java.util.Optional,則代表是Java8;
    否則直接返回Platform。

    Android和Java8都繼承自Platform。

    //Android類定義
    static class Android extends Platform {
        ...
    }
    
    //Java8類定義
    static class Java8 extends Platform {
        ...
    }
    

    顯然,我們的程序是運行在Android上的,因此該處優先返回的自然是Android對象。Android的內部結構如下,大家先熟悉一下,關注重點是ExecutorCallAdapterFactory和MainThreadExecutor。這兩個結構在接下來將起到重要作用。

    先小小地劇透一下MainThreadExecutor的作用:它是用來將異步請求的回調從子線程切換到主線程執行的!!!

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

    創建完Builder對象之后,接下來就是調用Builder的build方法來構建出具體的Retrofit對象了。

    public Retrofit build() {
    
      okhttp3.Call.Factory callFactory = this.callFactory;
      if (callFactory == null) {
        callFactory = new OkHttpClient();
      }
    
      Executor callbackExecutor = this.callbackExecutor;
      if (callbackExecutor == null) {
        callbackExecutor = platform.defaultCallbackExecutor();
      }
    
      List<CallAdapter.Factory> adapterFactories = new ArrayList<>(this.adapterFactories);
      adapterFactories.add(platform.defaultCallAdapterFactory(callbackExecutor));
    
      List<Converter.Factory> converterFactories =
          new ArrayList<>(1 + this.converterFactories.size());
    
    
      converterFactories.add(new BuiltInConverters());
      converterFactories.addAll(this.converterFactories);
    
      return new Retrofit(callFactory, baseUrl, converterFactories, adapterFactories,
          callbackExecutor, validateEagerly);
    }
    

    build方法里面主要執行了以下幾個初始化操作:

    1. 判斷callFactory是否為null,,顯然,由于使用了無參的Builder構造方法,因此callFactory==null,callFactory默認被賦值為OkHttpClient,即默認使用okhttp作為網絡請求引擎。

    2. 判斷callbackExecutor是否為null,同樣callbackExecutor==null,進入到callbackExecutor = platform.defaultCallbackExecutor(),根據上述分析,這里調用的是Android的defaultCallbackExecutor(),最終返回MainThreadExecutor對象。

    3. adapterFactories添加適配器工廠,默認添加了platform.defaultCallAdapterFactory(callbackExecutor),回溯到Android類中,發現該方法返回ExecutorCallAdapterFactory對象。

    4. converterFactories添加轉換器工廠,默認內置了BuiltInConverters,同時也添加用戶自定義的轉換器。

    執行完以上初始化操作后,最終會調用Retrofit的構造方法創建對象。


  • 動態代理

    在創建完Retrofit對象后,還記得接下來的使用姿勢嗎?

    是的,我們還需要一個接口,在這個接口里面需要預定義一些方法,這些方法使用Retrofit提供的注解聲明參數,范式如下:

    public interface GitHubService {
        @GET("users/{user}/repos")
        Call<List<Repo>> listRepos(@Path("user") String user);
    }
    

    聲明完接口之后,接下來就是調用Retrofit的create方法返回一個接口代理對象,而后就可以通過這個代理對象調用接口中的方法了,范式如下:

    GitHubService service = retrofit.create(GitHubService.class);
    
    Call<List<Repo>> repos = service.listRepos("octocat");
    

    create方法是Retrofit動態代理的核心所在。

     public <T> T create(final Class<T> service) {
     //傳入的service對象必須是接口,且該接
     //口不能繼承任何其他接口
    Utils.validateServiceInterface(service);
    
    if (validateEagerly) {
      eagerlyValidateMethods(service);
    }
    
    return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[] { service },
        new InvocationHandler() {
          private final Platform platform = Platform.get();
    
          @Override public Object invoke(Object proxy, Method method, @Nullable Object[] args)
              throws Throwable {
            // 如果方法隸屬于Object,則當做普通方法處理
            if (method.getDeclaringClass() == Object.class) {
              return method.invoke(this, args);
            }
            //如果是接口里default方法(Java8新特性),則調
            //用默認方法處理
            if (platform.isDefaultMethod(method)) {
              return platform.invokeDefaultMethod(method, service, proxy, args);
            }
            
            ServiceMethod<Object, Object> serviceMethod =
                (ServiceMethod<Object, Object>) loadServiceMethod(method);
            OkHttpCall<Object> okHttpCall = new OkHttpCall<>(serviceMethod, args);
            return serviceMethod.callAdapter.adapt(okHttpCall);
          }
        });
    }
    

    動態代理與裝飾者模式很像,都是通過攔截某些方法,在這些方法前后做一些附加操作。但動態代理又與裝飾者模式又存在區別,個人認為,主要有以下不同:
    1. 動態代理更傾向于用在當有較多的方法需要被攔截處理的情況,即批量處理的情況;
    2. 裝飾者模式傾向于用在需要攔截的方法的量較少的情況,即單個操作的情況;

    create方法里,通過Proxy的newProxyInstance構造出一個代理對象,Retrofit給該代理對象傳遞了一個InvocationHandler,之后我們所有經過代理對象調用的接口方法,都會被該InvocationHandler攔截處理。

    在InvocationHandler的invoke方法里,會讀取接口里聲明的方法參數,并生成對應的Call,之后將該Call適配成程序需要的類型返回。

    首先,invoke方法里需要先獲取ServiceMethod對象。ServiceMethod的類名很直觀地表明了它就是我們在GithubService接口里定義的方法。

    ServiceMethod<Object, Object> serviceMethod =
    (ServiceMethod<Object, Object>) loadServiceMethod(method);
    
    
    ServiceMethod<?, ?> loadServiceMethod(Method method) {
    ServiceMethod<?, ?> result = serviceMethodCache.get(method);
    if (result != null) return result;
    
    synchronized (serviceMethodCache) {
      result = serviceMethodCache.get(method);
      if (result == null) {
        result = new ServiceMethod.Builder<>(this, method).build();
        serviceMethodCache.put(method, result);
      }
    }
    return result;
    }
    

    可以看到,loadServiceMethod首先會到serviceMethodCache緩存中去取,如果可以取到ServiceMethod對象,就直接返回;否則調用ServiceMethod的Builder建造器去構造,并將構造結果緩存到serviceMethodCache中。

    接下來我們一起來看看ServiceMethod.Builder的build方法,瞧一瞧它在里面到底做了哪些事。

    public ServiceMethod build() {
      //創建callAdapter和convert對象
      callAdapter = createCallAdapter();
      responseType = callAdapter.responseType();
      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];
    
        Annotation[] parameterAnnotations = parameterAnnotationsArray[p];
    
        parameterHandlers[p] = parseParameter(p, parameterType, parameterAnnotations);
      }
    
      return new ServiceMethod<>(this);
    }
    

    以上我們省略掉了部分邏輯,只留下核心部分,便于分析。在build方法里,第一步先調用createCallAdapter和createResponseConverter創建callAdapter和responseConverter對象。

    之后遍歷接口方法的所有注解,調用parseMethodAnnotation處理每個方法級別的注解。方法級別的注解是用于方法上的,例如@GET@POST@Headers@Multipart@FormUrlEncoded等。該方法定義如下。

     private void parseMethodAnnotation(Annotation annotation) {
      if (annotation instanceof DELETE) {
        parseHttpMethodAndPath("DELETE", ((DELETE) annotation).value(), false);
      } else if (annotation instanceof GET) {
        parseHttpMethodAndPath("GET", ((GET) annotation).value(), false);
      } else if (annotation instanceof HEAD) {
        parseHttpMethodAndPath("HEAD", ((HEAD) annotation).value(), false);
        if (!Void.class.equals(responseType)) {
          throw methodError("HEAD method must use Void as response type.");
        }
      } else if (annotation instanceof PATCH) {
        parseHttpMethodAndPath("PATCH", ((PATCH) annotation).value(), true);
      } else if (annotation instanceof POST) {
        parseHttpMethodAndPath("POST", ((POST) annotation).value(), true);
      } else if (annotation instanceof PUT) {
        parseHttpMethodAndPath("PUT", ((PUT) annotation).value(), true);
      } else if (annotation instanceof OPTIONS) {
        parseHttpMethodAndPath("OPTIONS", ((OPTIONS) annotation).value(), false);
      } else if (annotation instanceof HTTP) {
        HTTP http = (HTTP) annotation;
        parseHttpMethodAndPath(http.method(), http.path(), http.hasBody());
      } else if (annotation instanceof retrofit2.http.Headers) {
        String[] headersToParse = ((retrofit2.http.Headers) annotation).value();
        if (headersToParse.length == 0) {
          throw methodError("@Headers annotation is empty.");
        }
        headers = parseHeaders(headersToParse);
      } else if (annotation instanceof Multipart) {
        if (isFormEncoded) {
          throw methodError("Only one encoding annotation is allowed.");
        }
        isMultipart = true;
      } else if (annotation instanceof FormUrlEncoded) {
        if (isMultipart) {
          throw methodError("Only one encoding annotation is allowed.");
        }
        isFormEncoded = true;
      }
    }
    

    這部分邏輯一目了然,不做贅述。

    提取完方法級別的注解,接下來就是提取參數級別的注解了。參數級別的注解就是在方法形參上添加的注解標記。

    private ParameterHandler<?> parseParameter(
        int p, Type parameterType, Annotation[] annotations) {
      ParameterHandler<?> result = null;
      for (Annotation annotation : annotations) {
        ParameterHandler<?> annotationAction = parseParameterAnnotation(
            p, parameterType, annotations, annotation);
    
        if (annotationAction == null) {
          continue;
        }
    
        result = annotationAction;
      }
    
    
      return result;
    }
    
    

    從以上代碼分析可知,parseParameter遍歷每個注解,并調用了parseParameterAnnotation去解析注解,之后返回參數處理器。parseParameterAnnotation的代碼太多,但邏輯很清晰,這里僅貼出部分示例代碼,具體詳細代碼請讀者自行翻閱源碼閱讀。

     if (annotation instanceof Url) {
        gotUrl = true;
    
        if (type == HttpUrl.class
            || type == String.class
            || type == URI.class
            || (type instanceof Class && "android.net.Uri".equals(((Class<?>) type).getName()))) {
          return new ParameterHandler.RelativeUrl();
        } 
    
    } else if (annotation instanceof Path) {
        gotPath = true;
    
        Path path = (Path) annotation;
        String name = path.value();
        validatePathName(p, name);
    
        Converter<?, String> converter = retrofit.stringConverter(type, annotations);
        return new ParameterHandler.Path<>(name, converter, path.encoded());
    }else if(...){
        ...
    }
    
    

  • 策略模式

    對于每個參數級別的注解,最終都會返回ParameterHandler.Xxx對象。ParameterHandler.Xxx是ParameterHandler的靜態內部類,這些內部類都是繼承自ParameterHandler,對應具體的參數注解類型。ParameterHandler.Xxx的繼承采用了策略模式,每個ParameterHandler的子類都實現了各自的apply方法。這部分代碼也請讀者自行翻閱,不難理解。

    策略模式與模板方法模式非常類似,導致很多讀者可能對它們的概念有些混淆。這里簡單說一下個人對這兩種設計模式的區別的理解。

    這兩種模式都是在父類定義一個抽象的或者空的算法接口,而具體的算法實現則延遲到子類實現。區別就在于,模板方法模式更多地是傾向于算法流程中的某些節點算法的延遲實現,也就是從算法流程中拆分出的一部分子算法;策略模式注重的是單一算法,其本身就是一個完整的算法實現,無需拆分。

    經過上述漫長的解析過程,構成ServiceMethod所需的成員的賦值終于準備完成,最終,ServiceMethod.Builder.build方法返回了new ServiceMethod對象。


至此,我們已經分析到了Retrofit.create在動態代理時解析注解封裝進ServiceMethod的過程。相信大家也需要一點時間消化,我們先休息會,稍后回來~~~~

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,283評論 6 530
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 97,947評論 3 413
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,094評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,485評論 1 308
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,268評論 6 405
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,817評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,906評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,039評論 0 285
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,551評論 1 331
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,502評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,662評論 1 366
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,188評論 5 356
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 43,907評論 3 345
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,304評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,563評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,255評論 3 389
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,637評論 2 370

推薦閱讀更多精彩內容

  • 適配器模式上一篇文章我們已經分析了Retrofit解析注解封裝進ServiceMethod的流程,讀者在這里要記住...
    andcoder閱讀 669評論 0 2
  • Retrofit這個開源庫出來也有一定年頭了,記得之前還是在V1.0的版本的時候,之前在三月份也寫過一個Retro...
    lovejjfg閱讀 1,455評論 0 5
  • retrofit是Square公司對OKHTTP封裝的網絡請求框架,完美支持rxjava,主要思想是使用java的...
    蒼龍閣閣主閱讀 648評論 0 3
  • 用法: Introduction Retrofit turns your HTTP API into a Java...
    Fargo的狗窩閱讀 269評論 0 1
  • 這篇文章是對最近關于人際關系交往的一些思考。 寫這篇文章的原因是最近網上新交了一些新朋友,同時以為一些原因讓原來不...
    成年小飯閱讀 482評論 0 0