注册

Retrofit解析

本次源码解析基于2.9.0,如有描述错误,请大佬们评论指出。

一、Retrofit的作用

Retrofit基于okhttp,简化了okhttp请求接口的操作,而且适配Rxjava和kotlin的协程,但目前还没有适配kotlin的Flow,如果要适配,自己封装也是可以的。

先看看早期直接使用okhttp请求 image.png 构造请求+解析响应+使用okhttp的线程池执行(当然okhttp也有同步调用),一堆操作很是麻烦,如果加上loading显示/隐藏、线程切换代码会更加复杂,retrofit+rxjava的经典搭配适应潮流就出现了。

retrofit适配的返回值 image.png 支持协程的话,小伙伴可能会懵逼了,注解啥的都好说,这个都好处理,它怎么拿到方法上的suspend,其实retrofit不需要拿suspend这个修饰符,因为java压根没有suspend,编译之后显真身,suspend在kotlin看来就只是一个挂起函数标志,在编译成java字节码后偷偷摸摸多了个用于回调的接口Continuation。 image.png 先来看看retrofit用法

//就是创建我们的retrofit客户端
public class HttpManager {
private Retrofit mRetrofit;
private Map<Class<?>, Object> mMap = new HashMap<>();
private static class SingletonInstance {
private static final HttpManager INSTANCE = new HttpManager();
}
public static HttpManager getInstance() {
return SingletonInstance.INSTANCE;
}
private HttpManager() {
mRetrofit = new Retrofit.Builder()
.client(自定义的okhttpClient) //不写的话,retrofit也会默认创建
.baseUrl("https://xxxx.xxxx.cn")
.addConverterFactory(ScalarsConverterFactory.create())//转换为String对象
.addConverterFactory(GsonConverterFactory.create())//转换为Gson对象
//接口返回值适配
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.build();
}
public <T> T create(Class<T> cls) {
if (!mMap.containsKey(cls)) {
T t = mRetrofit.create(cls);
mMap.put(cls, t);
}
return (T) mMap.get(cls);
}
}

image.png image.png image.png

Q: 那个Call直接可以enqueue,那个observeable在subscribe后数据就可以接收,协程挂起恢复后就直接返回了,有点厉害,咋实现?
拿简单的observable来说,我们会使用create方法创建一个Observable,然后需要自己管理数据的发射,retrofit操作的Observable估计也要自行处理数据的发射???是这样么?后文解释。 image.png

Q: 方法上有注解,请求参数也有注解,返参还有泛型,这个怎么处理?
方法上有注解,请求参数也有注解,拿到method后解析注解,这个不难,拿到这些注解后,构建Request,如果是post的话,还要构造RequestBody,要注明MediaType,返回值是Call、Observable等决定是走默认的还是rxjava,或者协程,返回值上的泛型也很关键,在okhttp的rawResponse拿到后,要解析响应,需要预先选择合适的解析器解析数据。

二、从Retrofit+Observable请求post接口熟悉流程

  • 2.1、post请求编写

image.png 这里没有为协程专门搞个什么CallAdapterFactory哦,因为协程走默认的DefaultCallAdapterFactory。 这个默认的在创建Retrofit对象时添加进去的。 image.png image.png

  • 2.2、 retrofit的创建--->Retrofit的build方法

