Retrofit是如何支持Kotlin协程的?

(众所周知)Retrofit从2.6.0开始支持协程。

    implementation 'com.squareup.retrofit2:retrofit:2.9.0'
    implementation 'com.squareup.retrofit2:converter-gson:2.9.0'

使用

1.定义retrofit。

 val retrofit: Retrofit = Retrofit.Builder()
    .addConverterFactory(GsonConverterFactory.create())
    .client(OkHttpClient.Builder().build())
    .baseUrl("")
    .build()

2.定义请求接口方法。

    //示例接口
    @GET("api/test")
    suspend fun testApi(): MyResponse

3.然后在协程作用域中调用testApi()方法,可以成功获取结果。但是,如果testApi()方法没有使用suspend关键字修饰,则会报错:

 Unable to create call adapter for com.package.name.net.MyResponse
        for method ServiceApi.testApi

4.从错误信息可以知道,请求是成功发起并返回了的,只是在转换结果对象的时候报错了。由此可以提出问题。

一 . 提出问题

为什么suspend关键字会影响返回结果bean的转换?既是:Retrofit是如何支持Kotlin协程的?

二 . 源码追溯

1.全局搜索“Unable to create call adapter for”,找到:

  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);
    }
  }

再追溯“createCallAdapter”找到:

 static  HttpServiceMethod parseAnnotations(
      Retrofit retrofit, Method method, RequestFactory requestFactory) {
    boolean isKotlinSuspendFunction = requestFactory.isKotlinSuspendFunction;
    ......

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

    //从这里报的错误
    CallAdapter callAdapter =
        createCallAdapter(retrofit, method, adapterType, annotations);

   ......
    if (!isKotlinSuspendFunction) {
      //如果不是suspend函数,则返回类型需要是retrofit2.Call。
      //代码走到这里就结束了。retrofit2并没有为我们发起网络请求。
      return new CallAdapted<>(requestFactory, callFactory, responseConverter, callAdapter);
    }else if (continuationWantsResponse) {
      //是suspend函数的处理,retrofit2执行了网络请求并返回了结果
      //返回完整的response
      return (HttpServiceMethod)
          new SuspendForResponse<>(
              requestFactory,
              callFactory,
              responseConverter,
              (CallAdapter>) callAdapter);
    } else {
      //是suspend函数的处理,retrofit2执行了网络请求并返回了结果
      //返回response里面的body部分
      ......
    }
  }

1.判断是否为suspend函数;
2.检查不同情况下,参数类型和返回类型是否正确;
3.retrun 结果;

判断是否为挂起函数:

  private @Nullable ParameterHandler parseParameter(
        int p, Type parameterType, @Nullable Annotation[] annotations, boolean allowContinuation) {
      ......
    //获取接口方法参数最顶层的type类型。
    if (Utils.getRawType(parameterType) == Continuation.class) {
              isKotlinSuspendFunction = true;
              return null;
    }
     ......
    }

这里的判断依据,跟suspend关键字有关。
使用studio自带的kotlin工具反编译查看java代码可以发现:
   @GET("api/test")
   @Nullable
   Object testApi(@Query("test") @test Integer var1, @NotNull Continuation var2);
用suspend关键字修饰的方法,在参数里面自动添加了Continuation参数。
所以判断方法是否有一个Continuation参数就可以判断是否用suspend修饰了。
Continuation参数可以理解为一个回调。

retrofit2帮我们发起网络请求:

  static final class SuspendForResponse extends HttpServiceMethod {
    private final CallAdapter> callAdapter;

    SuspendForResponse(
        RequestFactory requestFactory,
        okhttp3.Call.Factory callFactory,
        Converter responseConverter,
        CallAdapter> callAdapter) {
      super(requestFactory, callFactory, responseConverter);
      this.callAdapter = callAdapter;
    }

    @Override
    protected Object adapt(Call call, Object[] args) {
      call = callAdapter.adapt(call);

      //noinspection unchecked Checked by reflection inside RequestFactory.
      Continuation> continuation =
          (Continuation>) args[args.length - 1];

      // See SuspendForBody for explanation about this try/catch.
      try {
        return KotlinExtensions.awaitResponse(call, continuation);
      } catch (Exception e) {
        return KotlinExtensions.suspendAndThrow(e, continuation);
      }
    }
  }

