Retrofit 源码深入分析 — RxJava 和 协程的支持

一、概述

在上一篇 Retrofit 源码深入分析 —— Call 对象的诞生与请求 的文章中我们基本把 Retrofit 从如何构建一个请求到返回响应的整个过程都梳理了一遍,对 Retrofit 的基本工作原理有了一个完整的了解。按照文章的完成度来说,上一篇文章基本把 Retrofit 讲的差不多了,但笔者还是想把日常普遍使用的几种方式都梳理一遍,让两篇文章对 Retrofit 的分析更加完整。

本篇文章其实按理来说应该整合到上一篇中,但这样让本就有点长的文章变得更长,对于阅读来说可能会很累,而对于笔者来说无论是写还是校对也很累,索性单开一篇。而且也并不会妨碍彼此的连贯性。

二、Retrofit 对 RxJava 的支持

让我们看看如何用 RxJava 的方式进行请求,还是用官方 sample 的例子

  • 添加对 RxJava 的支持
addCallAdapterFactory(RxJavaCallAdapterFactory.create())
  • 声明一个返回类型为 Obervable 的接口方法
public interface GitHub {
    @GET("/repos/{owner}/{repo}/contributors")
    Observable<List<Contributor>> contributors(@Path("owner") String owner,@Path("repo") String repo);
}
  • 创建接口服务
GitHub github = retrofit.create(GitHub.class);
  • 发起一个请求
 Observable<List<Contributor>> observable = github.contributors("square", "retrofit");
 
  observable.
    .subscribeOn(Schedulers.io())
    .observeOn(AndroidSchedulers.mainThread())
    .subscribe(new Observer<List<Contributor>>() {
            @Override
            public void onCompleted() {}

            @Override
            public void onError(Throwable throwable) { }

            @Override
            public void onNext(List<Contributor> contributors) {
                for (Contributor contributor : contributors) {
                    System.out.println(contributor.login + " (" + contributor.contributions + ")");
                }
            }
    });

上述是一个标准的支持 RxJava 的请求步骤,这里与默认的请求最大的区别除了请求的过程不同外,返回的类型由原来的 Call.class 类型变为了 Observable.class 类型也就是一个被观察者对象,所以很明显 RxJavaCallAdapterFactory 内部帮我门做了某种转换,至于注解的解析过程都是一样的,这里不在赘述。

让我们再次延续上篇文章的 6.1 小节部分。回到创建 CallAdapter 的地方,也就是 createCallAdapter 方法,上篇文章对这个方法已经进行了描述,所以废话不多说让我们直接进入 RxJavaCallAdapterFactory 的 get 方法

