Retrofit加kotlin协程为何如此优雅

4,591 阅读9分钟

前奏

Retrofit的正常写法先啰嗦一遍如下:

interface AipInterface {

@GET("article/list/1/json")
fun getHomeList() : Call<WanBaseResponse<Data>>

}
val retrofit = Retrofit.Builder()
               .addConverterFactory(GsonConverterFactory.create())
               .baseUrl("https://www.wanandroid.com/")
               .build()
val service =  retrofit.create(AipInterface::class.java)
val call = service.getHomeList()
call.enqueue(object : Callback<WanBaseResponse<Data>> {
            override fun onFailure(call: Call<WanBaseResponse<Data>>, t: Throwable) {
                Log.i("CoroutinesActivity","onFailure")
            }

            override fun onResponse(call: Call<WanBaseResponse<Data>>, response: Response<WanBaseResponse<Data>>) {
                Log.i("CoroutinesActivity","onResponse:${response.body()}")
            }
        })

先创建retrofit,然后通过retrofit创建service,通过serice拿到Call对象,最后调用Call的enqueue方法,从回调中得到结果。

看起来也不是特别麻烦,而且真实项目中使用肯定会在封装一下,比这更简单,不过不管咋封装,回调还是少不了的,使用协程就可以把回调去掉啦,下面看看协程是咋实现的。

interface AipInterface {

    @GET("article/list/1/json")
    suspend  fun getHomeList() : WanBaseResponse<Data>
    
}

AipInterface中就跟以前不一样了,首先用suspend关键字修饰方法,表示这是一个挂起函数,可以在协程中使用,然后返回可以直接返回我们想要的实体类,这有点牛皮了,不过这个功能只能在Retrofit 2.6以后的版本中使用。

val retrofit = Retrofit.Builder()
               .addConverterFactory(GsonConverterFactory.create())
               .baseUrl("https://www.wanandroid.com/")
               .build()
val service =  retrofit.create(AipInterface::class.java)
GlobalScope.launch{
            val result = withContext(Dispatchers.IO){service.getHomeList()}
            Log.i("launch","onResponse:${result.data.datas[0].link}")
        }

主要是替换了之前的回调,使用一行代码搞定。没有了回调,代码看起来整洁了不少。

上面的代码虽然能工作,不过我们项目中肯定不能直接这么用,GlobalScope是一个顶级的协程,作用域是全局的,无法提早取消。使用的时候最好使用ViewModel,LiveData,和ViewModel的扩展viewModelScope来完成网络请求。

viewModelScope 是官方提供的ViewModel的扩展,继承CoroutineScope,CoroutineScope字面意思协程作用域,它会跟踪所有它所创建的协程, 当当前的ViewModel结束的时候,它所执行的异步任务也需要结束,防止内存泄露,之前我们需要在ViewModel的onCleared方法中通过SupervisorJob的cancel方法来销毁,使用viewModelScope可以简化这个操作,它会自动调用ViewModel的onCleared取消当前操作

创建一个ViewModel

class MyViewModel : ViewModel(){

    val mHomeData = MutableLiveData<Data>()

    fun launchOnUI(block:suspend CoroutineScope.()->Unit){
        viewModelScope.launch {
           withContext(Dispatchers.IO){ block()}
        }
    }
}

Activity中使用

val myViewModel = ViewModelProvider(this).get(MyViewModel::class.java)
        myViewModel.launchOnUI{
            val result = service.getHomeList()
            Log.i("myViewModel","onResponse:${result.data.datas[0].link}")
    }

就这样一个网络请求就完成了,是不是非常简单,而且我们也可以把MyViewModel中的block()代码块使用try catch包裹起来统计处理异常。封装一下用起来更爽。

OK封装的事情就先不讨论了,我们今天的问题是它是如何做到如此优雅的呢,两个问题:

  1. 为啥使用viewModelScope,就能自动管理生命周期,自动取消请求呢
  2. 为啥AipInterface中可以直接返回我们需要的实体类呢,它的网络请求时在哪里进行的呢?

带着问题去看代码吧

第一个问题

为啥使用viewModelScope,就能自动管理生命周期,自动取消请求呢

private const val JOB_KEY = "androidx.lifecycle.ViewModelCoroutineScope.JOB_KEY"

val ViewModel.viewModelScope: CoroutineScope
        get() {
            val scope: CoroutineScope? = this.getTag(JOB_KEY)
            if (scope != null) {
                return scope
            }
            return setTagIfAbsent(JOB_KEY,
                CloseableCoroutineScope(SupervisorJob() + Dispatchers.Main.immediate))
        }

