重溫Retrofit源碼,笑看協程實現

cover.png

最近迴歸看了一下Retrofit的源碼,主要是由於項目接入了協程,因此想研究一下Retorift是如何支持協程的。Retrofit是在Version 2.6.0開始支持協程的,因此本篇文章有關Retrofit的源碼都是基於2.6.0的。java

舒適提示,若是有 Retrofit的源碼閱讀經驗,閱讀這篇文章將會輕鬆不少。

<!--放心你沒有進錯房間,這不是分析協程的文章,只是恰好談到協程,因此仍是簡單說下Retrofit的實現。
不感興趣的能夠直接跳過下面的小插曲,放心不影響後續的閱讀。-->android

Retrofit

相信老鳥都應該很清楚,Retrofit核心部分是create()方法返回的動態代理(這裏就不詳細說明了,以後會有專門的文章分析動態代理)。就是下面這段代碼:git

public <T> T create(final Class<T> 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();
          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);
          }
        });
  }

第一眼看,跟我以前印象中的有點區別(也不知道是什麼版本),return的時候竟然沒有adapt方法了。開始還覺得有什麼重大的改變,其實也沒什麼,只是將以前的adapt方法封裝到invoke方法中。github

相關的method註解解析都放到ServiceMethod中,有兩個關鍵函數調用,分別是RequestFactoryHttpServiceMethodparseAnnotations()方法。算法

static <T> ServiceMethod<T> parseAnnotations(Retrofit retrofit, Method method) {
    RequestFactory requestFactory = RequestFactory.parseAnnotations(retrofit, method);
 
    Type returnType = method.getGenericReturnType();
    if (Utils.hasUnresolvableType(returnType)) {
      throw methodError(method,
          "Method return type must not include a type variable or wildcard: %s", returnType);
    }
    if (returnType == void.class) {
      throw methodError(method, "Service methods cannot return void.");
    }
 
    return HttpServiceMethod.parseAnnotations(retrofit, method, requestFactory);
  }

RequestFactory

首先RequestFactory中的parseAnnotations()最終經過build()方法來構建一個RequestFactory,用來保存解析出來的方法信息。api

RequestFactory build() {
      //1.解析方法上的註解
      for (Annotation annotation : methodAnnotations) {
        parseMethodAnnotation(annotation);
      }
      ...
      int parameterCount = parameterAnnotationsArray.length;
      parameterHandlers = new ParameterHandler<?>[parameterCount];
      //2.循環遍歷方法中的各個參數,解析參數的註解
      for (int p = 0, lastParameter = parameterCount - 1; p < parameterCount; p++) {
        parameterHandlers[p] =
            parseParameter(p, parameterTypes[p], parameterAnnotationsArray[p], p == lastParameter);
      }
      ...
      return new RequestFactory(this);
    }

能夠看到主要分爲兩步:微信

  1. 經過parseMethodAnnotation來解析出請求的方式,例如GETPOSTPUT等等;同時也會驗證一些註解的合規使用,例如MultipartFormUrlEncoded只能使用一個。
  2. 經過parseParameter來解析出請求的參數信息,例如PathUrlQuery等等;同時也對它們的合規使用作了驗證,例如QueryMapFieldMap等註解它們的key都必須爲String類型。這些註解的解析都是在parseParameterAnnotation()方法中進行的。

上面的p == lastParameter須要特別注意下,爲什麼要專門判斷該參數是否爲最後一個呢?請繼續向下看。架構

協程的判斷條件

下面咱們來着重看下parseParameter的源碼,由於從這裏開始就涉及到協程的判斷。異步

private @Nullable ParameterHandler<?> parseParameter(
        int p, Type parameterType, @Nullable Annotation[] annotations, boolean allowContinuation) {
      ParameterHandler<?> result = null;
      if (annotations != null) {
        for (Annotation annotation : annotations) {
          //1.解析方法參數的註解,並驗證它們的合法性
          ParameterHandler<?> annotationAction =
              parseParameterAnnotation(p, parameterType, annotations, annotation);

          if (annotationAction == null) {
            continue;
          }

          //每一個參數都只能有一個註解
          if (result != null) {
            throw parameterError(method, p,
                "Multiple Retrofit annotations found, only one allowed.");
          }

          result = annotationAction;
        }
      }

      //2.判斷是不是協程 
      if (result == null) {
        if (allowContinuation) {
          try {
            if (Utils.getRawType(parameterType) == Continuation.class) {
              isKotlinSuspendFunction = true;
              return null;
            }
          } catch (NoClassDefFoundError ignored) {
          }
        }
        throw parameterError(method, p, "No Retrofit annotation found.");
      }

      return result;
    }

第一點沒什麼好說的,裏面沒什麼邏輯,就是一個純註解解析與Converter的選取。ide

第二點是關鍵點,用來判斷該方法的調用是否使用到了協程。同時有個allowContinuation參數,這個是什麼呢?咱們向上看,發現它是方法中的一個參數,若是咱們繼續追溯就會發現它就是咱們以前特地須要注意的p == lastParameter

因此判斷是不是使用了協程有三步:

  1. result爲空,即該參數沒有註解
  2. allowContinuationtrue,便是最後一個參數
  3. Continuation.class,說明該參數的類型爲Continuation

只有符合上述三點才能證實使用了協程,但腦海裏回想一下協程的寫法,發現徹底對不到這三點...

到這裏可能有的讀者已經開始蒙圈了,若是你沒有深刻了解協程的話,這個是正常的狀態。

別急,要理解這塊,還須要一點協程的原理知識,下面我來簡單說一下協程的部分實現原理。

suspend原理

咱們先來看下使用協程是怎麼寫的:

@GET("/v2/news")
suspend fun newsGet(@QueryMap params: Map<String, String>): NewsResponse

這是一個標準的協程寫法,而後咱們再套用上面的條件,發現徹底匹配不到。

由於,這是不協程的原本面目。咱們思考一個問題,爲何使用協程要添加suspend關鍵字呢?這是重點。你能夠多想幾分鐘。

(幾分鐘以後...)

不弔你們胃口了,我這裏就直接說結論。

由於在代碼編譯的過程當中會自動爲帶有suspend的函數添加一個Continuation類型的參數,並將其添加到最後面。因此上面的協程真正的面目是這樣的:

@GET("/v2/news")
fun newsGet(@QueryMap params: Map<String, String>, c: Continuation<NewsResponse>): NewsResponse

如今咱們再來看上面的條件,發現可以所有符合了。

因爲篇幅有限,有關協程的原理實現就點到爲止,後續我會專門寫一個協程系列,但願到時可以讓讀者們認識到協程的真面目,你們能夠期待一下。

如今咱們已經知道了Retrofit如何判斷一個方法是否使用了協程。那麼咱們再進入另外一個點:

Retrofit如何將Call直接轉化爲NewResonse,簡單的說就是支持使newsGet方法返回NewsResponse。而這一步的轉化在HttpServiceMethod中。

HttpServiceMethod

上面已經分析完RequestFactoryparseAnnotations(),如今再來看下HttpServiceMethod中的parseAnnotations()

static <ResponseT, ReturnT> HttpServiceMethod<ResponseT, ReturnT> parseAnnotations(
      Retrofit retrofit, Method method, RequestFactory requestFactory) {
    boolean isKotlinSuspendFunction = requestFactory.isKotlinSuspendFunction;
    boolean continuationWantsResponse = false;
    boolean continuationBodyNullable = false;

    Annotation[] annotations = method.getAnnotations();
    Type adapterType;
    // 1. 是協程
    if (isKotlinSuspendFunction) {
      Type[] parameterTypes = method.getGenericParameterTypes();
      Type responseType = Utils.getParameterLowerBound(0,
          (ParameterizedType) parameterTypes[parameterTypes.length - 1]);
      // 2. 判斷接口方法返回的類型是不是Response
      if (getRawType(responseType) == Response.class && responseType instanceof ParameterizedType) {
        // Unwrap the actual body type from Response<T>.
        responseType = Utils.getParameterUpperBound(0, (ParameterizedType) responseType);
        continuationWantsResponse = true;
      } else {
        // TODO figure out if type is nullable or not
        // Metadata metadata = method.getDeclaringClass().getAnnotation(Metadata.class)
        // Find the entry for method
        // Determine if return type is nullable or not
      }

      // 3. 注意:將方法返回類型假裝成Call類型,並將SkipCallbackExecutor註解添加到annotations中
      adapterType = new Utils.ParameterizedTypeImpl(null, Call.class, responseType);
      annotations = SkipCallbackExecutorImpl.ensurePresent(annotations);
    } else {
      adapterType = method.getGenericReturnType();
    }

    // 4. 建立CallAdapter,適配call,將其轉化成須要的類型
    CallAdapter<ResponseT, ReturnT> callAdapter =
        createCallAdapter(retrofit, method, adapterType, annotations);
    Type responseType = callAdapter.responseType();
    // 5. 建立Converter,將響應的數據轉化成對應的model類型
    Converter<ResponseBody, ResponseT> responseConverter =
        createResponseConverter(retrofit, method, responseType);

    okhttp3.Call.Factory callFactory = retrofit.callFactory;
    // 6. 接口方法不是協程
    if (!isKotlinSuspendFunction) {
      return new CallAdapted<>(requestFactory, callFactory, responseConverter, callAdapter);
    } else if (continuationWantsResponse) {
      // 7. 接口方法是協程,同時返回類型是Response類型
      //noinspection unchecked Kotlin compiler guarantees ReturnT to be Object.
      return (HttpServiceMethod<ResponseT, ReturnT>) new SuspendForResponse<>(requestFactory,
          callFactory, responseConverter, (CallAdapter<ResponseT, Call<ResponseT>>) callAdapter);
    } else {
      // 8. 接口方法是協程,同時返回類型是body,即自定義的model類型
      //noinspection unchecked Kotlin compiler guarantees ReturnT to be Object.
      return (HttpServiceMethod<ResponseT, ReturnT>) new SuspendForBody<>(requestFactory,
          callFactory, responseConverter, (CallAdapter<ResponseT, Call<ResponseT>>) callAdapter,
          continuationBodyNullable);
    }
  }

