Retrofit加kotlin协程为何如此优雅

Retrofit加kotlin协程为何如此优雅

前奏

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

1
2
3
4
5
6
interface AipInterface {

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

}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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方法,从回调中得到结果。

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

1
2
3
4
5
6
interface AipInterface {

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

}

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

1
2
3
4
5
6
7
8
9
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

1
2
3
4
5
6
7
8
9
10
class MyViewModel : ViewModel(){

val mHomeData = MutableLiveData<Data>()

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

Activity中使用

1
2
3
4
5
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,就能自动管理生命周期,自动取消请求呢

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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方法

1
2
3
4
5
6
7
8
9
10
11
12
@MainThread
final void clear() {
mCleared = true;
if (mBagOfTags != null) {
synchronized (mBagOfTags) {
for (Object value : mBagOfTags.values()) {
closeWithRuntimeException(value);
}
}
}
onCleared();
}

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

1
2
3
4
5
6
7
8
9
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方法开始

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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方法

1
2
3
4
5
6
7
8
9
10
11
12
13
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方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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先看第一个方法

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

看起build方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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中规定的那些注解,我们去看一下参数解析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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 就能看到字节码了如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// ================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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
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方法。

1
2
3
4
@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方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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 : 顾名思义,继续、持续的意思,协程中的类,表示一个协程的延续,协程执行的时候会挂起,这个类就用于挂起完成之后的后续工作,看起来相当于一个回调。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
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 结束。

# 进阶

コメント

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×