@Override public @Nullable CallAdapter<?, ?> get(
      Type returnType, Annotation[] annotations, Retrofit retrofit) {
    Class<?> rawType = getRawType(returnType);
    boolean isSingle = rawType == Single.class;
    boolean isCompletable = rawType == Completable.class;
    if (rawType != Observable.class && !isSingle && !isCompletable) {
      return null;
    }

    if (isCompletable) {
      return new RxJavaCallAdapter(Void.class, scheduler, isAsync, false, true, false, true);
    }

    boolean isResult = false;
    boolean isBody = false;
    Type responseType;
    
    Type observableType = getParameterUpperBound(0, (ParameterizedType) returnType);
    Class<?> rawObservableType = getRawType(observableType);
    //Model 不可声明为 Retrofit 的Response 类型
    if (rawObservableType == Response.class) {
      if (!(observableType instanceof ParameterizedType)) {
        throw new IllegalStateException("Response must be parameterized"
            + " as Response<Foo> or Response<? extends Foo>");
      }
      responseType = getParameterUpperBound(0, (ParameterizedType) observableType);
    } else if (rawObservableType == Result.class) { //也不可声明为 RxJava 包下的 Result 类型
      if (!(observableType instanceof ParameterizedType)) {
        throw new IllegalStateException("Result must be parameterized"
            + " as Result<Foo> or Result<? extends Foo>");
      }
      responseType = getParameterUpperBound(0, (ParameterizedType) observableType);
      isResult = true;
    } else {
      responseType = observableType;
      isBody = true;
    }

    return new RxJavaCallAdapter(responseType, scheduler, isAsync, isResult, isBody, isSingle,
        false);

RxJavaCallAdapterFactory 的 get 方法的逻辑还是很清晰的,首先创建 RxJavaCallAdapter 的前置条件必须为 Observable、Single 和 Completable 而如果是 Completable 只接创建一个返回类型为 Void 的 adapter, 至于 Single 和 Completable ,前者在 RxJava 中代表只能处理一次事件,即只能发射单个数据或错误事件。而 Completable 正如它的名字,它不负责发送数据,只会处理 Rxjava 的 conComplete 和 onError 事件。

RxJavaCallAdapter 创建完成后就和 Call 对象的诞生流程差不多了。所以让们直接进入其中的 adapt 方法看看 Observable 的真面目

//形参为 OkHttpCall 创建的过程请看上一篇文章
@Override public Object adapt(Call<R> call) {
    OnSubscribe<Response<R>> callFunc = isAsync
        ? new CallEnqueueOnSubscribe<>(call)//异步,将 OkHttpCall  对象传入
        : new CallExecuteOnSubscribe<>(call);//同步,将 OkHttpCall 对象传入

    OnSubscribe<?> func;
    if (isResult) {//声明类型不是的 RxJava 包下的 Result 类型 默认 false
      func = new ResultOnSubscribe<>(callFunc);
    } else if (isBody) { //默认 true
      func = new BodyOnSubscribe<>(callFunc);
    } else {
      func = callFunc;
    }
    //创建一个被观察对象
    Observable<?> observable = Observable.create(func);

    if (scheduler != null) {
      observable = observable.subscribeOn(scheduler);
    }

    if (isSingle) {//返回一个 Single 事件
      return observable.toSingle();
    }
    if (isCompletable) {//返回一个 toCompletable 事件
      return observable.toCompletable();
    }
    return observable;
  }

注释已经进行了详细的解释就不多说了,由于我们使用的是异步请求,所以肯定创建的是 CallEnqueueOnSubscribe 对象,该对象实现了 OnSubscribe 接口并重写了 call 方法,代码如下

@Override public void call(Subscriber<? super Response<T>> subscriber) {
    // Since Call is a one-shot type, clone it for each new subscriber.
    Call<T> call = originalCall.clone();
    final CallArbiter<T> arbiter = new CallArbiter<>(call, subscriber);
    subscriber.add(arbiter);
    subscriber.setProducer(arbiter);

    call.enqueue(new Callback<T>() {
      @Override public void onResponse(Call<T> call, Response<T> response) {
        arbiter.emitResponse(response);
      }

      @Override public void onFailure(Call<T> call, Throwable t) {
        Exceptions.throwIfFatal(t);
        arbiter.emitError(t);
      }
    });
  }

代码并不复杂,可以看到内部还是委托 OkHttpCall 的 enqueue 方法来获取请求的 response ,在经过 GsonConvertAdapter 转换后,将该 Response 发射出去。那么到这里整个 RxJava 的支持过程就清晰了。

首先当我们把接口的返回类型声明为 Observable 类型时,会通过 RxJavaCallAdapterFactory 的 get 方法来帮我们创建一个 RxJavaCallAdapter 对象,RxJavaCallAdapter 的 adapt 方法中会帮我们创建一个被观察者对象返回。而当我们通过如下代码发起一个订阅

observable.
    .subscribeOn(Schedulers.io())
    .observeOn(AndroidSchedulers.mainThread())
    .subscribe(new Observer());

订阅完成后,CallEnqueueOnSubscribe 对象的 call 方法就会被触发,剩下的就是在 onNext 方法中接收 Resposne 了。

从整体的处理环节来看,除了 RxJava 本身的特性之外,基本的逻辑和默认的 Call 类型的处理是 差不多的。因为都是遵循的同一套接口标准。所以如果你不喜欢默认的类型或 Rxjava,完全可以按照 Retorfit 的标准建立一套自己的 callAdapter。

三、Retrofit 对协程的支持

协程作为近几年很火的异步框架,其简便的异步操作方式可以说但凡用过的人没有不喜欢的,其热度在 Android 领域已经有超过 RxJava 的趋势。而 Retrofit 自然不会甘于人后,在 2.6.x 版本以上也对协程进行了支持。

如果你还不了解协程,建议先 Google/Baidu 查询一下相关文章了解一番,否则可能无法无法愉快的阅读。

接下来让还是让我们以官方 Sample 为例看看 Retrofit 在 Android 开发中使用协程的基本流程(下面的步骤是 Kotlin 代码)

  • 添加基础依赖

implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation 'com.squareup.retrofit2:converter-gson:2.5.0'
//Android 协程依赖库
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.1'
// 包含协程的 Activity lifecycle 扩展
implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.3.0"
  • 构建 Retrofit 实例
val API_URL = "https://api.github.com"

val retrofit = Retrofit.Builder()
                .baseUrl(API_URL)
                .addConverterFactory(GsonConverterFactory.create())
                .build()
  • 创建挂起函数
interface Github {
    @GET("/repos/{owner}/{repo}/contributors")
    suspend fun contributors(
        @Path("owner") owner: String,
        @Path("repo") repo: String) : List<Contributor>
}
  • 创建接口服务
val github = retrofit.create(Github::class.java)
  • 开启一个协程进行请求
lifecycleScope.launch{
    val contributors = github.contributors("square", "retrofit"for ((login, contributions) in contributors) {
        println("$login ($contributions)")
    }
}

上述流程算是在 Android 中的常用步骤,但在实际使用中 lifecycleScope.launch 会单独封装不会像上面那样单独拿出来使用。

简单了解了上面的流程后,我们来看看 Retrofit 是如何对携程进行支持的。

3.1、Retrofit 是如何知道我们用的是协程?

要想知道这些让我们回到创建 RequestFactory 的地方(上一篇文章第五节)

     
RequestFactory build() {
      //解析接口方法的注解,列如 @GET @POST @Headers 的值等等...
      for (Annotation annotation : methodAnnotations) {
        parseMethodAnnotation(annotation);
      }

      //省略一些判断代码...

      //解析形参的注解并获取注解对应的 value
      int parameterCount = parameterAnnotationsArray.length;
      parameterHandlers = new ParameterHandler<?>[parameterCount];
      for (int p = 0, lastParameter = parameterCount - 1; p < parameterCount; p++) {
        parameterHandlers[p] =
            parseParameter(p, parameterTypes[p], parameterAnnotationsArray[p], p == lastParameter);
      }

      //省略一些判断代码...

      return new RequestFactory(this);
}

这里我们重点要关注的是解析形参的部分也就是 parseParameter 方法,该方法的完整签名如下

/*
* @p 传入的循环索引
* @parameterType 参数类型 例如 String Int
* @annotations 参数注解数组 例如 @Path @Query
* @allowContinuation 是否使用了协程
*/
 private @Nullable ParameterHandler<?> parseParameter(
        int p, Type parameterType, @Nullable Annotation[] annotations, boolean allowContinuation) {

了解了上面的内容,我们现在再看那个循环解析参数注解的逻辑,如果对协程不了解的话阅读这段代码的时候你可能会很疑惑,为什么当索引 P == lastparameter 的时候就可以允许支持协程了, 而且还有一个重要的布尔字段那就是 isKotlinSuspendFunction 的改为 true 的过程,可能看了也会让你迷惑,让我们进入 parseParameter 方法看看关键代码

 private @Nullable ParameterHandler<?> parseParameter(
        int p, Type parameterType, @Nullable Annotation[] annotations, boolean allowContinuation) {
      ParameterHandler<?> result = null;
      //省略参数解析代码...

      if (result == null) {
        if (allowContinuation) { //当 allowContinuation 为 true 也就是最后一个参数的索引和注解数组长度相等
          try {
            //最后一个参数类型为 Continuation.class 类型 isKotlinSuspendFunction 为true
            if (Utils.getRawType(parameterType) == Continuation.class) {
              isKotlinSuspendFunction = true;
              return null;
            }
          } catch (NoClassDefFoundError ignored) {
          }
        }
        throw parameterError(method, p, "No Retrofit annotation found.");
      }

      return result;
    }

可以看到表面逻辑倒也没什么难以理解的,就是判断参数是否为最后一个并判断最后一个参数是否为 Continuation.class ,奇怪了,我们并未声明 Continuation.class 类型的参数,它判断个什么?要想解答这些困惑,我们需要对 suspend 函数进行一下反编译来看看它在 java 中是个什么样子

//kotlin 代码
 @GET("/repos/{owner}/{repo}/contributors")
suspend fun contributors(
        @Path("owner") owner: String,
        @Path("repo") repo: String) : List<Contributor>

//反编译后的 Java 代码
@GET("/repos/{owner}/{repo}/contributors")
@Nullable
Object contributors(
@Path("owner") @NotNull String var1, 
@Path("repo") @NotNull String var2,
@NotNull Continuation var3);

看了上面的反编译代码是不是有点恍然大明白的感觉,原来 suspend 转换为 java 代码后返回的类型变为了最原始的 Object ,同时形参自动增加了一个 Continuation.class 类型的参数,至此也就明白了为什么要判断最后一个参数为 Continuation.class 类型才能把 isKotlinSuspendFunction 字段改为 true 的处理逻辑。

3.2、Retrofit 对协程的处理

上篇文章中笔者故意将协程相关代码隐藏,主要是为了专注非协程情况下的源码分析,而本节将会重点展示协程相关代码,以梳理 Retrofit 对协程的支持过程

上一节中我们已经明白了 isKotlinSuspendFunction 字段的处理逻辑,现在让我们把视线转到 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;
    //是否为 Suspend 挂起函数,也就是 Kotlin 的协程
    if (isKotlinSuspendFunction) {
      //获取所有参数类型
      Type[] parameterTypes = method.getGenericParameterTypes();
      //获取参数类型的下边界
      Type responseType = Utils.getParameterLowerBound(0,
          (ParameterizedType) parameterTypes[parameterTypes.length - 1]);
      //获取类型全限定类名并判断是否为 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; //协程返回类型为 Response<T> 类型标记位 true 
      } else {
        //省略...
      }

      //获取要适配的 call 类型 ,其实就是创建了一个 ParameterizedType 的实例,类型为 call
      adapterType = new Utils.ParameterizedTypeImpl(null, Call.class, responseType);
      //这里调用了一个实现了 SkipCallbackExecutor 注解的类,
      //ensurePresent 方法主要作用是将原注解数组替换为 SkipCallbackExecutor 注解
      annotations = SkipCallbackExecutorImpl.ensurePresent(annotations);
    } else {
      //获取要适配的 call 类型
      adapterType = method.getGenericReturnType();
    }

    //省略若干代码...

    //获取 OkHttpClient 实例
    okhttp3.Call.Factory callFactory = retrofit.callFactory;
    if (!isKotlinSuspendFunction) {
      return new CallAdapted<>(requestFactory, callFactory, responseConverter, callAdapter);
    } else if (continuationWantsResponse) {
      //返回的是 Response<T> 类型
      return (HttpServiceMethod<ResponseT, ReturnT>) new SuspendForResponse<>(requestFactory,
          callFactory, responseConverter, (CallAdapter<ResponseT, Call<ResponseT>>) callAdapter);
    } else {
      //返回的是 Body 
      return (HttpServiceMethod<ResponseT, ReturnT>) new SuspendForBody<>(requestFactory,
          callFactory, responseConverter, (CallAdapter<ResponseT, Call<ResponseT>>) callAdapter,
          continuationBodyNullable);
    }
  }

注释解释的很清楚了,这里我们重点关注一下 SkipCallbackExecutorImpl.ensurePresent() 方法,代码如下

 static Annotation[] ensurePresent(Annotation[] annotations) {
    if (Utils.isAnnotationPresent(annotations, SkipCallbackExecutor.class)) {
      return annotations;
    }
    
    //创建一个新的注解数组
    Annotation[] newAnnotations = new Annotation[annotations.length + 1];
    // 利用系统深拷贝将原数组注解类型替换为 SkipCallbackExecutor 注解
    newAnnotations[0] = SkipCallbackExecutorImpl.INSTANCE;
    System.arraycopy(annotations, 0, newAnnotations, 1, annotations.length);
    return newAnnotations;
  }

看完上面的代码你可能会觉得这个 SkipCallbackExecutor 似曾相识,还记得 DefaultCallAdapterFactory 的 get 方法吗

@Override 
public @Nullable CallAdapter<?, ?> get(
      Type returnType, Annotation[] annotations, Retrofit retrofit) {
  
    //省略若干代码...
  
    //这里其实主要的作用是为了判断是否使用了协程,如果实现了协程那么则不使用系统的回调线程并返回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) {
        return executor == null
            ? call // OkHttpCall 对象
            : new ExecutorCallbackCall<>(executor, call);
      }
    };
  }

