Retrofit2之CallAdapter和Converter

1、這篇文章適合誰?

先看一個問題,Retrofit2 中定義的接口能夠直接返回一個ResponseBodyString嗎?html

public interface RestClientV1 {
	@GET("order/detail")
	String getOrderDetail(@Query("orderId") long orderId);
}
複製代碼

若是你不能確定的回答能夠,同時不能清楚的知道該怎麼作,很是推薦閱讀這篇文章。這是一篇 Retrofit2 的進階用法的文章,若是不熟悉 Retrofit2 的基本用法,建議先去 官網 看一下教程,再過來看這篇文章。若是你正在考慮如何使用 Retrofit2 來封裝一個網絡層,這篇文章講到的示例、原理和設計思想應該會很是適合你。java

2、如何實現上面的功能

咱們先看兩段源碼:android

  • CallAdapter.java
public interface CallAdapter<R, T> {
    Type responseType();
    T adapt(Call<R> call);
  
    abstract class Factory {
        public abstract @Nullable CallAdapter<?, ?> get(Type returnType, 
        Annotation[] annotations,
                Retrofit retrofit);
        //省略
  }
}
複製代碼

這個適配器的做用是將一個R,轉化爲一個自定義類型T;其中的適配器工廠會根據returnType(就是上面接口中定義的返回類型)來返回相應的適配器。git

  • Converter.java
public interface Converter<F, T> {
    T convert(F value) throws IOException;
    abstract class Factory {
        public @Nullable Converter<ResponseBody, ?> responseBodyConverter(Type type,
                Annotation[] annotations, Retrofit retrofit) {
            return null;
        }
        //省略
    }
}
複製代碼

這個轉換器的做用就是將F對象轉換爲T對象;其中轉換器工廠中的responseBodyConverter(..)須要根據type(就是咱們自定義的響應結果類型)來返回相應的轉換器,而且這個轉換器中的F被指定爲了ResponseBodygithub

你們可能對這兩個接口的設計和做用仍是有點困惑,不要緊,下面我們會反覆說到這兩個接口。api

下面開始寫咱們的實現代碼:網絡

2.1 定義StringCallAdapterFactoryStringCallAdapter

public class StringCallAdapterFactory extends CallAdapter.Factory {
    @Nullable
    @Override
    public CallAdapter<?, ?> get(Type returnType, Annotation[] annotations, Retrofit retrofit) {
        if(returnType == String.class)
            return new StringCallAdapter();
    	 return null;
    }

    class StringCallAdapter implements CallAdapter<String,String>{
        @Override
        public Type responseType() {
            return String.class;
        }

        @Override
        public String adapt(Call<String> call) {
            try {
                return call.execute().body();
            } catch (IOException e) {
                e.printStackTrace();
            }
            return "";
        }
    }
}

複製代碼

咱們在StringCallAdapterFactoryget(..)方法中,當發現接口的返回類型是String時,返回咱們自定義的StringCallAdapter,而StringCallAdapter,顧名思義就是Callretrofit2.Call)的一個適配器,做用就是將Call轉化成String(這個邏輯具體是在adapt(..)方法裏面處理)。特別地,responseType()方法的做用是告訴Converter,我須要一個String.class的響應數據。
app

2.2 定義StringConverterFactoryStringConverter

public class StringConverterFactory extends Converter.Factory {
    @Nullable
    @Override
    public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit) {
        if (type == String.class) {
            return new StringConverter();
        }
        return null;
    }

    class StringConverter implements Converter<ResponseBody, String> {

        @Override
        public String convert(ResponseBody value) throws IOException {
            return value.string();
        }
    }
}
複製代碼

類似的,咱們在StringConverterFactoryresponseBodyConverter(..)方法中,當發現參數type(就是上一步的responseType()方法返回值)是String的時候,返回一個自定義的StringConverter,這個適配器的做用是把 http 響應數據ResponseBody轉化爲String,其實現也很簡單,直接調用ResponseBody.string()方法便可。框架

2.3 測試

當咱們在寫跟界面無關的代碼的時候,特別推薦使用單元測試來驗證邏輯的正確性,這是一件省時省力,又能夠有效確保質量的作法。下面是咱們寫的一個簡單的測試示例:異步

@Before
public void create() {
    mockWebServer = new MockWebServer();
    mockWebServer.setDispatcher(new MockDispatcher());
    OkHttpClient client = new OkHttpClient.Builder().build();
    restClientV1 = new Retrofit.Builder()
            .baseUrl(mockWebServer.url("/"))
            .client(client)
            .addCallAdapterFactory(new StringCallAdapterFactory())
            .addConverterFactory(new StringConverterFactory())
            .build()
            .create(RestClientV1.class);
}
@Test
public void test() {
   System.out.println(restClientV1.getOrderDetail(1));
}   
複製代碼

運行結果: hi man,this is order detail(orderId=1)

完整可運行的代碼在這裏

3、原理解析

分析框架原理的時候,通常我會先去找切入點,或者是疑惑點。基於上面的例子,有 3 個疑惑點:

  1. RestClientV1.getOrderDetail(..)方法返回類型(稱爲returnType) 是如何起做用的?
  2. CallAdapterFactory是如何起做用的?
  3. ConverterFactory是如何起做用的?

下面咱們順着這個思路來查看相應源碼:

3.1 從returnTypeCallAdapter<T, R>

在源碼中咱們先找到獲取CallAdapter<T, R>的地方

private CallAdapter<T, R> createCallAdapter() {
      Type returnType = method.getGenericReturnType();
      //省略
      Annotation[] annotations = method.getAnnotations();
      try {
        //<-關鍵代碼
        return (CallAdapter<T, R>) retrofit.callAdapter(returnType, annotations);
      } catch (RuntimeException e) { // Wide exception range because factories are user code.
        throw methodError(e, "Unable to create call adapter for %s", returnType);
      }
    }
複製代碼

代碼中的 method 對象就是RestClientV1.getOrderDetail(..)方法

上面關鍵代碼處以returnType爲參數調用了retrofit.callAdapter(..)方法獲取一個適配器,接着看

public CallAdapter<?, ?> callAdapter(Type returnType, Annotation[] annotations) {
    return nextCallAdapter(null, returnType, annotations);
}
  
public CallAdapter<?, ?> nextCallAdapter(@Nullable CallAdapter.Factory skipPast, Type returnType,
      Annotation[] annotations) {
    int start = adapterFactories.indexOf(skipPast) + 1;
    for (int i = start, count = adapterFactories.size(); i < count; i++) {
    	CallAdapter<?, ?> adapter = adapterFactories.get(i).get(returnType, annotations, this);
      	if (adapter != null) {
        	return adapter;
      }
    }
}
複製代碼

核心是這一行adapterFactories.get(i).get(returnType, annotations, this),就是循環調用CallAdapter.Factory的 get 方法來獲取一個可用的適配器,一旦找到就返回。注意這裏的returnType就是String.class,再回過頭來看咱們以前的 2.1 的代碼,

public CallAdapter<?, ?> get(Type returnType, Annotation[] annotations, Retrofit retrofit) {
        if(returnType == String.class)
            return new StringCallAdapter();
    	 return null;
}
複製代碼

這時咱們就拿到了一個StringCallAdapter對象。拿到後還作了一件事情,再看代碼

callAdapter = createCallAdapter();
      responseType = callAdapter.responseType();
      //省略
      responseConverter = createResponseConverter();
複製代碼

其中把callAdapter.responseType()方法的結果存了下來,而後調用了createResponseConverter()

3.2 從responseTypeConverter<F, T>

再看createResponseConverter()方法代碼:

private Converter<ResponseBody, T> createResponseConverter() {
      Annotation[] annotations = method.getAnnotations();
      try {
        return retrofit.responseBodyConverter(responseType, annotations);
      } catch (RuntimeException e) { // Wide exception range because factories are user code.
        throw methodError(e, "Unable to create converter for %s", responseType);
      }
}
    
public <T> Converter<ResponseBody, T> responseBodyConverter(Type type, Annotation[] annotations) {
       return nextResponseBodyConverter(null, type, annotations);
}

public <T> Converter<ResponseBody, T> nextResponseBodyConverter( @Nullable Converter.Factory skipPast, Type type, Annotation[] annotations) {
    int start = converterFactories.indexOf(skipPast) + 1;
    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;
      }
    }
}
複製代碼

經過上面的代碼追蹤,咱們知道在retrofit.responseBodyConverter(..)中的responseType參數正是StringCallAdapter中返回的String.class,再看核心的獲取Converter的方法,跟獲取CallAdapter的方法基本一致,結合 2.2 的代碼

public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit) {
        if (type == String.class) {
            return new StringConverter();
        }
        return null;
    }
複製代碼

顯然,這時會返回一個StringConverter對象。

3.3 CallAdapter<T, R>Converter<F, T> 如何起做用?

這時框架已經獲取到的StringCallAdapterStringConverter,而後它們會被存放在一個ServiceMethod對象中

final class ServiceMethod<R, T> {
	final CallAdapter<R, T> callAdapter;
	private final Converter<ResponseBody, R> responseConverter;
}
複製代碼

咱們再看看這兩個對象分別是在哪一個地方起做用的

  • CallAdapter<T, R>的做用處
public <T> T create(final Class<T> service) {
    //省略部分代碼
    return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[] { service },
        new InvocationHandler() {
          @Override public Object invoke(Object proxy, Method method, @Nullable Object[] args) throws Throwable {
            //省略部分代碼
            ServiceMethod<Object, Object> serviceMethod =
                (ServiceMethod<Object, Object>) loadServiceMethod(method);
            OkHttpCall<Object> okHttpCall = new OkHttpCall<>(serviceMethod, args);
            return serviceMethod.callAdapter.adapt(okHttpCall);//<- 關鍵代碼在這裏
          }
        });
  }
複製代碼

上面的代碼使用動態代理返回了service接口的一個實現類,調用restClient.getOrderDetail(1)方法時,它的返回值就是invoke(..)方法的返回值,這個返回值就是調用StringCallAdapter.adapt(..)的返回值。再看 2.1 中我們自定義適配器的代碼

@Override
public String adapt(Call<String> call) {
    try {
        return call.execute().body();
    } catch (IOException e) {
        e.printStackTrace();
    }
    return "";
}
複製代碼

咱們直接將bodyString的形式返回了。Retrofit這個設計很是的巧妙和靈活,咱們能夠將一個 http 請求包裝成任何對象返回,能夠選擇包裝的時候直接執行 http 請求(就像咱們這個例子),也可使其再調用新的包裝對象的某個方法再執行 http 請求(好比自帶的Call)。

  • Converter<F, T>的做用處

前面咱們看到實際執行 http 請求的是OkHttpCall,在請求執行完成後有一段解析Response的代碼

Response<T> parseResponse(okhttp3.Response rawResponse) throws IOException {
    ResponseBody rawBody = rawResponse.body();
	//省略代碼
    ExceptionCatchingRequestBody catchingBody = new ExceptionCatchingRequestBody(rawBody);
    try {
      T body = serviceMethod.toResponse(catchingBody);//<- 關鍵代碼在這裏
      return Response.success(body, rawResponse);
    } catch (RuntimeException e) {
      catchingBody.throwIfCaught();
      throw e;
    }
  }
複製代碼

這個方法的做用是將okhttp3.Response轉化爲retrofit2.Response

由於 Retrofit2 最終也是使用 Okhttp 來發起 http 請求的。 再看上面關鍵代碼的實現

/** Builds a method return value from an HTTP response body. */
R toResponse(ResponseBody body) throws IOException {
   return responseConverter.convert(body);
}
複製代碼

其實很簡單,直接調用了咱們自定義的StringConverter來獲取一個自定義對象,再看 2.2 中自定義的轉換器的代碼

@Override
public String convert(ResponseBody value) throws IOException {
    return value.string();
}
複製代碼

這個也很是簡單,就很少解釋了。

最後我們再用圖形的方式來捋一下整個調用流程:

retrofit2-calladapter-convert

4、進階使用

前面這個例子咱們在實際中並不會這麼用,下面咱們使用 Retrofit2 來作一個有實用價值的 App 網絡層封裝,在這以前咱們先看看在網絡層封裝時基本都會遇到的幾個問題:

  1. 網絡請求返回時,須要防止組件(ActivityFragment)已被銷燬時致使的崩潰
  2. 監聽請求狀態,靈活實現 loading、success 和 error
  3. 方便串行發起多個網絡請求
  4. 便於實現請求先後的業務攔截,好比登陸token失效自動跳轉登陸頁面

爲了解決上面的問題,咱們設計本身的Call接口,在看Call接口的代碼前,有幾點須要說明下:

  • 通常咱們須要跟服務端約定一個統一的數據返回格式,在這個示例中,約定以下:
public class ApiResponse<T> {
	/** * api業務響應狀態 */
    private String status;
    /** * api業務數據 */
    private T content;
    /** * api業務響應失敗錯誤碼 */
    private String errorCode;
    /** * 錯誤字符串信息 */
    private String errorMsg;
}
複製代碼

正常狀況下,服務端會返回請求業務成功( ok )或者失敗( fail );異常狀況下,客戶端增長一個 error 的 status ,用於表示非 200 的請求以及調用拋出異常的狀況

  • 爲了適配不一樣的 Loading 樣式,咱們設計了ProgressOperation接口
public interface ProgressOperation {
    /**加載數據失敗*/
    void showFailed();
	/**加載數據成功*/
    void showContent();
	/**開始加載數據*/
    void showProgress();
}
複製代碼

這樣當咱們須要使用不一樣的界面 展現形式(如:ProgressDialog或者Progressbar)來實現 Loading 的時候,只要分別作一個實現類便可

  • 示例中使用了 Android 官方的 lifecycle 組件,用來管理組件的生命週期,沒用過的同窗能夠去官網簡單查看一下教程,不須要了解太深,只要知道大概便可

下面是自定義的Call接口代碼:

public interface Call<T> {
    /** * 設置http200 ok的回調 */
    Call<T> ok(@NonNull Observer<T> observer);