public Retrofit build() {
.....
okhttp3.Call.Factory callFactory = this.callFactory;
if (callFactory == null) { //没有就默认可以给你创建OkhttpClient
callFactory = new OkHttpClient();
}
Executor callbackExecutor = this.callbackExecutor;
if (callbackExecutor == null) { //回调的线程池,安卓默认就是主线程切换
callbackExecutor = new MainThreadExecutor();
}
//添加默认的 new DefaultCallAdapterFactory(callbackExecutor)
List<CallAdapter.Factory> callAdapterFactories = new ArrayList<>(this.callAdapterFactories);
callAdapterFactories.addAll(new DefaultCallAdapterFactory(callbackExecutor)
List<Converter.Factory> converterFactories = new ArrayList<>( 1 + this.converterFactories.size() + platform.defaultConverterFactoriesSize());
//添加一些默认的转换器
converterFactories.add(new BuiltInConverters());
converterFactories.addAll(this.converterFactories);
converterFactories.addAll(new OptionalConverterFactory());
return new Retrofit(....);
}
//android的回调的主线程池
static final class MainThreadExecutor implements Executor {
private final Handler handler = new Handler(Looper.getMainLooper());
@Override
public void execute(Runnable r) {
handler.post(r);
}
}

如果没看retrofit的build方法,在debug时,发现我们明明只添加了两个转换器和一个RxjavaCallAdapter,为啥会多出来一些不认识的转换器,那是因为retrofit在创建时,偷偷摸摸给你添加了一些默认的。多贴心呐。 记住:callFactory就是OkHttpClient

  • 2.3、 经典retrofit的动态代理---->create方法

image.png create方法的返回值是我们自定义的Api接口对象,所以可以直接调用Api的方法---废话。
InvocationHandler的invoke方法的返回值是Object,Api接口类里面方法的返回值可能是Call、Observable、Object,采用Object做返回值就都可以支持了。

来看看loadServiceMethod

private final Map<Method, ServiceMethod<?>> serviceMethodCache = new ConcurrentHashMap<>();
//用一个支持并发安全的Map缓存Method了
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,他的子类有好几个。 image.png 其中以Rxjava和Call方式请求都是返回的CallAdapted.
协程返回的是SuspendForBody或者SuspendForResponse.

来看看parseAnnotations

static <T> ServiceMethod<T> parseAnnotations(Retrofit retrofit, Method method) {
//先创建RequestFactory,然后创建HttpServiceMethod
RequestFactory requestFactory = RequestFactory.parseAnnotations(retrofit, method);
........
返回值是Void以及不能解析的返回值类型判断
.....
return HttpServiceMethod.parseAnnotations(retrofit, method, requestFactory);
}
  • 2.4、 RequestFactory创建---->解析方法注解以及方法参数

很关键的RequestFactory.parseAnnotations(...)返回RequestFactory,它里面含有太多信息

static RequestFactory parseAnnotations(Retrofit retrofit, Method method) {
return new Builder(retrofit, method).build();
}
//方法参数处理器数组
ParameterHandler<?>[] parameterHandlers
RequestFactory build() {
for (Annotation annotation : methodAnnotations) {
//解析方法上的注解
parseMethodAnnotation(annotation);
}
//hasBody isFormEncoded relativeUrl isFormEncoded isMultipart gotPart
//那我们的post这里肯定是有body的哈,不是表单提交,这里不是很重要的细节,不写
......
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);
}

来看看解析方法上的注解parseMethodAnnotation(annotation)
这里就以例子@POST("app/courses/behaviour")参考,解析方法上的注解获取请求方式以及短路径

private void parseMethodAnnotation(Annotation annotation) {
//判断方法上注解的类型 DELETE GET HEAD PATCH PUT OPTIONS HTTP POST retrofit2.http.Headers
//Multipart FormUrlEncoded)
//这里只保留POST
if (annotation instanceof DELETE) {
....
} else if (annotation instanceof POST) {
//这里就是处理这个 @POST("app/courses/behaviour")
this.httpMethod ="POST";
this.hasBody = true;
String value=((POST) annotation).value();
if (value.isEmpty()) {
return;
}
.......
//保存短路径
this.relativeUrl = value;
this.relativeUrlParamNames = parsePathParameters(value);
}
......
}

来看解析方法参数--->parseParameter
先讲讲我们的Body类
fun getCourse(@Body info: CourseInfo): Observable 参数用@Body注解,设计了ParameterHandler这个类 image.png 这里拎出Body这个类,它要构造RequestBody,需选择xxxRequestBodyConverter才能构建成功。 image.png