代碼中已經解析的很清楚了,須要注意3,若是是協程會作兩步操做,首先將接口方法的返回類型假裝成Call類型,而後再將SkipCallbackExecutor手動添加到annotations中。字面意思就在後續調用callAdapter.adapt(call)時,跳過建立Executor,簡單理解就是協程不須要Executor來切換線程的。爲何這樣?這一點先放這裏,後續建立Call的時候再說。

咱們直接看協程的7,8部分。7也不詳細分析,簡單提一下,它就是返回一個Response<T>的類型,這個Retrofit最基本的支持了。至於如何在使用協程時將Call<T>轉化成Response<T>原理與8基本相同,只是比8少一步,將它的body轉化成對應的返回類型model。因此下面咱們直接看8。

將Call轉化成對應的Model

static final class SuspendForBody<ResponseT> extends HttpServiceMethod<ResponseT, Object> {
    private final CallAdapter<ResponseT, Call<ResponseT>> callAdapter;
    private final boolean isNullable;

    SuspendForBody(RequestFactory requestFactory, okhttp3.Call.Factory callFactory,
        Converter<ResponseBody, ResponseT> responseConverter,
        CallAdapter<ResponseT, Call<ResponseT>> callAdapter, boolean isNullable) {
      super(requestFactory, callFactory, responseConverter);
      this.callAdapter = callAdapter;
      this.isNullable = isNullable;
    }

    @Override protected Object adapt(Call<ResponseT> call, Object[] args) {
      // 1. 獲取適配的Call
      call = callAdapter.adapt(call);

      //noinspection unchecked Checked by reflection inside RequestFactory.
      // 2. 獲取協程的Continuation
      Continuation<ResponseT> continuation = (Continuation<ResponseT>) args[args.length - 1];
      return isNullable
          ? KotlinExtensions.awaitNullable(call, continuation)
          : KotlinExtensions.await(call, continuation);
    }
  }

咱們的關注點在adapt,文章開頭已經說了,新版的Retrofitadapt隱藏到invoke中。而invoke中調用的就是這個adapt

首先第一步,適配Call,若是是RxJava,這裏的callAdapter就是RxJava2CallAdapter,同時返回的就是Observable,這個以前看過源碼的都知道。

但如今是協程,那麼這個時候的callAdapter就是Retrofit默認的DefaultCallAdapterFactory

@Override public @Nullable CallAdapter<?, ?> get(
      Type returnType, Annotation[] annotations, Retrofit retrofit) {
    // 1. 注意: 若是是協程,由於接口方法返回沒有使用Call,以前3的第一步假裝成Call的處理就在這裏體現了做用
    if (getRawType(returnType) != Call.class) {
      return null;
    }
    if (!(returnType instanceof ParameterizedType)) {
      throw new IllegalArgumentException(
          "Call return type must be parameterized as Call<Foo> or Call<? extends Foo>");
    }
    final Type responseType = Utils.getParameterUpperBound(0, (ParameterizedType) returnType);

    // 2. 以前3的第二部就在這裏體現,因爲以前已經將SkipCallbackExecutor註解添加到annotations中,因此Executor直接爲null
    final Executor executor = Utils.isAnnotationPresent(annotations, SkipCallbackExecutor.class)
        ? null
        : callbackExecutor;

    return new CallAdapter<Object, Call<?>>() {
      @Override public Type responseType() {
        return responseType;
      }

      @Override public Call<Object> adapt(Call<Object> call) {
        // 3. 最終調用adapt時候返回的就是它自己的Call,即不須要進行適配。
        return executor == null
            ? call
            : new ExecutorCallbackCall<>(executor, call);
      }
    };
  }