internal class CloseableCoroutineScope(context: CoroutineContext) : Closeable, CoroutineScope {
    override val coroutineContext: CoroutineContext = context

    override fun close() {
        coroutineContext.cancel()
    }
}

上面是viewModelScope的全部代码,它继承CoroutineScope,CoroutineScope会跟踪所有它所创建的协程, 当当前的ViewModel结束的时候,它所执行的异步任务也需要结束。

get方法中,首先去缓存中(HashMap中)找有没有CoroutineScope对象,如果有直接返回,没有就创建一个CloseableCoroutineScope对象,并放到map缓存中。

CloseableCoroutineScope实现了Closeable和CoroutineScope,Closeable是个可关闭的数据源,在其close方法中就可以调用coroutineContext.cancel()方法取消当前作用域。

ViewModel是官方jetPack中的一个组件,当当前页面销毁的时候,会自动调用ViewModel的clear方法

  @MainThread
    final void clear() {
        mCleared = true;
        if (mBagOfTags != null) {
            synchronized (mBagOfTags) {
                for (Object value : mBagOfTags.values()) {
                    closeWithRuntimeException(value);
                }
            }
        }
        onCleared();
    }

内部调用了closeWithRuntimeException方法来关闭资源

   private static void closeWithRuntimeException(Object obj) {
        if (obj instanceof Closeable) {
            try {
                ((Closeable) obj).close();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
    }

里面调用了Closeable的close方法,前面我们知道CloseableCoroutineScope就实现了Closeable接口,所以这里就会调用到Closeable的close方法,取消当前作用域了。

第二个问题

为啥AipInterface中可以直接返回我们需要的实体类呢,它的网络请求时在哪里进行的呢?

这个问题从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);
          }
        });
  }

看过Retrofit代码的对这块经典的地方肯定很熟悉,它通过动态代理我们的AipInterface接口,把里面的方法转换成一个OkHttpCall并执行网络请求。

这些我们都不关注,我们主要寻找它是怎么直接返回我们需要的实体类的

进入loadServiceMethod方法

  ServiceMethod<?> loadServiceMethod(Method method) {
    ServiceMethod<?> result = serviceMethodCache.get(method);
    if (result != null) return result;

    synchronized (serviceMethodCache) {
      result = serviceMethodCache.get(method);
      if (result == null) {
        result = ServiceMethod.parseAnnotations(this, method);
        serviceMethodCache.put(method, result);
      }
    }
    return result;
  }

这里调用了ServiceMethod的parseAnnotations方法

  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.parseAnnotations方法,最后返回HttpServiceMethod.parseAnnotations先看第一个方法

static RequestFactory parseAnnotations(Retrofit retrofit, Method method) {
    return new Builder(retrofit, method).build();
  }

看起build方法

