阅读 742

Retofit 是如何支持协程的

「本文已参与好文召集令活动,点击查看:后端、大前端双赛道投稿,2万元奖池等你挑战!

自Retofit 2.6.0 版本之后就默认只是 kotlin 协程了。使用起来也很方便。只有在函数前面加上suspend 就可以了。

interface ApiService {
  @GET("article/list/{page}/json")
  suspend fun getArticleList(@Path("page") page:Int): Result<PageEntity<Article>> 
}
复制代码

简单回顾一下使用

val retrofit= Retrofit.Builder()
    .baseUrl("https://www.wanandroid.com")
    .addConverterFactory(GsonConverterFactory.create(GsonBuilder().create()))
    .build()

val apiService = retrofit.create(ApiService::class.java)

lifecycleScope.launch { 
    val result = apiService.getArticleList(1)
}
复制代码

返回结果直接的我们想要的结果,不需要 Call 包裹了。那 Retofit 是如何做到的呢?

简单的源码分析

关于 Retofit 的源码分析网上有很多,我在这里就不详细介绍了。

方法入口 Retofit#create

查看Retofit的create方法发现有一个动态代理,当调用接口方法时就会触发InvocationHandler的invoke方法。

public <T> T create(final Class<T> 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)//java1.8 接口可以有默认实现
                  ? platform.invokeDefaultMethod(method, service, proxy, args)
                  : loadServiceMethod(method).invoke(args);
            }
          });
}
复制代码

方法最后的loadServiceMethod(method) 会调用 ServiceMethodparseAnnotations()方法。

ServiceMethod.java 源码


abstract class ServiceMethod<T> {
  static <T> ServiceMethod<T> parseAnnotations(Retrofit retrofit, Method method) {
  // 0️⃣ 构建请求工厂
    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.");
    }
  // 1️⃣ 
    return HttpServiceMethod.parseAnnotations(retrofit, method, requestFactory);
  }

  abstract @Nullable T invoke(Object[] args);
}
复制代码

在 0️⃣ 处 构建 RequestFactory 对象时,会调用RequestFactorybuild()方法, 在这个方法中有这么一段代码 遍历解析方法参数

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);
}
复制代码

找到了 isKotlinSuspendFunction

parseParameter 方法部分代码

private @Nullable ParameterHandler<?> parseParameter(
    int p, Type parameterType, @Nullable Annotation[] annotations, boolean allowContinuation) {
	// 省略部分代码……
  if (result == null) {
    //0️⃣ 是否为最后一个参数
    if (allowContinuation) {
      try {
        //1️⃣ 
        if (Utils.getRawType(parameterType) == Continuation.class) {
          isKotlinSuspendFunction = true;
          return null;
        }
      } catch (NoClassDefFoundError ignored) {
      }
    }
    throw parameterError(method, p, "No Retrofit annotation found.");
  }

  return result;
}
复制代码

注释 0️⃣ 处allowContinuation是的值是遍历解析调用时传入的 p == lastParameter 也就是判断是否是最后一个参数,如果是最后一个参数,那就判断这个参数的类是否是Continuation.class (kotlin.coroutines包下的类)如果是就把 isKotlinSuspendFunction 标记为 true,这里似乎是找到关于kotlin 协程相关的东西了,这把一个是否支持Kotlin suspend 函数 的遍历变成了true。

kotlin 的黑魔法

为什么判断最后一个参数是 Continuation.class 就把 isKotlinSuspendFunction 标记为 true 呢? 我们写方法的时候没有加上Continuation类型参数呀? 那这就说说Kotlin 的黑魔法了,kotlin 之所以能让我写很简洁的代码,是因为kotlin 编译器帮我们“写”了很多代码。 我们通过 AS 提供的 kotlin 的字节码工具反编译得到结果如下

//kotlin 	
@GET("article/list/{page}/json")
 suspend fun getArticleList(@Path("page") page:Int): Result<PageEntity<Article>>

-------------------------------------------------------------------------

//kotlin字节码 反编译后的java代码
@GET("article/list/{page}/json")
@Nullable
Object getArticleList(@Path("page") int var1, @NotNull Continuation<Result<PageEntity<Article>>> var2);
复制代码

发现如果函数使用 suspend 的话,编译之后就会在参数类表最后添加一个 Continuation 类型的参数。Continuation的泛型类型就是原函数的返回值类型,反编译后的java代码是看不到的,直接看kotlin 字节码是可以看到这个方法签名是这样的。kotlin协程的 Continuation 相当于回调,返回函数的返回值就是在回调是返回的。

isKotlinSuspendFunction 在哪里使用的

明白了 isKotlinSuspendFunction 判断的依据那么继续往下看吧。 我们再回到ServiceMethod的parseAnnotations的方法,方法的最后调用的HttpServiceMethod的parseAnnotations 并将返回值返回。

