Retrofit加kotlin协程为何如此优雅
前奏
Retrofit的正常写法先啰嗦一遍如下:1
2
3
4
5
6interface AipInterface {
@GET("article/list/1/json")
fun getHomeList() : Call<WanBaseResponse<Data>>
}
1 | val retrofit = Retrofit.Builder() |
先创建retrofit,然后通过retrofit创建service,通过serice拿到Call对象,最后调用Call的enqueue方法,从回调中得到结果。
看起来也不是特别麻烦,而且真实项目中使用肯定会在封装一下,比这更简单,不过不管咋封装,回调还是少不了的,使用协程就可以把回调去掉啦,下面看看协程是咋实现的。
1 | interface AipInterface { |
AipInterface中就跟以前不一样了,首先用suspend关键字修饰方法,表示这是一个挂起函数,可以在协程中使用,然后返回可以直接返回我们想要的实体类,这有点牛皮了,不过这个功能只能在Retrofit 2.6以后的版本中使用。
1 | val retrofit = Retrofit.Builder() |
主要是替换了之前的回调,使用一行代码搞定。没有了回调,代码看起来整洁了不少。
上面的代码虽然能工作,不过我们项目中肯定不能直接这么用,GlobalScope是一个顶级的协程,作用域是全局的,无法提早取消。使用的时候最好使用ViewModel,LiveData,和ViewModel的扩展viewModelScope来完成网络请求。
viewModelScope 是官方提供的ViewModel的扩展,继承CoroutineScope,CoroutineScope字面意思协程作用域,它会跟踪所有它所创建的协程, 当当前的ViewModel结束的时候,它所执行的异步任务也需要结束,防止内存泄露,之前我们需要在ViewModel的onCleared方法中通过SupervisorJob的cancel方法来销毁,使用viewModelScope可以简化这个操作,它会自动调用ViewModel的onCleared取消当前操作
创建一个ViewModel1
2
3
4
5
6
7
8
9
10class MyViewModel : ViewModel(){
val mHomeData = MutableLiveData<Data>()
fun launchOnUI(block:suspend CoroutineScope.()->Unit){
viewModelScope.launch {
withContext(Dispatchers.IO){ block()}
}
}
}
Activity中使用1
2
3
4
5val 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封装的事情就先不讨论了,我们今天的问题是它是如何做到如此优雅的呢,两个问题:
- 为啥使用viewModelScope,就能自动管理生命周期,自动取消请求呢
- 为啥AipInterface中可以直接返回我们需要的实体类呢,它的网络请求时在哪里进行的呢?
带着问题去看代码吧
第一个问题
为啥使用viewModelScope,就能自动管理生命周期,自动取消请求呢
1 | private const val JOB_KEY = "androidx.lifecycle.ViewModelCoroutineScope.JOB_KEY" |
上面是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
9private 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
23public <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
13ServiceMethod<?> 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
14static <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 | static RequestFactory parseAnnotations(Retrofit retrofit, Method method) { |
看起build方法1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19RequestFactory 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
20private @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 | // ================com/chs/androiddailytext/kotlin/AipInterface.class ================= |
哈哈 找到了,参数果然是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
31static <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 | static final class SuspendForBody<ResponseT> extends HttpServiceMethod<ResponseT, Object> { |
先获取到Call的实例,isNullable参数是创建SuspendForBody的时候穿过来的,写死的false,所以最后会走到KotlinExtensions.await(call, continuation)
方法传入两个参数
- call :Retrofit中的Call,最终执行网络请求
- continuation : 顾名思义,继续、持续的意思,协程中的类,表示一个协程的延续,协程执行的时候会挂起,这个类就用于挂起完成之后的后续工作,看起来相当于一个回调。
1 | suspend fun <T : Any> Call<T>.await(): T { |
喔和 终于看到了enqueue的执行,这里执行了Call类中的enqueue方法,并拿到返回值,通过continuation把结果和异常统统返回给协程的调用者也就是我们最开始val result = service.getHomeList()
,为了实现直接返回实体类,Retrofit内部帮我们调用了call的enqueue方法,拿到结果之后通过协程的continuation返回给我们。
OK 结束。