private @Nullable ParameterHandler<?> parseParameter(....) {
ParameterHandler<?> result = null;
if (annotations != null) {
for (Annotation annotation : annotations) {
ParameterHandler<?> annotationAction =
//解析方法参数的注解
parseParameterAnnotation(p, parameterType, annotations, annotation);
.......
result = annotationAction;
}
}
if (result == null) {
if (allowContinuation) {
try { //判断是不是走协程,使用Continuation.class类判断
//这个判断有点粗糙
if (Utils.getRawType(parameterType) == Continuation.class) {
isKotlinSuspendFunction = true;
return null;
}
} catch (NoClassDefFoundError ignored) {
}
}
.....
}
return result;
}

上面👆就解释了,Retrofit怎么判断是走协程的,是通过判断参数里面有没有一个Continuation类型,是的话,就走协程。下面在参数里面添加了Continuation,但是我希望他走Rxjava,但不幸的事,它认为应该走协程,那就奔溃了。后文解释。 方来了,这个parseParameterAnnotation方法有400+行,一个方法400+行呐。但我们只看需要的 这里以注解是Field和Body为例。

@Nullable
private ParameterHandler<?> parseParameterAnnotation(
int p, Type type, Annotation[] annotations, Annotation annotation
) {
//annotation instanceof Url/Path/Query/QueryName/QueryMap/Header/HeaderMap等太多,删除
//以Field和Body类讲解
if (annotation instanceof Url) {
.....
} else if (annotation instanceof Field) {
.....
//省略部分逻辑
Converter<?, String> converter = retrofit.stringConverter(type, annotations);
return new ParameterHandler.Field<>(name, converter, encoded);
} else if (annotation instanceof Body) {
//body类型 肯定就不是表单类型了
......
Converter<?, RequestBody> converter;
converter = retrofit.requestBodyConverter(type, annotations, methodAnnotations);
//requestBody转换器存在于请求参数处理器中
return new ParameterHandler.Body<>(method, p, converter);
}
return null; // Not a Retrofit annotation.
}

Field注解: image.png 来看看retrofit.stringConverter(type, annotations)方法 image.png image.png image.png 请求url(baseUrl+relativeUrl拼接)、头字段的处理

Request.Builder get() {
HttpUrl url;
HttpUrl.Builder urlBuilder = this.urlBuilder;
if (urlBuilder != null) {
url = urlBuilder.build();
} else {
url = baseUrl.resolve(relativeUrl);
}
RequestBody body = this.body;
......
MediaType contentType = this.contentType;
if (contentType != null) {
if (body != null) {
body = new ContentTypeOverridingRequestBody(body, contentType);
} else {
headersBuilder.add("Content-Type", contentType.toString());
}
}
return requestBuilder.url(url).headers(headersBuilder.build()).method(method, body);
}

解析okhttp3的rawResponse为Retrofit的Response

 Response<T> parseResponse(okhttp3.Response rawResponse) throws IOException {
ResponseBody rawBody = rawResponse.body();
rawResponse = rawResponse.newBuilder()
.body(new NoContentResponseBody(rawBody.contentType(), rawBody.contentLength()))
.build();
int code = rawResponse.code();
if (code < 200 || code >= 300) {
try {
// 使用okio读取的
ResponseBody bufferedBody = Utils.buffer(rawBody);
return Response.error(bufferedBody, rawResponse);
} finally {
rawBody.close();
}
}
if (code == 204 || code == 205) {
rawBody.close();
return Response.success(null, rawResponse);
}
ExceptionCatchingResponseBody catchingBody = new ExceptionCatchingResponseBody(rawBody);
try {
//之前保存的响应体转换器去转换ResponseBody
T body = responseConverter.convert(catchingBody);
return Response.success(body, rawResponse);
} catch (RuntimeException e) {
catchingBody.throwIfCaught();
throw e;
}
}

image.png

Q: 协程请求的接口方法声明上没有Call,它是怎么选择DefaultCallAdapterFactory的呢?