    /** * 設置http200 fail的回調 */
    Call<T> fail(@NonNull Observer<ApiResponse<T>> observer);

    /** * 設置error的回調 */
    Call<T> error(@NonNull Observer<ApiResponse<T>> observer);

    /** * 設置進度監聽 */
    Call<T> progress(@NonNull ProgressOperation progressOperation);

    /** * 執行異步請求,綁定組件生命週期(獲取所有狀態結果) */
    void enqueue(@NonNull LifecycleOwner owner, @NonNull Observer<ApiResponse<T>> observer);

    /** * 執行異步請求,綁定組件生命週期(獲取部分狀態結果) */
    void enqueue(@NonNull LifecycleOwner owner);

    /** * 執行異步請求,但不須要綁定組件生命週期(獲取部分狀態結果) */
    void enqueue();

    /** * 執行異步請求,但不須要綁定組件生命週期(獲取所有狀態結果) */
    void enqueue(@NonNull Observer<ApiResponse<T>> observer);

    /** * 發起同步網絡請求 */
    ApiResponse<T> execute();

    /** * 取消請求 * 一、對於單個http請求,取消時若是尚未開始執行,則不執行;若是在執行中,則會確保執行結束不會回調,不確保必定能被取消 * 二、對於多個連續http請求,除了1的特性外,取消後剩下的未開始執行請求也不會被執行 */
    void cancel();

    /** * 是否被取消 * * @return */
    boolean isCancelled();
}


複製代碼

上面的代碼註釋已經比較詳細,你們能夠仔細看下

  • 咱們以 Android 官方的LiveData爲基礎作了單個請求Call實現類,實現這塊就再也不講解,能夠有多種實現方式,有興趣你們能夠自行查看源碼。下面我們看一下如何使用這個Call
restClientV1.ok("1").progress(progress).ok(content -> {
     System.out.println(content.getName());
}).enqueue(lifecycleOwner);
複製代碼

其中lifecycleOwner能夠直接使用 supportv26 包中的ActivityFragment,傳入這個對象後,若是組件已處於 destroy 狀態,則回調不會被執行

  • 對於須要執行多個任務的狀況,能夠這樣用
Task task1 = ((lifeState, apiResponse) -> restClientV1.okArray("1").execute());
Task task2 = ((lifeState, apiResponse) -> restClientV1.ok("1").execute());
Call<Content> call = MergeCall.task(task1, task2);
call.enqueue(lifecycleOwner,apiResponse -> {
     if(apiResponse.isOk()){
           //更新UI
     }else{
           //顯示錯誤信息
     }
});
複製代碼

上面咱們使用了本身定義的Task接口來描述一個任務,執行多個任務的時候,只有上個任務成功,纔會執行下一個任務,不然會直接執行回調

  • 對於業務攔截器,咱們能夠這樣定義
public class CheckTokenInterceptor implements Call.Interceptor {
    public static final CheckTokenInterceptor INSTANCE = new CheckTokenInterceptor();

    /** * 返回true表示中止下一步執行 */
    @Override
    public boolean preExecute() {
        return false;
    }
    /** * 對於異步請求,返回true表示中止下一步執行 */
    @Override
    public boolean onResponse(ApiResponse apiResponse) {
        return checkTokenExpired(apiResponse.getErrorCode());
    }
    private boolean checkTokenExpired(String errorCode) {
        //檢查token是否過時
        return false;
    }
}

複製代碼

而後能夠這樣設置攔截器

  • 對於單個網絡請求,在構造Retrofit對象時調用方法addCallAdapterFactory(new CustomCallAdapterFactory(CheckTokenInterceptor.INSTANCE))
  • 對於多個串行網絡請求,在生成MergeCall的時候傳入攔截器
public static Call task(Executor executor, List<Interceptor> interceptors, Task... tasks) {
        return new MergeCall(Arrays.asList(tasks), interceptors, executor);
}
public static Call task(Task... tasks) {
        return task(AsyncTask.THREAD_POOL_EXECUTOR, tasks);
}

//<- 關鍵在這裏
public static Call task(Executor executor, Task... tasks) {
        return MergeCall.task(executor, Arrays.asList(CheckTokenInterceptor.INSTANCE), tasks);
}
複製代碼

小結

從上面對於單個和多個串行請求的設計和用法中能夠看到,咱們解決了前面提到的4個問題。在這種設計下,咱們使用本身的Call接口作爲網絡層和其它層交互的紐帶,其它層(Presenter/ViewModelActivity/Fragment)徹底不知道底層使用的是什麼網絡框架,那麼若是哪天有一個更好用的網絡框架,咱們替換起來也是很是方便。最後再放一下本文 demo 的 源碼連接

在 demo 中,你們能夠從 test 目錄下相應的測試用例開始看起

立刻過年了,祝你們新年快樂^_^

相關文章
相關標籤/搜索