Retrofit源碼詳解

Retrofit的使用從很早之前就已經開始了, 但是一直沒有深入研究為什么使用Retrofit只要定義一個接口, 同時在接口的方法上和方法的參數上加上一些注解就可以完成Http請求了, 也沒有研究請求參數和請求結果是如何進行封裝的, 所以使用Retrofit一直是處于一知半解的狀態, 不知道其內部的原理, 因此花了一點時間看了Retrofit的源碼, 對Retrofit的整個請求流程有了一定的理解.

Retrofit核心源碼解讀

1. 創建Retrofit對象

創建Retrofit對象的時候使用的是Builder模式, 可以在創建Retrofit對象的時候設置RetrofitbaseURL, 添加自己的converterFactory

例子:

Retrofit retrofit = new Retrofit.Builder()
           .baseUrl(GITHUB_API)
           .addConverterFactory(GsonConverterFactory.create())
           .build();

注意點:

  • 在設置baseUrl的時候和Retrofit 1.x版本不同的是url必須以'/'結尾

    源碼:

    
    // Retrofit.java#baseUrl method
    
    public Builder baseUrl(HttpUrl baseUrl) {
        ....
       // pathSegments() 返回的是url的查找路徑List(查找路徑是以'/'分割的字符串, 所以只要split("/")就可以了)
      List<String> pathSegments = baseUrl.pathSegments();
      // 如果List的最后一個元素不是(""), 則說明查找路徑不是以'/'結尾的字符串
      if (!"".equals(pathSegments.get(pathSegments.size() - 1))) {
        throw new IllegalArgumentException("baseUrl must end in /: " + baseUrl);
      }
        .... 
    }
    
    
  • 如果不添加自己的轉換器, 則返回值只能封裝成ResponseBody類型或者沒有返回值

    源碼:

    // Retrofit.java#Builder
    
    Builder(Platform platform) {
           this.platform = platform;
           // 添加默認的BuiltInConverter
           converterFactories.add(new BuiltInConverters());
        }
    
    // BuiltInConverters.java#responseBodyConverter method
    
     public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit) {
     
         // 如果返回值是ResponseBody類型
        if (type == ResponseBody.class) {
          return Utils.isAnnotationPresent(annotations, Streaming.class)
              ? StreamingResponseBodyConverter.INSTANCE
              : BufferingResponseBodyConverter.INSTANCE;
        }
        
        // 如果返回值類型是Void類型
        if (type == Void.class) {
          return VoidResponseBodyConverter.INSTANCE;
        }
        
        // 如果返回值是其他的類型
        return null;
      }
    
    
  • 創建Retrofit對象時使用了Builder模式, 而使用Builder模式的好處就是創建的對象是不可變對象(因為屬性都是private的且沒有setter方法), 這樣在使用的使用就不用擔心對象中屬性是否被修改或者不小心修改了對象中的屬性了. 如果存在有些請求不能和其他請求共用同一個Retrofit對象, 但是大部分的屬性都一樣只有少部分的屬性不一致, 那么可以用一個Retrofit對象為模版來創建一個新的Retrofit對象

    源碼:

    
    // Retrofit.java#Builder
    
    Builder(Retrofit retrofit) {
        // 以retrofit對象為模版生成一個新的Retrofit對象
      this.platform = Platform.get();
      callFactory = retrofit.callFactory;
      baseUrl = retrofit.baseUrl;
      converterFactories.addAll(retrofit.converterFactories);
      adapterFactories.addAll(retrofit.adapterFactories);
      // Remove the default, platform-aware call adapter added by build().
      adapterFactories.remove(adapterFactories.size() - 1);
      callbackExecutor = retrofit.callbackExecutor;
      validateEagerly = retrofit.validateEagerly;
    }
    
  • 這里要額外提一下. 因為Retrofit是支持多個平臺(jvm, android, ios), 那么Retrofit是如何分辨當前引用的是什么平臺的呢, 實際上Retrofit使用的方法就是查看當前的classpath中是否存在相應平臺特有的class來判斷當前是在哪個平臺. 這個和Spring的@ConditionalOnClass(ClassName.class)來決定是否初始化注解所修飾的bean有異曲同工的味道

    關鍵代碼:

    
    // Platform.java#findPlatform method 
    // 查找當前所在的平臺
    private static Platform findPlatform() {
        try {
          // 如果存在"android.os.Build"則是android平臺
          Class.forName("android.os.Build");
          if (Build.VERSION.SDK_INT != 0) {
            return new Android();
          }
        } catch (ClassNotFoundException ignored) {
        }
        try {
          // 如果存在"java.util.Optional"則是java8平臺
          Class.forName("java.util.Optional");
          return new Java8();
        } catch (ClassNotFoundException ignored) {
        }
        try {
          // 如果存在"org.robovm.apple.foundation.NSObject"則是ios平臺
          Class.forName("org.robovm.apple.foundation.NSObject");
          return new IOS();
        } catch (ClassNotFoundException ignored) {
        }
        
        // 默認平臺
        return new Platform();
      }
    