static <ResponseT, ReturnT> HttpServiceMethod<ResponseT, ReturnT> parseAnnotations(
Retrofit retrofit, Method method)
{
//先创建RequestFactory
RequestFactory requestFactory = RequestFactory.parseAnnotations(retrofit, method);
boolean isKotlinSuspendFunction = requestFactory.isKotlinSuspendFunction; //true
......
Annotation[] annotations = method.getAnnotations();
Type adapterType;
if (isKotlinSuspendFunction) {
Type[] parameterTypes = method.getGenericParameterTypes();
//找那个Continuation<?super ExtendItem>参数,取的是ExtendItem的下界
Type responseType = Utils.getParameterLowerBound(0, (ParameterizedType) parameterTypes[parameterTypes.length - 1]);
.....
//获取返回值的类型,,这里偷偷摸摸加东西了,Call.class-->看来要走DefaultCallAdapterFactory
adapterType = new Utils.ParameterizedTypeImpl(null, Call.class, responseType);
//而且给方法注解加多一个SkipCallbackExecutor类型的注解
annotations = SkipCallbackExecutorImpl.ensurePresent(annotations);
} else {
adapterType = method.getGenericReturnType();
}
//创建CallAdapter 区分是走默认的还是Rxjava那一套
CallAdapter<ResponseT, ReturnT> callAdapter = createCallAdapter(retrofit, method, adapterType, annotations);
//校验响应类型是不是okhttp的Response,是的话直接throw Exception
//检验响应类型是不是retrofit的Response,是的话,没带泛型,直接throw Exception
//校验请求如果是head请求且返回值是Void类型,不满足的就直接throw Exception
.....
//创建转换器
Converter<ResponseBody, ResponseT> responseConverter = createResponseConverter(retrofit, method, responseType);
okhttp3.Call.Factory callFactory = retrofit.callFactory;
if (!isKotlinSuspendFunction) {
//非协程的部分
return new CallAdapted<>(requestFactory, callFactory, responseConverter, callAdapter);
} else if (continuationWantsResponse) {
//协程的返回值是Response<XXX>类型
return (HttpServiceMethod<ResponseT, ReturnT>)
new SuspendForResponse<>(requestFactory, callFactory, responseConverter, (CallAdapter<ResponseT, Call<ResponseT>>) callAdapter);
} else {
//协程的返回值是xxx类型-->我们这里就是ExtendItem类型,所以走这里
return (HttpServiceMethod<ResponseT, ReturnT>)
new SuspendForBody<>(requestFactory, callFactory, responseConverter, (CallAdapter<ResponseT, Call<ResponseT>>) callAdapter, continuationBodyNullable);
}
}

requestFactory.isKotlinSuspendFunction在创建RequestFactory时,走协程的话会设置为true。
上面依次创建requestFactory、callAdapter,响应转换器responseConverter,最后创建SuspendForBody(HttpServiceMethod的子类)。--->callFactory就是OkhttpClient。
这个地方细节有点多,suspend编译之后,参数加了个Continuation,但是CallAdapter只有2种,协程走的是默认的DefaultCallAdapterFactory,而且为了不跟retrofit的Call请求方式起冲突了,偷偷摸摸的在我们代码里面下毒了。

    Type adapterType = new Utils.ParameterizedTypeImpl(null, Call.class, responseType);
Annotation[] annotations = SkipCallbackExecutorImpl.ensurePresent(annotations);

Utils.ParameterizedTypeImpl就是ParameterizedType的子类,这里rawType就是Call,responseType就是ExtendItem

static final class ParameterizedTypeImpl implements ParameterizedType { 
.....
ParameterizedTypeImpl(@Nullable Type ownerType, Type rawType, Type... typeArguments) {
this.ownerType = ownerType;
this.rawType = rawType;
this.typeArguments = typeArguments.clone();
}
@Override
public Type getRawType() {
return rawType;
}
.......
}