RequestFactory build() {

    for (Annotation annotation : methodAnnotations) {
        parseMethodAnnotation(annotation);
      }
      
    ......

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

这里会解析方法的注解和参数,注解肯定就是Retrofit中规定的那些注解,我们去看一下参数解析

private @Nullable ParameterHandler<?> parseParameter(
        int p, Type parameterType, @Nullable Annotation[] annotations, boolean allowContinuation) {
    
     ......
     
      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;
    }

这里好像看到了一些线索,isKotlinSuspendFunction = true,isKotlinSuspendFunction看它名字的意思“是否是 Suspend 函数” 通过 Utils.getRawType(parameterType) == Continuation.class方法来判断参数类型是否一样。

那我们的ApiInterface接口中的suspend fun getHomeList() : WanBaseResponse<Data>方法的参数类型到底是不是Continuation.class呢?这里面看着是无参数啊,为了搞清楚,还是看一下它的字节码吧。

AndroidStudio自身就可以方便的查看kotlin的字节码,先进入到ApiInterface这个类里面,点击Tools->kotlin->show kotlin Bytecode 就能看到字节码了如下

 // ================com/chs/androiddailytext/kotlin/AipInterface.class =================
       // class version 50.0 (50)
      // access flags 0x601
        public abstract interface com/chs/androiddailytext/kotlin/AipInterface {

        // access flags 0x401
        // signature (Lkotlin/coroutines/Continuation<-Lcom/chs/androiddailytext/kotlin/WanBaseResponse<Lcom/chs/androiddailytext/kotlin/Data;>;>;)Ljava/lang/Object;
        // declaration:  getHomeList(kotlin.coroutines.Continuation<? super com.chs.androiddailytext.kotlin.WanBaseResponse<com.chs.androiddailytext.kotlin.Data>>)
        public abstract getHomeList(Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
        @Lretrofit2/http/GET;(value="article/list/1/json")
        @Lorg/jetbrains/annotations/Nullable;() // invisible
        // annotable parameter count: 1 (visible)
        // annotable parameter count: 1 (invisible)
        @Lorg/jetbrains/annotations/NotNull;() // invisible, parameter 0
        LOCALVARIABLE this Lcom/chs/androiddailytext/kotlin/AipInterface; L0 L1 0

        @Lkotlin/Metadata;(mv={1, 1, 16}, bv={1, 0, 3}, k=1, d1={"\u0000\u0016\n\u0002\u0018\u0002\n\u0002\u0010\u0000\n\u0000\n\u0002\u0018\u0002\n\u0002\u0018\u0002\n\u0002\u0008\u0002\u0008f\u0018\u00002\u00020\u0001J\u0017\u0010\u0002\u001a\u0008\u0012\u0004\u0012\u00020\u00040\u0003H\u00a7@\u00f8\u0001\u0000\u00a2\u0006\u0002\u0010\u0005\u0082\u0002\u0004\n\u0002\u0008\u0019\u00a8\u0006\u0006"}, d2={"Lcom/chs/androiddailytext/kotlin/AipInterface;", "", "getHomeList", "Lcom/chs/androiddailytext/kotlin/WanBaseResponse;", "Lcom/chs/androiddailytext/kotlin/Data;", "(Lkotlin/coroutines/Continuation;)Ljava/lang/Object;", "app_debug"})
        // compiled from: AipInterface.kt
    

哈哈 找到了,参数果然是Lkotlin/coroutines/Continuation;到这我们就可以确定isKotlinSuspendFunction这个参数会为true了

然后回到ServiceMethod的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;
    
    ......
    
    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 {
      }
      
   ......
   
    okhttp3.Call.Factory callFactory = retrofit.callFactory;
    if (!isKotlinSuspendFunction) {
      return new CallAdapted<>(requestFactory, callFactory, responseConverter, callAdapter);
    } else if (continuationWantsResponse) {
      //noinspection unchecked Kotlin compiler guarantees ReturnT to be Object.
      return (HttpServiceMethod<ResponseT, ReturnT>) new SuspendForResponse<>(requestFactory,
          callFactory, responseConverter, (CallAdapter<ResponseT, Call<ResponseT>>) callAdapter);
    } else {
      //noinspection unchecked Kotlin compiler guarantees ReturnT to be Object.
      return (HttpServiceMethod<ResponseT, ReturnT>) new SuspendForBody<>(requestFactory,
          callFactory, responseConverter, (CallAdapter<ResponseT, Call<ResponseT>>) callAdapter,
          continuationBodyNullable);
    }
  }

如果不是isKotlinSuspendFunction就返回正常的CallAdapted,反之先判断continuationWantsResponse,这个参数的意思就是确定我们返回一个完整的Response还是只返回Response的body部分,我们这里返回的是body部分,所以最后返回了一个SuspendForBody类。

先记下这里

然后回到最开始的create方法的最后一步invoke方法,最终调用了HttpServiceMethod的invoke方法。

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

最后调用了adapt方法,adapt是一个抽象类,它的实现有三个,就是前面判断的那三种CallAdapted,SuspendForResponse和SuspendForBody。前面我们最终选择的是SuspendForBody,所以最终来到了SuspendForBody的adapt方法。

  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.suspendAndThrow(e, continuation);
      }
    }
  }

先获取到Call的实例,isNullable参数是创建SuspendForBody的时候穿过来的,写死的false,所以最后会走到KotlinExtensions.await(call, continuation)方法传入两个参数

  • call :Retrofit中的Call,最终执行网络请求
  • continuation : 顾名思义,继续、持续的意思,协程中的类,表示一个协程的延续,协程执行的时候会挂起,这个类就用于挂起完成之后的后续工作,看起来相当于一个回调。
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) {
            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")
            continuation.resumeWithException(e)
          } else {
            continuation.resume(body)
          }
        } else {
          continuation.resumeWithException(HttpException(response))
        }
      }

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

喔和 终于看到了enqueue的执行,这里执行了Call类中的enqueue方法,并拿到返回值,通过continuation把结果和异常统统返回给协程的调用者也就是我们最开始val result = service.getHomeList(),为了实现直接返回实体类,Retrofit内部帮我们调用了call的enqueue方法,拿到结果之后通过协程的continuation返回给我们。

OK 结束。