美文网首页Android补给站JetPack
重温Retrofit源码,笑看协程实现

重温Retrofit源码,笑看协程实现

作者: 午后一小憩 | 来源:发表于2020-09-22 20:38 被阅读0次

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

    温馨提示,如果有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: 每日一算法,由浅入深,欢迎加入一起共勉。

    相关文章

      网友评论

        本文标题:重温Retrofit源码,笑看协程实现

        本文链接:https://www.haomeiwen.com/subject/etdgyktx.html