如此 SkipCallbackExecutor 注解的作用就清晰了,首先当为 Suspend 函数时,Retrofit 会将原注解进行深拷贝变为 SkipCallbackExecutor 注解类型,然后会在创建 Call 对象的时候判断,如果符合条件则不用默认的 Executor , 直接通过 OkHttpCall 进行 async / enqueue 请求。

接下来就是协程和非协程返回对象的区别,从上面的代码中我们可以很清晰的看到,对于协程返回有两种Response 类型,一种为自定义的 Model , 一种为 responseBody 类型,分别对应 SuspendForResponse 和 SuspendForBody 对象。而两者和非协程环境下的 CallAdapted 对象相同,都是 HttpServiceMethod 的子类,所以无论哪种环境,抛开其它因素,两者的调用过程都是相同的,即先调用 invoke 方法创建 OkHttpCall 对象,在调用 adapt 方法进行具体的请求,SuspendForResponse 类的 adapt 方法如下

/*
*  @call 是 OkHttpCall 对象
*  @args 是我们请求的参数 就是 github.contributors("square", "retrofit")
*/
 @Override 
 protected Object adapt(Call<ResponseT> call, Object[] args) {
      call = callAdapter.adapt(call);

      //获取参数列表中的最后一个 continuation 类型参数 ,详看上面 suspend 反编译成 Java 代码
      Continuation<Response<ResponseT>> continuation =
          (Continuation<Response<ResponseT>>) args[args.length - 1];

      // 调用 Kotlin 扩展函数
      try {
        return KotlinExtensions.awaitResponse(call, continuation);
      } catch (Exception e) {
        return KotlinExtensions.suspendAndThrow(e, continuation);
      }
}

