前言
Retrofit 是 OkHttp 的一个封装,它结合注解+动态代理,适配器模式,实现了可配置的网络请求,数据解析方式和数据返回类型,同时会把异步请求的回调切换到主线程的一个框架。
为什么使用 Retrofit?
为什么要使用 Retrofit 呢,不是已经有 OkHttp 了吗,直接使用不香吗?回忆下 OkHttp 的使用方式
class OkHttpUtil private constructor() {
private object OkHttpHolder {
val okHttpUtil = OkHttpUtil()
}
companion object {
fun getInstance(): OkHttpUtil {
return OkHttpHolder.okHttpUtil
}
}
private var mOkHttpClient: OkHttpClient = OkHttpClient.Builder().build()
fun enqueue() {
val request = Request.Builder().url("https://www.wanandroid.com/").build()
mOkHttpClient.newCall(request).enqueue(object:Callback{
override fun onResponse(call: Call, response: Response) {
val result = response.body().toString()
}
override fun onFailure(call: Call, e: IOException) {
}
})
}
fun execute(): Response {
val request = Request.Builder().url("https://www.wanandroid.com/").build()
return mOkHttpClient.newCall(request).execute()
}
}
如果直接使用 OkHttp ,面临几个问题,网络请求的 url,请求方式,请求参数统一管理问题,由于 OkHttp 是直接返回的 json 字符串,所以需要处理数据统一解析问题,Callback 返回的还是异步线程,更新 UI 的时候还需要统一切换到 UI 线程.而 Retrofit 正好帮我们解决了这些问题。
动态代理用在哪里,反射不是会耗性能?
我们日常使用 Retrofit 会创建 Retrofit 实例,然后调用 create 方法来获取我们定义的 API 接口
class RetrofitClient {
companion object {
fun getInstance(): RetrofitClient {
return SingletonHolder.INSTANCE
}
}
private object SingletonHolder {
const val DEFAULT_TIMEOUT = 20
const val baseUrl = "https://www.wanandroid.com/"
val INSTANCE = RetrofitClient()
lateinit var retrofit: Retrofit
}
private constructor() : this(SingletonHolder.baseUrl, null)
constructor(url: String, headers: Map<String, String>?) {
val okHttpClient = OkHttpClient.Builder()
.connectTimeout(SingletonHolder.DEFAULT_TIMEOUT.toLong(), TimeUnit.SECONDS)
.writeTimeout(
SingletonHolder.DEFAULT_TIMEOUT.toLong(),
TimeUnit.SECONDS
)
.build()
SingletonHolder.retrofit = Retrofit.Builder()
.client(okHttpClient)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.baseUrl(url)
.build()
}
fun <T> create(service: Class<T>?): T {
if (service == null) {
throw RuntimeException("Api service is null!")
}
return SingletonHolder.retrofit.create(service)
}
}
Retrofit 动态代理的使用就在 create 方法里面,简化代码后:
public class Retrofit {
@SuppressWarnings("unchecked") // Single-interface proxy creation guarded by parameter safety.
public <T> T create(final Class<T> service) {
return (T)
Proxy.newProxyInstance(
service.getClassLoader(),
new Class<?>[] {service},
new InvocationHandler() {
@Override
public @Nullable Object invoke(Object proxy, Method method, @Nullable Object[] args)
throws Throwable {
return loadServiceMethod(method).invoke(args);
}
});
}
}
通过这个方法,Retrofit 为自定义的 API 接口生成了一个代理类,我们调用 API 接口方法去网络请求都会来到 invoke 方法里面。
那问题来了,我们调用接口往往是高频的,使用动态代理就会面临反射耗性能问题。反射耗性能体现在 2 个地方,一个是大量反射创建实例时候,还一个是反射方法的调用。通过注释我们可以知道,Retrofit 推荐我们使用单例创建接口,也就是我们开发中尽量减少 API 接口的数量,一般都一个就好了。在数量少的情况下,动态代理反射创建的对象就少,那就和 new 创建对象区别不大了。接着就是反射方法的调用性能问题,那我们就要来看 loadServiceMethod 方法的实现
public class Retrofit {
private final Map<Method, ServiceMethod<?>> serviceMethodCache = new ConcurrentHashMap<>();
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;
}
}
可以发现,Retrofit 通过 DCL 方式调用 parseAnnotations 方法,然后使用serviceMethodCache 来缓存反射方法的注解解析信息 ServiceMethod 和返回类型。从而减少获取注解信息,返回类型的调用,从而降低反射带来的性能问题。
ServiceMethod 是怎么创建的,有什么用处?
ServiceMethod 是一个抽象类,它有一个继承类 HttpServiceMethod ,里面的 parseAnnotations 就是创建 ServiceMethod 的地方。
public class HttpServiceMethod {
static <ResponseT, ReturnT> retrofit2.HttpServiceMethod<ResponseT, ReturnT> parseAnnotations(
Retrofit retrofit, Method method, RequestFactory requestFactory) {
//返回值是否使用了协程,参数是否有Continuation,就知道是不是协程了。
boolean isKotlinSuspendFunction = requestFactory.isKotlinSuspendFunction;
//协程返回类型是不是 Response 类型,Retrofit Response 保存着 OkHttp 返回的 Response 体
boolean continuationWantsResponse = false;
//给协程用的,返回类型是否可以为 null ,这里为 false
boolean continuationBodyNullable = false;
Annotation[] annotations = method.getAnnotations();
Type adapterType;
if (isKotlinSuspendFunction) {
Type[] parameterTypes = method.getGenericParameterTypes();
//这里获取 Continuation 的泛型,也是协程的返回类型
/**
* 看下 kt 的字节码就明白了,如
* interface MyApi {
* suspend fun login():BaseResponse<String>
* }
* 对应字节码为
* // access flags 0x401
* // signature (Lkotlin/coroutines/Continuation<-Lcom/goach/retrofit/code/BaseResponse<Ljava/lang/String;>;>;)Ljava/lang/Object;
* // declaration: login(kotlin.coroutines.Continuation<? super com.goach.retrofit.code.BaseResponse<java.lang.String>>)
* public abstract login(Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
* @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/goach/retrofit/code/MyApi; L0 L1 0
*
* @Lkotlin/Metadata;(mv={1, 4, 2}, 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\u0010\u000e\n\u0002\u0008\u0002\u0008f\u0018\u00002\u00020\u0001J\u0017\u0010\u0002\u001a\u0008\u0012\u0004\u0012\u00020\u00040\u0003H\u00a6@\u00f8\u0001\u0000\u00a2\u0006\u0002\u0010\u0005\u0082\u0002\u0004\n\u0002\u0008\u0019\u00a8\u0006\u0006"}, d2={"Lcom/goach/retrofit/code/MyApi;", "", "login", "Lcom/goach/retrofit/code/BaseResponse;", "", "(Lkotlin/coroutines/Continuation;)Ljava/lang/Object;", "app_debug"})
* // compiled from: SuspendTest.kt
* }
*/
Type responseType =
Utils.getParameterLowerBound(
0, (ParameterizedType) parameterTypes[parameterTypes.length - 1]);
if (getRawType(responseType) == Response.class && responseType instanceof ParameterizedType) {
// Unwrap the actual body type from Response<T>.
//协程返回类型类型 Response<T>
responseType = Utils.getParameterUpperBound(0, (ParameterizedType) responseType);
continuationWantsResponse = true;
} else {
// TODO figure out if type is nullable or not
// Metadata metadata = method.getDeclaringClass().getAnnotation(Metadata.class)
// Find the entry for method
// Determine if return type is nullable or not
}
//responseType 是协程返回的数据类型,由于 Retrofit 默认的是 Call<?> 返回类型,
// 所以这里为协程创建了一个 Call<responseType> 的类型
adapterType = new Utils.ParameterizedTypeImpl(null, Call.class, responseType);
//这里给协程方法添加一个 SkipCallbackExecutor 注解,有这个注解的时候,后面调用 adapt 时候不会再创建 ExecutorCallbackCall
annotations = SkipCallbackExecutorImpl.ensurePresent(annotations);
} else {
adapterType = method.getGenericReturnType();
}
//获取配置的 CallAdapter,这里有3种工厂分别创建的不同的 CallAdapter,DefaultCallAdapterFactory 直接 new CallAdapter,RxJava2CallAdapterFactory 对应 RxJava2CallAdapter,
//CompletableFutureCallAdapterFactory 对应 ResponseCallAdapter,如果不配置,就默认使用的是 DefaultCallAdapterFactory,如果配置了,Builder 配置的放在最前面,所以优先匹配配置的,匹配不到再使用
//DefaultCallAdapterFactory。另外 CallAdapter 的 adapt 方法就是去 OkHttp 请求的方法了。
CallAdapter<ResponseT, ReturnT> callAdapter =
createCallAdapter(retrofit, method, adapterType, annotations);
Type responseType = callAdapter.responseType();
if (responseType == okhttp3.Response.class) {
throw methodError(
method,
"'"
+ getRawType(responseType).getName()
+ "' is not a valid response body type. Did you mean ResponseBody?");
}
if (responseType == Response.class) {
throw methodError(method, "Response must include generic type (e.g., Response<String>)");
}
// TODO support Unit for Kotlin?
if (requestFactory.httpMethod.equals("HEAD") && !Void.class.equals(responseType)) {
throw methodError(method, "HEAD method must use Void as response type.");
}
//获取数据解析的转换器,这里我们一般配置 GsonConverterFactory,获取到 GsonResponseBodyConverter
Converter<ResponseBody, ResponseT> responseConverter =
createResponseConverter(retrofit, method, responseType);
okhttp3.Call.Factory callFactory = retrofit.callFactory;
if (!isKotlinSuspendFunction) {
//不是协程方法,创建 CallAdapted,它继承 HttpServiceMethod,
//这里 requestFactory 是指 RequestFactory,在 ServiceMethod 里面创建的,
// callFactory 这里默认是 OkHttpClient,在 Retrofit.Builder 会赋值
return new retrofit2.HttpServiceMethod.CallAdapted<>(requestFactory, callFactory, responseConverter, callAdapter);
} else if (continuationWantsResponse) {
//返回数据类型为 Call<Response<T>>
//noinspection unchecked Kotlin compiler guarantees ReturnT to be Object.
return (retrofit2.HttpServiceMethod<ResponseT, ReturnT>)
new retrofit2.HttpServiceMethod.SuspendForResponse<>(
requestFactory,
callFactory,
responseConverter,
(CallAdapter<ResponseT, Call<ResponseT>>) callAdapter);
} else {
// 协程,并且返回类型不是 Call<Response<T>>
//noinspection unchecked Kotlin compiler guarantees ReturnT to be Object.
return (retrofit2.HttpServiceMethod<ResponseT, ReturnT>)
new retrofit2.HttpServiceMethod.SuspendForBody<>(
requestFactory,
callFactory,
responseConverter,
(CallAdapter<ResponseT, Call<ResponseT>>) callAdapter,
continuationBodyNullable);
}
}
}
通过上面代码我们知道,它分为非协程类型和协程类型,协程类型里面又分返回类型是否是 Response 的。不同类型分别对应着不同的 ServiceMethod.
- 非协程 -> CallAdapted
- 协程 Response 返回类型 -> SuspendForResponse
- 协程非 Response 返回类型 -> SuspendForBody
这三种 ServiceMethod 类型的创建,都需要首先创建 RequestFactory,CallFactory,ResponseConverter,CallAdapter。
- RequestFactory: 创建 OkHttp 的 Request 作用,在 OkHttpCall 的 createRawCall 会调用该 create 方法
- CallFactory :这里默认是 OkHttpClient,在 Retrofit.Builder 里面赋值的
- ResponseConverter, 解析数据时候用到,如 GsonConverterFactory 工厂,它里面的 requestBodyConverter 方法可以获取到 GsonRequestBodyConverter
- CallAdapter 里面的 adapt 方法会去真正调用 OkHttp 的请求方法进行网络请求。
所以 ServiceMethod 类是 Retrofit 核心类,它保存着每个接口的注解,参数,返回数据类型等等配置信息,还有解析数据类型的方法,调用网络请求的方法。
Retrofit 是如何切换回 UI 线程的?
- RxJava2CallAdapterFactory
如果配置的是 RxJava2CallAdapterFactory,那么 RxJava2 自带线程切换,无需处理,通过 observableOn 就可以切换回 UI 线程
- DefaultCallAdapterFactory
这是我们未配置 CallAdapter 工厂的时候使用的默认工厂,先看下里面 get 方法源码:
public class DefaultCallAdapterFactory {
@Override
public @Nullable CallAdapter<?, ?> get(
Type returnType, Annotation[] annotations, Retrofit retrofit) {
//...省略
final Executor executor =
Utils.isAnnotationPresent(annotations, SkipCallbackExecutor.class)
? null
: callbackExecutor;
return new CallAdapter<Object, Call<?>>() {
@Override
public Type responseType() {
return responseType;
}
@Override
public Call<Object> adapt(Call<Object> call) {
return executor == null ? call : new retrofit2.DefaultCallAdapterFactory.ExecutorCallbackCall<>(executor, call);
}
};
}
}
当我们使用的非协程的时候,会发现它创建了 ExecutorCallbackCall。
static final class ExecutorCallbackCall<T> implements Call<T> {
final Executor callbackExecutor;
final Call<T> delegate;
ExecutorCallbackCall(Executor callbackExecutor, Call<T> delegate) {
this.callbackExecutor = callbackExecutor;
this.delegate = delegate;
}
@Override
public void enqueue(final Callback<T> callback) {
Objects.requireNonNull(callback, "callback == null");
delegate.enqueue(
new Callback<T>() {
@Override
public void onResponse(Call<T> call, final Response<T> response) {
callbackExecutor.execute(
() -> {
if (delegate.isCanceled()) {
// Emulate OkHttp's behavior of throwing/delivering an IOException on
// cancellation.
callback.onFailure(retrofit2.DefaultCallAdapterFactory.ExecutorCallbackCall.this, new IOException("Canceled"));
} else {
callback.onResponse(retrofit2.DefaultCallAdapterFactory.ExecutorCallbackCall.this, response);
}
});
}
@Override
public void onFailure(Call<T> call, final Throwable t) {
callbackExecutor.execute(() -> callback.onFailure(retrofit2.DefaultCallAdapterFactory.ExecutorCallbackCall.this, t));
}
});
}
}
那这个 callbackExecutor 是谁呢,在 Retrofit 的 builder 方法会发现它拿的 Platform 的 defaultCallbackExecutor ,在 Android 平台,实现它的是 MainThreadExecutor:
static final class MainThreadExecutor implements Executor {
private final Handler handler = new Handler(Looper.getMainLooper());
@Override
public void execute(Runnable r) {
handler.post(r);
}
}
可以发现 DefaultCallAdapterFactory 在非协程的时候就是使用的 Handler 切换到 UI 线程的。
那协程情况下呢?在上面 adapt 方法里面,如果协程,那么它会直接拿到 OkHttp 的 Call,然后调用 OkHttp 的异步方法进行请求,当请求回来恢复就好了,具体见 KotlinExtensions 里面的 await 方法,它在 SuspendForBody 里面的 adapt 方法里面会调用。
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)
}
})
}
}
这个泛型就是协程返回的类型,Call 是 Retrofit 前面帮我们包装的,成功时候调用 resume 就好了,因为协程如果会帮我们通过 Handler 方式切换到 UI 线程。
另外 suspendCancellableCoroutine,这也是协程里面把回调方法转换为协程的一种方法。比如如果用了 RxJava 有些回调在过渡中,那我们处理业务时候也可以使用这种方式来转换成协程来使用。
Retrofit 是如何支持协程的?
在上文 parseAnnotations 方法里面有提到协程的情况,这里总结下
- 获取参数 Continuation 的泛型,也是协程返回的数据类型
- 创建协程的返回类型为 Call,T 就是数据返回的类型
- 协程会使用 DefaultCallAdapterFactory 工厂,并且在 adapt 方法返回Retrofit 的 Call 对象
- 根据协程返回类型创建 ServiceMethod,分别为 SuspendForResponse 和 SuspendForBody。它两的区别就是 SuspendForResponse 返回的数据类型包裹一层 Response
- 通过 suspendCancellableCoroutine 的 resume 响应协程的返回结果。
Retrofit 是如何解析数据的?
无论是否使用了协程,最终异步网络请求都会来到 OkHttpCall 里面的 enquene 方法,里面会调用 OkHttp 的 Call 里面的 enqueue 方法得到请求结果 Response.
public class OkHttpCall {
@Override
public void enqueue(final Callback<T> callback) {
//省略...
call.enqueue(
new okhttp3.Callback() {
@Override
public void onResponse(okhttp3.Call call, okhttp3.Response rawResponse) {
Response<T> response;
try {
response = parseResponse(rawResponse);
} catch (Throwable e) {
}
}
});
}
Response<T> parseResponse(okhttp3.Response rawResponse) throws IOException {
//... 省略
try {
T body = responseConverter.convert(catchingBody);
return Response.success(body, rawResponse);
} catch (RuntimeException e) {
}
}
}
这里面会调用 parseResponse。然后调用对应 responseConverter 里面的 convert 方法。如果配置的是 GsonConverterFactory 就会来到 GsonResponseBodyConverter 的 convert 方法
public T convert(ResponseBody value) throws IOException {
JsonReader jsonReader = gson.newJsonReader(value.charStream());
try {
T result = adapter.read(jsonReader);
if (jsonReader.peek() != JsonToken.END_DOCUMENT) {
throw new JsonIOException("JSON document was not fully consumed.");
}
return result;
} finally {
value.close();
}
}
通过 OkHttp 的 ResponseBody 拿到 Reader。然后通过反序列化为数据类型赋值。那么,为什么这里不直接使用 fromJson 呢?看下 fromJson 源码会发现不同之处
public <T> T fromJson(String json, Type typeOfT) throws JsonSyntaxException {
if (json == null) {
return null;
}
StringReader reader = new StringReader(json);
T target = (T) fromJson(reader, typeOfT);
return target;
}
public final Reader charStream() {
Reader r = reader;
return r != null ? r : (reader = new BomAwareReader(source(), charset()));
}
Gson 使用的是 StringReader,而通过 OkHttp 获取的是 BomAwareReader,它们的区别是,BomAwareReader 可以识别 BOM 字节码顺序标记具体采用哪种编码格式。还一个区别就是
private static void assertFullConsumption(Object obj, JsonReader reader) {
try {
if (obj != null && reader.peek() != JsonToken.END_DOCUMENT) {
throw new JsonIOException("JSON document was not fully consumed.");
}
} catch (MalformedJsonException e) {
throw new JsonSyntaxException(e);
} catch (IOException e) {
throw new JsonIOException(e);
}
}
Gson 的 fromJson 会 try catch 住 MalformedJsonException 和 IOException 异常,转换为 gson 自己的异常,而 Retrofit 并没有特殊处理这 2 个异常。
Retrofit 是什么时候创建 OkHttp Call 和 Request 的?
Retrofit 会在 create 方法获取到 serviceMethod 后调用 serviceMethod 的 invoke 方法,这个方法里面会创建 OkHttpCall 对象。这里面同样提供了和 OkHttp Call 同名的 enqueue 方法。在这个方法里面
public class OkHttpCall {
@GuardedBy("this")
private @Nullable okhttp3.Call rawCall;
public void enqueue(final Callback<T> callback) {
synchronized (this) {
call = rawCall;
failure = creationFailure;
if (call == null && failure == null) {
try {
call = rawCall = createRawCall();
} catch (Throwable t) {
}
}
}
}
}
通过 createRawCall 方法就会调用 OkHttpClient 的 newCall 方法创建。这里对 Call 进行了判空,也就是一个 OkHttpCall 对应一个 Call 对象,不会出现多次调用 enqueue 方法导致多个 Call 对象。同时也使用了懒加载的方式,在第一次调用接口请求的时候再创建 Call。
private okhttp3.Call createRawCall() throws IOException {
okhttp3.Call call = callFactory.newCall(requestFactory.create(args));
if (call == null) {
throw new NullPointerException("Call.Factory returned null.");
}
return call;
}
而 Request 是在 createRawCall 里面调用 RequestFactory 的 create 方法创建的。这样 Retrofit 创建了 Call 和 Request ,可以调用 OkHttp 的 enqueue 方法进行网络请求了。
也就是多次调用一个接口进行请求,就会多次执行 invoke 方法,所以就会创建多个对应的 OkHttpCall,和多个 Request.而 Call 和 Request 在 enqueue 初始化,仅仅只是为了懒加载。
总结
最后总结下,整个流程走下来就是:
好了,Retrofit 就暂时学习到这里。