代碼註釋已經將以前3的做用解釋完了,咱們回到SuspendForBodyadpt,再看第二步。熟悉的一幕,又用到了最後的一個參數。這裏的isNullable目前Retrofit的版本都是false,可能後續會支持空類型。但如今確定是不支持的,因此直接進入KotlinExtensions.await()

suspend fun <T : Any> Call<T>.await(): T {
  return suspendCancellableCoroutine { continuation ->
    continuation.invokeOnCancellation {
      cancel()
    }
    enqueue(object : Callback<T> {
      override fun onResponse(call: Call<T>, response: Response<T>) {
        if (response.isSuccessful) {
          // 1. 拿到body
          val body = response.body()
          if (body == null) {
            val invocation = call.request().tag(Invocation::class.java)!!
            val method = invocation.method()
            val e = KotlinNullPointerException("Response from " +
                method.declaringClass.name +
                '.' +
                method.name +
                " was null but response body type was declared as non-null")
            // 2. body爲空,喚起協程,拋出異常
            continuation.resumeWithException(e)
          } else {
            // 3. 喚起協程,返回body
            continuation.resume(body)
          }
        } else {
          // 4. 喚起協程,拋出異常
          continuation.resumeWithException(HttpException(response))
        }
      }

      override fun onFailure(call: Call<T>, t: Throwable) {
        // 5. 喚起協程,拋出異常
        continuation.resumeWithException(t)
      }
    })
  }
}

看到這段代碼,可能有的讀者很熟悉,已經明白了它的轉化。由於在Retrofit以前的幾個版本,若是使用協程是不支持接口方法直接返回model的,須要返回Call<T>類型的數據。因此當時有的開源項目就是經過這個相似的extensions方法來轉化成對應的model

遺憾的是,就是使用了RetrofitVersion 2.6.0以後的版本,我仍是看到有的人使用這一套來本身轉化,但願看到這篇文章的讀者不要再作重複的事情,將其交給Retrofit自身來作就能夠了。

上面的extensions做用就一個,經過suspendCancellableCoroutine來建立一個協程,它是協程中幾個重要的建立協程的方法之一,這裏就不細說,後續開協程系列在詳細說明。

主要是onResponse回調,協程經過掛起來執行耗時任務,而成功與失敗會分別經過resume()resumeWithExecption()來喚起掛起的協程,讓它返回以前的掛起點,進行執行。而resumeWithExecption()內部也是調用了resume(),因此協程的喚起都是經過resume()來操做的。調用resume()以後,咱們能夠在調用協程的地方返回請求的結果。那麼一個完美的協程接口調用就是這樣實現的。

嗯,結束了,整理一下也不是很複雜吧。以後使用Retroift寫協程時將通暢多了。

今天就這樣吧,協程部分就分析到這裏,Retrofit的整個協程實現部分就分析結束了,我將關鍵點都特別進行了標註與說明,但願對分析Retrofit的協程實現有所幫助。

最後,感謝你的閱讀,若是你有時間的話,推薦帶着這篇文章再去閱讀一下源碼,你將會有更深入的印象。

項目

android_startup: 提供一種在應用啓動時可以更加簡單、高效的方式來初始化組件,優化啓動速度。不只支持Jetpack App Startup的所有功能,還提供額外的同步與異步等待、線程控制與多進程支持等功能。

AwesomeGithub: 基於Github客戶端,純練習項目,支持組件化開發,支持帳戶密碼與認證登錄。使用Kotlin語言進行開發,項目架構是基於Jetpack&DataBindingMVVM;項目中使用了ArouterRetrofitCoroutineGlideDaggerHilt等流行開源技術。

flutter_github: 基於Flutter的跨平臺版本Github客戶端,與AwesomeGithub相對應。

android-api-analysis: 結合詳細的Demo來全面解析Android相關的知識點, 幫助讀者可以更快的掌握與理解所闡述的要點。

daily_algorithm: 每日一算法,由淺入深,歡迎加入一塊兒共勉。

爲本身代言

微信搜索公衆號:【Android補給站】或者掃描下方二維碼進行關注

相關文章
相關標籤/搜索