在 Retrofit 源码中有一个 KotlinExtensions.kt 文件,是一个 Kotlin 的扩展文件,专门用来处理 suspend 函数,我们进入 awaitResponse 函数看看

suspend fun <T : Any> Call<T>.awaitResponse(): Response<T> {
  return suspendCancellableCoroutine { continuation ->
    continuation.invokeOnCancellation {
      cancel()
    }
    //调用 OkHttpCall 的 enqueue 方法获取转换后的响应(和上篇文章中的过程是一样的)
    enqueue(object : Callback<T> {
      override fun onResponse(call: Call<T>, response: Response<T>) {
        //唤醒挂起函数,返回 response
        //表现形式看开头通过协程获取 model 的代码
        continuation.resume(response)
      }

      override fun onFailure(call: Call<T>, t: Throwable) {
        //唤醒挂起函数,抛出异常
        continuation.resumeWithException(t)
      }
    })
  }
}

注释解释的很清楚了,其中 suspendCancellableCoroutine 是创建协程的方式之一,感兴趣的可以了解下。至于 SuspendForBody 对象,它和 SuspendForResponse 的处理过程是一样的,这里就不多说了。

所以回顾整个流程对于 suspend 函数的处理步骤如下

  • 如果形参列表的最后一个参数是为 Continuation.class 类型,则 isKotlinSuspendFunction 赋值为 true
  • 获取 suspend 函数返回类型,如果为 Response 类型,continuationWantsResponse = true
  • 获取接口方法所有注解,深拷贝替换为 SkipCallbackExecutor 类型,并以此为条件决定采用默认的 Excutor 还是 直接使用 OkHttpCall
  • 根据 continuationWantsResponse 判断返回 SuspendForResponse 还是 SuspendForBody 对象
  • 在 SuspendForResponse / SuspendForBody 的 adapt 方法中调用 KotlinExtensions.awaitResponse / awaitNullable 方法获取 response 并唤起协程返回数据

如果有些地方还是不明白可以跟着笔者的步骤跟进源码一步步看,这样会更清晰。

四、结语

终于,用了两篇文章将 Retrofit 分析完了,没有拉下任何一个常用的关键步骤,可以说是非常全面的分析了。而对于自己来说也有种难言的收获感,文章很长,代码也是反复看了很久很久,而过程中的抓耳挠腮,反复 debug, 在理解完最后一行源码的时候,顿时有一种通透的舒爽感,久久不能平静,这里也吐槽一下自己,其实关于 Retrofit 的文章在前几年就该写出来,但一直拖到了现在,再次感到自己是真的懒,以后尽量勤快点吧!

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值