2. 創建發送請求的接口

包含發送請求方法必須是一個接口, 同時這個接口是不能繼承其他的接口的

例子:

    public interface GitApi {
        @GET("/users/{user}")
        Call<GitModel> getFeed(@Path("user") String user);
    }

注意點:

  • 包含請求方法必須是一個接口同時不能繼承其他的接口的

    源碼:

    static <T> void validateServiceInterface(Class<T> service) {
    
         // class必須是一個接口
        if (!service.isInterface()) {
          throw new IllegalArgumentException("API declarations must be interfaces.");
        }
        
        // 接口不能繼承其他的接口
        if (service.getInterfaces().length > 0) {
          throw new IllegalArgumentException("API interfaces must not extend other interfaces.");
        }
    }
    
  • 為什么只要定一個接口和一個請求方法, Retrofit就可以發送Http請求了呢? 因為Retrofit使用了代理, 其實在創建接口的對象的時候返回的是一個代理對象, 這個代理其實也是Retrofit的核心.

    源碼:

        // 創建接口的實現對象, 返回的是GitApi接口的代理對象
       GitApi git = retrofit.create(GitApi.class);
       
       // 關鍵代碼: create方法
       public <T> T create(final Class<T> service) {
       
            // 校驗service是否是一個沒有繼承其他接口的接口
            Utils.validateServiceInterface(service);
            
            // 如果validateEagerly==true, 則先將接口中的方法全部都放到serviceMethodCache中,
            // 這樣在之后的調用過程中就不需要走loadServiceMethod的流程, 而是直接走的緩存, 這樣子可以加快訪問的速度,
            // 但是這樣子也存在一定的壞處, 因為會造成內存占用量變大而且可能有些方法不會被調用都被放到緩存中了
            if (validateEagerly) {
              eagerlyValidateMethods(service);
            }
            
            // 代理模式,
            // 使用代理攔截接口中所有的方法, 從而解析方法上的注解, 方法參數上的注解, 返回值等, 這也是retrofit可以實現面向接口和注解編程的關鍵
            // service: 真實對象, proxy: 代理對象, method: 調用的方法, args: 方法參數
            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, Object... args)
                      throws Throwable {
                    
                    // 如果調用的是Object對象中方法則直接返回調用結果
                    if (method.getDeclaringClass() == Object.class) {
                      return method.invoke(this, args);
                    }
                    // 如果調用的是接口中的默認方法則直接返回對應平臺對調用默認方法的處理
                    if (platform.isDefaultMethod(method)) {
                      return platform.invokeDefaultMethod(method, service, proxy, args);
                    }
                    
                    // 獲取或者創建ServiceMethod對象
                    ServiceMethod<Object, Object> serviceMethod =
                        (ServiceMethod<Object, Object>) loadServiceMethod(method);
                   // 創建OkHttpCall對象用于發送請求和解析結果 
                    OkHttpCall<Object> okHttpCall = new OkHttpCall<>(serviceMethod, args);
                    return serviceMethod.callAdapter.adapt(okHttpCall);
                  }
                });
            }
    
  • 上面很明顯可以看到Retrofit實際上使用了jdkProxy動態代理技術使得可以定義一個接口加上注解就完成http請求. 由于使用了Proxy所以必須定義成一個接口而不是一個抽象類的原因也就顯而易見了, 因為jdkproxy只能生成接口的代理對象. 如果這里使用的是cglib庫的話那也可以定義成抽象類

3. 發送Http請求

要發送http請求就離不開ServiceMethod類和OkHttpCall類, 他們是發送Http請求的核心類. 首先會從Map中獲取和當前請求方法相關聯的ServiceMethod, 如果找到了(說明之前這個方法已經被調用過了)就直接使用找到的ServiceMethod, 如果沒有找到, 則創建一個新的ServiceMethod并且和當前的請求方法相關聯putMap中. 最后調用OkHttpCall.execute發送請求.

源碼:

  // 查找或者判斷和請求的方法相關聯的ServiceMethod
  
    ServiceMethod<?, ?> loadServiceMethod(Method method) {
        // 緩存技術
        ServiceMethod<?, ?> result = serviceMethodCache.get(method);
        if (result != null) return result;
    
        // 使用了兩次判斷(兩段鎖)
        synchronized (serviceMethodCache) {
          // 這一次從緩存中取是有必要的且非常重要, 如果沒有這一次則有可能下面的代碼會被重復執行, 同一個key也可能被重復賦值
          result = serviceMethodCache.get(method);
          if (result == null) {
            result = new ServiceMethod.Builder<>(this, method).build();
            serviceMethodCache.put(method, result);
          }
        }
        return result;
    }
  • 創建ServiceMethod也使用了Build模式, 在build方法中對請求的方法上的注解, 方法參數, 返回值進行了解析