我们再去看看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;
//0️⃣  根据是否是kotlin suspend 来处理
  if (isKotlinSuspendFunction) {
    Type[] parameterTypes = method.getGenericParameterTypes();
    Type responseType = Utils.getParameterLowerBound(0,
        (ParameterizedType) parameterTypes[parameterTypes.length - 1]);
    //1️⃣ ⬇️
    if (getRawType(responseType) == Response.class && responseType instanceof ParameterizedType) {//2
      // 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
    }

    adapterType = new Utils.ParameterizedTypeImpl(null, Call.class, responseType);
    annotations = SkipCallbackExecutorImpl.ensurePresent(annotations);
  } else {
    adapterType = method.getGenericReturnType();
  }
 // 省略部分代码……
  if (!isKotlinSuspendFunction) {
    return new CallAdapted<>(requestFactory, callFactory, responseConverter, callAdapter);
  } else if (continuationWantsResponse) {
    //noinspection unchecked Kotlin compiler guarantees ReturnT to be Object.
    //2️⃣ ⬇️
    return (HttpServiceMethod<ResponseT, ReturnT>) new SuspendForResponse<>(requestFactory,
        callFactory, responseConverter, (CallAdapter<ResponseT, Call<ResponseT>>) callAdapter);
  } else {
    //noinspection unchecked Kotlin compiler guarantees ReturnT to be Object.
    //3️⃣ ⬇️
    return (HttpServiceMethod<ResponseT, ReturnT>) new SuspendForBody<>(requestFactory,
        callFactory, responseConverter, (CallAdapter<ResponseT, Call<ResponseT>>) callAdapter,
        continuationBodyNullable);
  }
}
复制代码

在注释 0️⃣ 处 根据 isKotlinSuspendFunction 来判断是否按kotlin suspend 函数处理,上面我们知道suspend 函数的返回值类型,最终会变成最后一个参数 Continuation 的泛型类型,在注释 1️⃣ 处判断最后一个参数的泛型是不是 Response 类的,如果是就把 continuationWantsResponse 标记为true

在注释 2️⃣ 和注释 3️⃣ 处根据 continuationWantsResponse 创建不同的对象。看到这我们可以知道,suspend 函数应该还有一种写法,那就是返回值用Response包裹。

@GET("article/list/{page}/json")
suspend fun getArticleList1(@Path("page") page:Int): Response<Result<PageEntity<Article>>>
复制代码

上面是另外一种写法,我们还是继续分析返回值不带 Response 的,那就分析 3️⃣ 处的情况吧,也就是SuspendForBody。

支持协程的奥秘

SuspendForBody

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) {
    call = callAdapter.adapt(call);
    Continuation<ResponseT> continuation = (Continuation<ResponseT>) args[args.length - 1];
   
    try {
      return isNullable
          ? KotlinExtensions.awaitNullable(call, continuation)
          : KotlinExtensions.await(call, continuation);
    } catch (Exception e) {
      return KotlinExtensions.yieldAndThrow(e, continuation);
    }
  }
}
复制代码

根据返回值是否为空调用不同的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) {
          val body = response.body()
          if (body == null) {
    				//省略部分代码
            continuation.resumeWithException(e)
          } else {
            continuation.resume(body)
          }
        } else {
          continuation.resumeWithException(HttpException(response))
        }
      }
      override fun onFailure(call: Call<T>, t: Throwable) {
        continuation.resumeWithException(t)
      }
    })
  }
}
复制代码

它是一个 Call 的扩展函数 suspendCancellableCoroutine 函数中帮我调用了 enqueue 函数,在它的onResponse 回调中调用 continuation.resume(body)把请求的数据返回给我们。

还有个问题上面SuspendForBodyadapt 是怎么调用的呢? 我们从头再看看当接口方法调用是触发的那个方法吧,在InvocationHandler 的invoke的方法最后一句是

return loadServiceMethod(method).invoke(args != null ? args : emptyArgs);
复制代码

我们上面分析了那么多我们只是分析了loadServiceMethod(method)方法执过程,此时loadServiceMethod(method)的返回值就是 SuspendForBody 对象,SuspendForBody 对象调用 invoke方法最终就调用了adapt方法。

@Override final @Nullable ReturnT invoke(Object[] args) {
  Call<ResponseT> call = new OkHttpCall<>(requestFactory, args, callFactory, responseConverter);
  return adapt(call, args);
}
复制代码

以上就是Retofit协程请求方式的大致过程。

扩展——回调变协程

上面我们看到 Retofit 是利用 kotlin 提供的suspendCancellableCoroutine 函数实现的对kotlin 协程的支持。kotlin 协程一个很好的用处就是解决回调地狱,让异步代码变成“同步代码”。对于 Retofit 支持kotlin 协程,那对于那些不支持的咋办。我们可以仿照 Retofit 的写法,让回调变协程。 比如我们让 OKHTTP 支持协程。我们可以写一个Okhttp的 Call 的扩展。

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, e: IOException) {
                continuation.resumeWithException(e)
            }
        })
    }
}
复制代码

使用

lifecycleScope.launch {
    val request = Request.Builder().url("https://www.wanandroid.com").get().build()
    val response = okHttpClient.newCall(request).awaitResponse()
   if (response.isSuccessful){
       //TODO
   }
}
复制代码

任何回调,都可以通过这种方式变成支持协程,如果遇到回调嵌套的话,可以考虑用这个方式优化。

文章分类
Android
文章标签