4

Retrofit加kotlin协程为何如此优雅

 3 years ago
source link: https://blog.csdn.net/mingyunxiaohai/article/details/103914274
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Retrofit加kotlin协程为何如此优雅

chsmy2018 2020-01-09 19:13:10 401
分类专栏: Kotlin

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 结束。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK