Retrofit之CallAdapter解析

語言: CN / TW / HK

highlight: a11y-dark

攜手創作,共同成長!這是我參與「掘金日新計劃 · 8 月更文挑戰」的第3天,點選檢視活動詳情

前言

在Retrofit原始碼裡面,網路請求介面卡CallAdapter是一個出現得比較頻繁的介面,在呼叫介面方法發起網路請求的時候,必然要呼叫CallAdapter的adapt方法。那CallAdapter實際上到底是什麼,Retrofit為什麼要設計這個介面,本文就來進一步扒一扒CallAdapter。

PS:本文基於Retrofit版本2.8.0,在閱讀本文之前建議先了解Retrofit的原始碼,可以參考挖一挖Retrofit原始碼(一)挖一挖Retrofit原始碼(二)

正文

用過Retrofit的朋友應該都熟悉下面的網路請求介面的常規寫法: kotlin interface TestApi { @GET("/.../...") fun getDataA(): Call<DataBean> } 介面方法getDataA的返回型別是Call,那思考一下想讓這個方法直接返回DataBean可以嗎?在初始化Retrofit時沒有新增任何CallAdapterFactory和不考慮掛起介面方法的情況下,答案是不可以的,此時getDataA的返回型別必須是Retrofit的Call而不能是其他型別,至於為什麼一定要返回Call呢?繼續看下去。

CallAdapter是什麼

在分析之前,首先來搞清楚CallAdapter是什麼,顧名思義CallAdapter就是Call(retrofit2.Call)的介面卡,用來適配不同返回型別的網路請求介面方法使其能正常呼叫OkHttpCall來發起網路請求,並返回該方法指定的返回型別的資料。

先來看下CallAdapter的原始碼: ```java public interface CallAdapter { Type responseType();

T adapt(Call call);

abstract class Factory { public abstract @Nullable CallAdapter<?, ?> get(Type returnType, Annotation[] annotations, Retrofit retrofit);

protected static Type getParameterUpperBound(int index, ParameterizedType type) {
  return Utils.getParameterUpperBound(index, type);
}

protected static Class<?> getRawType(Type type) {
  return Utils.getRawType(type);
}

} } ``` 其中CallAdapter中的泛型R是API方法的原始返回型別(報文中的資料結構),泛型T是適配後的返回型別(期望拿到的資料結構)。接口裡只有兩個方法:responseType()和adapt(),根據原始碼給出的註釋可以知道:

  • responseType()是用來獲取介面方法的響應型別的,例如當報文返回BaseResponseBean時,responseType就是泛型T,當報文返回的型別不帶泛型時,responseType直接就是返回的那個類
  • adapt()將Call物件轉換成一個泛型T物件

在介面的內部還定義了一個抽象類Factory,其中的get方法會根據介面方法的returnType來返回對應的CallAdapter,getParameterUpperBound方法的作用是返回第index個引數的最上層的類,getRawType方法的作用是返回引數的原始型別。

當然,單看這段原始碼很難知道CallAdapter具體是幹嘛的,responseType()和adapt()這兩個方法是在子類中實現的,目前Retrofit提供了以下幾種CallAdapterFactory:DefaultCallAdapterFactory、RxJavaCallAdapterFactory、CompletableFutureCallAdapterFactory、GuavaCallAdapterFactory、ScalaCallAdapterFactory,沒有新增CallAdapterFactory的話是預設使用DefaultCallAdapterFactory,接下來就看看CallAdapter的兩個方法在DefaultCallAdapterFactory裡具體是怎麼實現的。

DefaultCallAdapterFactory

```java final class DefaultCallAdapterFactory extends Factory { @Nullable private final Executor callbackExecutor;

DefaultCallAdapterFactory(@Nullable Executor callbackExecutor) {
    this.callbackExecutor = callbackExecutor;
}

@Nullable
public CallAdapter<?, ?> get(Type returnType, Annotation[] annotations, Retrofit retrofit) {
    //介面方法返回型別必須是Call<T>
    if (getRawType(returnType) != Call.class) {
        return null;
    } else if (!(returnType instanceof ParameterizedType)) {
        throw new IllegalArgumentException("Call return type must be parameterized as Call<Foo> or Call<? extends Foo>");
    } else {
        //獲取響應型別
        final Type responseType = Utils.getParameterUpperBound(0, (ParameterizedType)returnType);
        final Executor executor = Utils.isAnnotationPresent(annotations, SkipCallbackExecutor.class) ? null : this.callbackExecutor;
        return new CallAdapter<Object, Call<?>>() { //Call<?>為Retrofit的Call
            public Type responseType() {
                return responseType;
            }

            public Call<Object> adapt(Call<Object> call) {
                return (Call)(executor == null ? call : new DefaultCallAdapterFactory.ExecutorCallbackCall(executor, call));
            }
        };
    }
}

static final class ExecutorCallbackCall<T> implements Call<T> {
    final Executor callbackExecutor;
    final Call<T> delegate;

    ExecutorCallbackCall(Executor callbackExecutor, Call<T> delegate) {
        this.callbackExecutor = callbackExecutor;
        this.delegate = delegate;
    }

    public void enqueue(final Callback<T> callback) {
        Objects.requireNonNull(callback, "callback == null");
        this.delegate.enqueue(new Callback<T>() {
            ......
        });
    }
}

} ``` 程式碼很簡單,DefaultCallAdapterFactory繼承了CallAdapter.Factory這個類,在get方法裡面主要做了以下的事:

  • 對介面方法的返回型別returnType進行了檢測,要求returnType的原始型別必須是Call,如果不是則返回null
  • 獲取方法的響應型別,即Call中的T,然後由CallAdapter的responseType方法返回
  • CallAdapter的adapt方法最終返回了ExecutorCallbackCall物件

