关于android:重温Retrofit源码笑看协程实现

47次阅读

共计 11349 个字符,预计需要花费 29 分钟才能阅读完成。

最近回归看了一下 Retrofit 的源码,次要是因为我的项目接入了协程,所以想钻研一下 Retorift 是如何反对协程的。Retrofit是在 Version 2.6.0 开始反对协程的,所以本篇文章无关 Retrofit 的源码都是基于 2.6.0 的。

舒适提醒,如果有 Retrofit 的源码浏览教训,浏览这篇文章将会轻松很多。

<!– 释怀你没有进错房间,这不是剖析协程的文章,只是刚好谈到协程,所以还是简略说下 Retrofit 的实现。
不感兴趣的能够间接跳过上面的小插曲,释怀不影响后续的浏览。–>

Retrofit

置信老鸟都应该很分明,Retrofit外围局部是 create() 办法返回的动静代理(这里就不具体阐明了,之后会有专门的文章剖析动静代理)。就是上面这段代码:

  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 办法中。

相干的 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,用来保留解析进去的办法信息。

    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 的选取。

第二点是关键点,用来判断该办法的调用是否应用到了协程。同时有个 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 补给站】或者扫描下方二维码进行关注

正文完
 0