从Contiuation参数上获取到响应类型是ExtendItem类型,然后再经过封装,将响应类型的getRawType返回Call,这点很关键,这下就决定能走DefaultCallAdapterFactory了。
再来看看ensurePrensent方法:偷偷摸摸给方法加上一个新的注解SkipCallbackExecutor返回,然后给CallAdapter创建使用。贴心呐。
SkipCallbackExecutor 表示跳过线程切换到主线程,协程才不用Retrofit的主线程切换MainThreadExecutor,是用户通过协程调度器实现。 image.png

最后,我们看看suspend实际处理的样子: image.png 创建callAdapter 之前也讲过的
image.png

看看DefaultCallAdapterFactory的get方法,有用到SkipCallbackExecutor注解。 image.png 看那个getRawType(returnType),只要是Call.class就能返回非null的CallAdapter,所以Retrofit封装响应类型的rawType为Call是有用的。
同时基于SkipCallbackExecutor注解的判断,导致CallAdapter的adapt方法直接将入参的call原样返回了。

响应体的转换器准备

private final List<Converter.Factory> converterFactories = new ArrayList<>();
private final List<CallAdapter.Factory> callAdapterFactories = new ArrayList<>();

前面讲过的,他会尝试converFactories,我们这里是ExtendItem类型,那么它的转换器,只可能是GsonResponseBodyConverter了 image.png

Retrofit的java代码跟协程代码融合的地方-->SuspendForBody的invoke方法

final @Nullable Object invoke(Object[] args) {
Call<ResponseT> call = new OkHttpCall<>(requestFactory, args, callFactory, responseConverter);
//之前SuspendForBody的CallAdapter啥事没干,入参是Retrofit中的Call,因没有处理线程切换的操作,返参还是Retrofit中的Call,看后面的图哈。
Call<ResponseT> call = callAdapter.adapt(call, args)
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);
}

}

image.png Retrofit的call创建好后,因我们的返回值是ExtendItem,不是可空的,所以就丢给KotlinExtensions.await方法,开始协程处理。

suspend fun <T : Any> Call<T>.await(): T {
//支持可取消的协程
return suspendCancellableCoroutine { continuation ->
continuation.invokeOnCancellation {
//取消接口请求
cancel()
}
//走okhttp的异步接口请求
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)
}
})
}
}

java调用转到kotlin调用,java的第一个参数是Call,这里是Call的拓展方法await,然后java第二个参数,给suspendCancellableCoroutine接收,这个await方法就是回调转成挂起函数的经典模板。这个模板代码一行都没精简过哈。

Q: 那协程请求完后切换到主线程在哪里执行的呢? image.png

看代码,关注下lifecycleScope的launch是否有主线程调度器,

val LifecycleOwner.lifecycleScope: LifecycleCoroutineScope
get() = lifecycle.coroutineScope
val Lifecycle.coroutineScope: LifecycleCoroutineScope
get() {
while (true) {
val existing = mInternalScopeRef.get() as LifecycleCoroutineScopeImpl?
if (existing != null) {
return existing
}
val newScope = LifecycleCoroutineScopeImpl( this,
//协程上下文在这里
SupervisorJob() + Dispatchers.Main.immediate)
if (mInternalScopeRef.compareAndSet(null, newScope)) {
newScope.register()
return newScope
}
}
}

协程上下文:SupervisorJob() + Dispatchers.Main.immediate,从这里看出他是个Supervisor,主从作用域,它取消了,不会影响父协程,非常的可以。
调度器是这个Dispatchers.Main.immediate,还能说啥,协程的调度通过协程拦截器拦截Continuation实现。

image.png image.png kotlin的代码debug不好整,大家大致看下。

image.png

五、后续

我们看到用retrofit+协程写个接口请求,还要显式的try catch,真的是,有点,哎,看后续Retrofit支持Kotlin的Flow不,不支持的话,可以考虑自己整个试试。

0 个评论

要回复文章请先登录注册