分析到這裡想必大家也清楚在前面提到的前提條件下為什麼介面方法必須要返回Call了,至於ExecutorCallbackCall實際上是OkHttpCall的一個代理類,在呼叫介面方法的時候進行非同步請求時網路請求和回撥處理的工作實際上是由OkHttpCall的enqueue方法去完成的,回顧一下Retrofit的create方法: ```java public T create(final Class service) { validateServiceInterface(service); return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[] { service }, new InvocationHandler() { private final Platform platform = Platform.get(); private final Object[] emptyArgs = new Object[0];

      @Override public @Nullable Object invoke(Object proxy, Method method,
          @Nullable Object[] args) throws Throwable {
        // If the method is a method from Object then defer to normal invocation.
        if (method.getDeclaringClass() == Object.class) {
          return method.invoke(this, args);
        }
        if (platform.isDefaultMethod(method)) {
          return platform.invokeDefaultMethod(method, service, proxy, args);
        }
        return loadServiceMethod(method).invoke(args != null ? args : emptyArgs);
      }
    });

} ``` Retrofit的核心在於動態代理,呼叫介面方法時動態代理物件會將方法轉發到InvocationHandler的invoke方法中去處理,創建出這個方法的ServiceMethod物件然後呼叫HttpServiceMethod的invoke去發起請求,而實際上呼叫的正是CallAdapter的adapt方法,也就是呼叫鏈路實際上為loadServiceMethod.invoke -> ServiceMethod.invoke -> HttpServiceMethod.invoke->CallAdapter.adapt->ExecutorCallbackCall.enqueue->OkHttpCall.enqueue

那麼問題來了,既然實際上幹活的是OkHttpCall,OkHttpCall實現了Call介面,那在HttpServiceMethod的invoke方法裡直接返回OkHttpCall不就好了,這樣呼叫getDataA一樣能拿到正確的資料,返回的Call就是OkHttpCall了,那還要這個CallAdapter幹嘛?別急,Retrofit設計這個介面必然是有它的道理,繼續往下看。

CallAdapter到底有什麼意義

假如說在HttpServiceMethod的invoke方法裡直接返回OkHttpCall的話,就可以直接操控這個OkHttpCall物件去完成網路請求的工作,然而這樣一來,介面方法的返回型別就只能是Call了,那要是我不想返回Call呢?我想要返回自定義的Data類或者RxJava的Observable呢,這可怎麼搞?!

這時候CallAdapter的作用就體現出來了,介面方法可以定義想要的返回型別,然後初始化Retrofit時配置相應的CallAdapterFactory,CallAdapter對方法的返回型別進行適配,讓Retrofit能正常操控網路請求的核心類OkHttpCall進行網路請求,並在請求完成後返回適配後的物件。如Retrofit的BodyCallAdapter: ```java private static final class BodyCallAdapter implements CallAdapter> { private final Type responseType;

BodyCallAdapter(Type responseType) {
  this.responseType = responseType;
}

@Override public Type responseType() {
  return responseType;
}

@Override public CompletableFuture<R> adapt(final Call<R> call) {
  final CompletableFuture<R> future = new CallCancelCompletableFuture<>(call);

  call.enqueue(new Callback<R>() {
    @Override public void onResponse(Call<R> call, Response<R> response) {
      if (response.isSuccessful()) {
        future.complete(response.body());
      } else {
        future.completeExceptionally(new HttpException(response));
      }
    }

    @Override public void onFailure(Call<R> call, Throwable t) {
      future.completeExceptionally(t);
    }
  });

  return future;
}

} 介面方法發起網路請求實際上是操控OkHttpCall去請求,而只有Call才能去呼叫OkHttpCall(因為OkHttpCall實現的是Call介面),現在我期望通過CompletableFuture去完成請求工作,那麼呼叫這個API方法的時候Retrofit就會根據方法的原始返回型別構建一個Call物件出來交給CallAdapter,CallAdapter根據這個Call物件去構建CompletableFuture物件,並利用Call去執行請求,最終將請求結果封裝進CompletableFuture物件並返回。也就是說,不管API方法返回什麼型別,實際上都是由Call去執行請求的, 而經過CallAdapter的適配則讓任何返回型別的方法都能實現網路請求,就好比遊樂園入口處的檢票員只認門票不認錢,只要你有門票就能進,你想要進行網路請求就必須持有門票Call,而CallAdapter則類似於售票處,把不同形式的錢兌換成門票。mermaid sequenceDiagram Retrofit->>Call:構建 Call->>自定義型別:構建(CallAdapter) 自定義型別->>呼叫者:持有 呼叫者->>自定義型別:呼叫 自定義型別->>Call:呼叫 Call->>Retrofit:執行網路請求 Retrofit->>Call:返回結果 Call->>自定義型別:返回結果 自定義型別->>呼叫者:返回結果 好了現在我想要在API里加一個返回型別為Observable的方法:kotlin interface TestApi { @GET("/.../...") fun getDataA(): Call

@GET("/.../...")
fun getDataB(): Observable<DataBean>

} ``` Retrofit本身有提供RxJavaCallAdapterFactory,初始化時直接配置這個介面卡工廠之後就可以正常呼叫getDataB了,也就是說每個介面方法都需要有一個能適配其返回型別的CallAdapter的,那呼叫的時候Retrofit怎麼判斷由哪個CallAdapter來工作呢?繼續扒一扒Retrofit是怎麼檢索CallAdapter的。