//前面已经return了SuspendForResponse的构造方法。
//到这里,源码追溯到头了。
//再看adapt方法,必然会被调用,追溯adapt方法,找到一个唯一调用。
  @Override
  final @Nullable ReturnT invoke(Object[] args) {
    Call call = new OkHttpCall<>(requestFactory, args, callFactory, responseConverter);
    return adapt(call, args);
  }
//追溯invoke方法,找到一个唯一调用。
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);
                }
                args = args != null ? args : emptyArgs;
                return platform.isDefaultMethod(method)
                    ? platform.invokeDefaultMethod(method, service, proxy, args)
                    : loadServiceMethod(method).invoke(args);
              }
            });
  }

再追溯create方法,就到我们自己定义的代码了。追溯“KotlinExtensions.awaitResponse”,再看refrofit2是怎么帮我们发起网络请求的:

suspend fun  Call.awaitResponse(): Response {
  return suspendCancellableCoroutine { continuation ->
    continuation.invokeOnCancellation {
      cancel()
    }
    enqueue(object : Callback {
      override fun onResponse(call: Call, response: Response) {
        continuation.resume(response)
      }

      override fun onFailure(call: Call, t: Throwable) {
        continuation.resumeWithException(t)
      }
    })
  }
}


1.首先这是一个retrofit2.Call,返回retrofit2.Response的suspend扩展方法。
该方法开启了一个可取消的协程挂起函数 suspendCancellableCoroutine。
该函数可以把异步回调的写法,封装转换为协程同步的写法。
这个挂起函数是使用Java的方式配合try catch调用的,既:
// See SuspendForBody for explanation about this try/catch.
try {
        return KotlinExtensions.awaitResponse(call, continuation);
      } catch (Exception e) {
        return KotlinExtensions.suspendAndThrow(e, continuation);
      }
这样就没有报:Suspend function 'xxx' should be called only from a coroutine or another suspend function.的错误。
2.调用了enqueue方法,发起了一个异步请求。这里的enqueue方法,就是对Okhttp的enqueue的封装。
实际调用到的是:retrofit2.OkHttpCall.enqueue(callBack)。
3.在得到结果后,通过“continuation.resume(response)”或者“continuation.resumeWithException(t)”恢复程序继续从这里执行,并返回结果。
4.无论接口方法是否由suspend关键字修饰,返回的结果,都经过了转换。
用到了定义retrofit时传入的GsonConverterFactory里面的GsonResponseBodyConverter。
 try {
      T body = responseConverter.convert(catchingBody);
      return Response.success(body, rawResponse);
    } catch (RuntimeException e) {......}

总结

1.retrofit2封装了Okhttp,远程依赖只依赖retrofit就可以了。
2.使用suspend关键字时,retrofit2框架内部会帮我们判断接口方法的合法合规性,然后在内部开启一个可取消的协程 suspendCancellableCoroutine{} 去执行异步请求。
实际上还是调用的Okhttp Request Call enqueue 那一套。
3.所以,api接口方法如果不用suspend关键字修饰,则应该写成:

     @GET("api/test")
     fun testApi(@Query("test") test: Int?): retrofit2.Call>

     //调用:
      testService.testApi(0).enqueue(object :retrofit2.Callback>{
                override fun onResponse(
                    call: Call>,
                    response: Response>,
                ) { }

                override fun onFailure(call: Call>, t: Throwable) {  }

            })

使用suspend关键字修饰,则可以直接像同步方法一样调用:

suspend fun xxx() =  testService.testApi(0)

4.retrofit2是怎么实现切换线程的呢?下一篇继续。

你可能感兴趣的:(Retrofit是如何支持Kotlin协程的?)