源碼:

public ServiceMethod build() {
 // 創建callAdapter對象(默認使用的是DefaultCallAdapterFactory), callAdapter對象用于.
 // callAdapter對象的主要作用就是返回一個CallAdapter接口的實例用于調用底層okhttp的方法發送請求和解析返回值(DefaultCallAdapterFactory中使用OkHttpCall.execute來發送請求和解析結果). 
 // 默認的請求方法返回值都是Call<T>, 可以繼承CallAdapter.Factory來實現自定義的返回值類型
 callAdapter = createCallAdapter();
 
 ...
 other code
 ...
 
 // 創建返回值解析器(默認只有BuiltInConverters), 可以在創建Retrofit的時候使用addConverterFactory加入其他的converters
 responseConverter = createResponseConverter();

    // 解析方法上的注解
 for (Annotation annotation : methodAnnotations) {
   parseMethodAnnotation(annotation);
 }

 ...
 other code
 ...
 
 // 解析參數
 int parameterCount = parameterAnnotationsArray.length;
 parameterHandlers = new ParameterHandler<?>[parameterCount];
 for (int p = 0; p < parameterCount; p++) {
   Type parameterType = parameterTypes[p];
   if (Utils.hasUnresolvableType(parameterType)) {
     throw parameterError(p, "Parameter type must not include a type variable or wildcard: %s",
         parameterType);
   }

   Annotation[] parameterAnnotations = parameterAnnotationsArray[p];
   if (parameterAnnotations == null) {
     throw parameterError(p, "No Retrofit annotation found.");
   }

   parameterHandlers[p] = parseParameter(p, parameterType, parameterAnnotations);
 }

 ...
 一些錯誤情況的處理
 ...

 return new ServiceMethod<>(this);
}
  • Retrofit.java#createproxy中調用請求方法最后返回的是 return serviceMethod.callAdapter.adapt(okHttpCall);, 默認情況下返回的就是OkHttpCall, 所以最后調用的就是OkHttpCall.execute()方法發送請求和解析返回結果

關鍵代碼:

    @Override public Response<T> execute() throws IOException {
    okhttp3.Call call;
    
    synchronized (this) {
      
      ...
        
      call = rawCall;
      if (call == null) {
        try {
          // 創建Okhttp的call方法
          call = rawCall = createRawCall();
        } catch (IOException | RuntimeException e) {
          ...
        }
      }
    }
        
     ....
    
     // call.execute: 調用okhttp發送真正的請求
     // parseResponse: 對返回的結果進行解析, 方法就是調用創建retrofit時加入的所有converterFactory, 知道找到一個converter可以處理返回值
    return parseResponse(call.execute());
  }

總結

至此, Retrofit發送請求的整個流程就已經講解完畢了, 實際上整個流程中關鍵點就只有幾個, 比如:

  • Retrofit.java#create方法, 這個方法的作用就是使用Proxy創建用戶定義的接口的實現, 從而實現使用時只要創建接口, 創建方法, 添加相關注釋三個步驟就可以完成一個http請求的關鍵

  • ServiceMethod.java#build方法, 這個方法的作用是對請求方法上的注解, 方法的參數, 方法的返回值進行解析, 同時創建callAdapter對象, 這是Retrofit留下的一個'插口', 只要我們實現CallAdapter.Factory就可以處理自定義的方法返回值類型, 方法不一定要返回Call<T>類型

  • OkHttpCall.java#execute方法, 這個方法的作用是調用底層的okhttp發送真正的http請求 , 然后對返回結果進行解析. 解析返回結果是的方法就是遍歷Retrofit中的converterFactories, 知道找到一個可以解析返回結果的對象. 這事Retrofit留下的另一個'插口', 只要我們實現Converter.Factory就可以處理自定義的返回結果, 而不一定只能是Call<ResponseBody>類型

另外, Retrofit提供了

Gson: com.squareup.retrofit2:converter-gson
Jackson: com.squareup.retrofit2:converter-jackson
Moshi: com.squareup.retrofit2:converter-moshi
Protobuf: com.squareup.retrofit2:converter-protobuf
Wire: com.squareup.retrofit2:converter-wire
Simple XML: com.squareup.retrofit2:converter-simplexml
Scalars (primitives, boxed, and String): com.squareup.retrofit2:converter-scalars

這寫轉換器, 應該可以滿足日常開發需要了.

從上面可以看到, Retrofit可以允許我們自定義返回類型, 返回結果同時屏蔽掉了底層的OkHttp的復雜性, 使得我們只要定義一個接口就完成Http請求的發送, 這對于使用者來說是非常友好的. 易于上手和高度的定制性是現如今Retrofit如此流行的關鍵

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容