CallAdapter的建立和檢索

建立Retrofit例項的時候是通過build方法來設定配置的,在build方法中會把手動新增的CallAdapterFactory加入網路請求介面卡工廠集合中: java public Retrofit build() { List<CallAdapter.Factory> callAdapterFactories = new ArrayList<>(this.callAdapterFactories); callAdapterFactories.addAll(platform.defaultCallAdapterFactories(callbackExecutor)); ......//省略無關程式碼 } 在生成介面方法的ServiceMethod物件時會呼叫到HttpServiceMethod的parseAnnotations方法,而對應這個方法的CallAdapter物件也是在parseAnnotations裡建立的: ```java static HttpServiceMethod parseAnnotations( Retrofit retrofit, Method method, RequestFactory requestFactory) { boolean isKotlinSuspendFunction = requestFactory.isKotlinSuspendFunction; boolean continuationWantsResponse = false; boolean continuationBodyNullable = false;

Annotation[] annotations = method.getAnnotations();
Type adapterType;
if (isKotlinSuspendFunction) {
  ......//Kotlin協程檢測

  adapterType = new Utils.ParameterizedTypeImpl(null, Call.class, responseType);
  annotations = SkipCallbackExecutorImpl.ensurePresent(annotations);
} else {
  adapterType = method.getGenericReturnType();
}

CallAdapter<ResponseT, ReturnT> callAdapter =
    createCallAdapter(retrofit, method, adapterType, annotations);
Type responseType = callAdapter.responseType();
......//省略無關程式碼

} parseAnnotations方法中先是獲取了介面方法的返回型別adapterType,並傳進createCallAdapter裡生成並返回介面方法的CallAdapter物件,繼續進入createCallAdapter方法:java private static CallAdapter createCallAdapter( Retrofit retrofit, Method method, Type returnType, Annotation[] annotations) { try { //noinspection unchecked return (CallAdapter) retrofit.callAdapter(returnType, annotations); } catch (RuntimeException e) { // Wide exception range because factories are user code. throw methodError(method, e, "Unable to create call adapter for %s", returnType); } } 可以看到實際上呼叫了Retrofit的callAdapter方法,繼續點進去看看到底幹了什麼:java public CallAdapter<?, ?> callAdapter(Type returnType, Annotation[] annotations) { return nextCallAdapter(null, returnType, annotations); } Retrofit.callAdapter裡呼叫了nextCallAdapter這個方法,檢索CallAdapter的實際呼叫鏈路為loadServiceMethod -> ServiceMethod.parseAnnotations -> HttpServiceMethod.parseAnnotations->HttpServiceMethod.createCallAdapter->Retrofit.callAdapter->Retrofit.nextCallAdapter:java public CallAdapter<?, ?> nextCallAdapter(@Nullable CallAdapter.Factory skipPast, Type returnType, Annotation[] annotations) { Objects.requireNonNull(returnType, "returnType == null"); Objects.requireNonNull(annotations, "annotations == null");

int start = callAdapterFactories.indexOf(skipPast) + 1;
for (int i = start, count = callAdapterFactories.size(); i < count; i++) {
  CallAdapter<?, ?> adapter = callAdapterFactories.get(i).get(returnType, annotations, this);
  if (adapter != null) {
    return adapter;
  }
}
......

} ``` 在nextCallAdapter方法裡面,根據介面方法的返回型別returnType遍歷呼叫CallAdapter.Factory的get方法,直到找出合適的CallAdapter並返回一個CallAdapter物件。也就說明在建立介面方法的ServiceMethod物件時,會在網路請求介面卡工廠集合里根據方法的返回型別檢索出合適的CallAdapter,並把這個CallAdapter物件封裝進ServiceMethod物件中。