Retrofit turns your HTTP API into a Java interface.
我们先看一段 OkHttp 官方的示例代码:
// From: https://square.github.io/okhttp/#post-to-a-server
public static final MediaType JSON
= MediaType.get("application/json; charset=utf-8");
OkHttpClient client = new OkHttpClient();
String post(String url, String json) throws IOException {
RequestBody body = RequestBody.create(json, JSON);
Request request = new Request.Builder()
.url(url)
.post(body)
.build();
try (Response response = client.newCall(request).execute()) {
return response.body().string();
}
}
上面的代码是不是朴素无华?一看就懂。
我们再来看看 Retrofit 的示例代码:
-
定义Service接口类,跟服务端的Restful APIs一一对应:
// Refer: https://developer.github.com/v3/ public interface GitHubService { @GET("/users/{user}") Call<UserProfile> userProfile(@Path("user") String user); @GET("users/{user}/repos") Call<List<Repo>> listRepos(@Path("user") String user); }
-
执行网络请求:
Retrofit retrofit = new Retrofit.Builder() .baseUrl("https://api.github.com/") .addConverterFactory(GsonConverterFactory.create()) .build(); GitHubService service = retrofit.create(GitHubService.class); Call<List<Repo>> reposCall = service.listRepos("yongce"); List<Repo> repos = reposCall.execute().body(); // 执行网络请求
-
还支持 RxJava:
public interface GitHubService { @GET("users/{user}/repos") Observable<List<Repo>> listRepos(@Path("user") String user); }
也支持Kotlin协程的 supsend 关键字:
interface GitHubService { @GET("users/{user}/repos") suspend fun listRepos(@Path("user") user: String): List<Repo> } // Called on main thread suspend fun refreshRepos() { val retrofit = Retrofit.Builder() .baseUrl("https://api.github.com/") .addConverterFactory(GsonConverterFactory.create()) .build() val service: GitHubService = retrofit.create() val repos: List<Repo> = service.listRepos("yongce") // use 'repos' to update UI directly... }
当第一眼看到上面的 Retrofit 示例代码时,是不是一下来了精神,心中的问号越来越多? 大家一定很好奇,这个魔法是怎么实现的,其技术原理如何? 但并不是每个人都愿意去分析源码,不论是因为没有时间,还是因为过于枯燥、耗时、乏味。 本文将为你呈现 Retrofit2 背后的技术原理和实现细节,不论你是仅想知道其技术原理, 还是也想自己去分析源码,都可以从本文有所收获。
本文的源码解析将分为如下几个部分:
-
技术原理
-
设计模式
-
实现细节
当前 Retrofit2 源码使用 Gradle 来构建,你在 IntelliJ IDEA 或者 Android Studio 中直接导入源码即可。
📎
|
本文所分析的源码版本如下: $ git show HEAD commit 65de04d1da61f9e8124956f6fff8902fc81d05e2 Merge: 9e597f2 56e15f8 Author: Jake Wharton <github@jakewharton.com> Date: Wed Jun 17 23:18:03 2020 -0400 Merge pull request #3424 from clydebarrow/robovm Prevent use of Java 8 classes on RoboVM |
💡
|
如果之前没有接触过 Retrofit 库,需要先花点时间去看看 Retrofit官方文档 , 并运行一些示例代码直观感受下。 |
当我们在思考 Retrofit 背后的技术原理时,可能会提出如下一些问题:
-
下面这段代码的背后发生了什么?Service接口类的对象是如何被创建出来的?
GitHubService service = retrofit.create(GitHubService.class);
-
下面这段对Service接口类方法的调用,发生了什么事情?返回值似乎跟 OkHttp 的Call作用相似?
Call<List<Repo>> reposCall = service.listRepos("yongce");
-
下面这段代码似乎与OkHttp的Call相似?这里才真正执行网络请求?
List<Repo> repos = reposCall.execute().body();
-
Service接口类中的方法返回值还可以是其它的?是如何支持其它类型的?
Observable<List<Repo>> listRepos(@Path("user") String user);
-
suspend函数似乎有点不一样?直接执行了网络请求,而不是返回一个类似Call的中间对象? 按照Kotlin suspend函数的实现惯例,应该可以直接在main线程调用而不会阻塞main线程?
suspend fun listRepos(@Path("user") user: String): List<Repo>
-
Converter.Factory 和 CallAdapter.Factory 是如何工作的?
Retrofit retrofit = new Retrofit.Builder() .baseUrl("https://api.github.com/") .addConverterFactory(GsonConverterFactory.create()) .addCallAdapterFactory(RxJavaCallAdapterFactory.createWithScheduler(io())) .build();
-
Retrofit 是如何把网络请求转发给 OkHttp 的?
-
Retrofit 是如何处理Service接口类中的那些注解的?
-
Retrofit 整个工作流程是怎样的?主要有哪些步骤?
我们先来寻找下面代码的答案:
GitHubService service = retrofit.create(GitHubService.class);
当我们去查看 Retrofit 源码时,很快就能发现上面代码背后的真相:
public <T> T create(final Class<T> service) {
validateServiceInterface(service);
return (T)
Proxy.newProxyInstance(
service.getClassLoader(),
new Class<?>[] {service},
new InvocationHandler() {
...省略代码
});
}
原来是通过Java动态代理技术来创建Service接口类的对象的。
此时,似乎Retrofit背后的整个蓝图有点轮廓了?
-
通过Java动态代理创建出Service接口类对象。
-
(猜测)在InvocationHandler中处理Service接口类的方法调用时,解析该方法的函数签名和注解, 根据调用传入的实参,构造出OkHttp网络请求所需要的参数(如URL、request body、headers等), 进一步构造出 okhttp3.Request 对象,然后通过 OkHttpClient#newCall() 构造出 okhttp3.Call 对象。
-
(猜测)根据Service接口类方法的返回类型,对 okhttp3.Call 对象进行处理:
-
转换为 retrofit2.Call 对象,并返回;
-
转换为 RxJava 的 Observerable 对象,并返回;
-
立即在后台开始执行网络请求,并将当前Kotlin协程挂起,等网络请求返回后恢复当前协程;
-
如何支持其它返回值类型?显然 Retrofit 自身不可能支持所有返回类型。
-
我们继续看 Retrofit 中 InvocationHandler 的实现,其最终是需要调用 ServiceMethod#parseAnnotations() 方法来解析当前Service接口类方法, 并把解析的结果(ServiceMethod对象)保存在缓存中,供下次直接使用。 然后,调用该ServiceMethod对象的 invoke() 方法来最终完成本次InvocationHandler的处理。
ServiceMethod 类非常简单:
abstract class ServiceMethod<T> {
static <T> ServiceMethod<T> parseAnnotations(Retrofit retrofit, Method method) {
RequestFactory requestFactory = RequestFactory.parseAnnotations(retrofit, method);
...省略代码
return HttpServiceMethod.parseAnnotations(retrofit, method, requestFactory);
}
abstract @Nullable T invoke(Object[] args);
}
从上面的代码可以看到,ServiceMethod类把方法解析的工作委托给了 RequestFactory#parseAnnotations() 和 HttpServiceMethod#parseAnnotations(),而 #invoke() 的实现交给了子类去完成(即 HttpServiceMethod类)。
ServiceMethod只有一个直接子类 HttpServiceMethod
abstract class HttpServiceMethod<ResponseT, ReturnT> extends ServiceMethod<ReturnT> {
...省略代码
@Override
final @Nullable ReturnT invoke(Object[] args) {
Call<ResponseT> call = new OkHttpCall<>(requestFactory, args, callFactory, responseConverter);
return adapt(call, args);
}
protected abstract @Nullable ReturnT adapt(Call<ResponseT> call, Object[] args);
...省略代码
}
在上面的代码可以看到,HttpServiceMethod#invoke() 先构造出一个 OkHttpCall 对象, 然后通过 HttpServiceMethod#adapt() 抽象方法把Call对象转换为Service接口类的返回类型对象。 似乎署光就在眼前!
HttpServiceMethod有3个子类:
-
CallAdapted:支持 retrofit.Call<T>、Java 8的CompletableFuture<T>、RxJava的Obserable等返回类型
-
SuspendForResponse:支持suspend函数,返回类型为 Response<T>
-
SuspendForBody:支持suspend函数,返回类型为 T
让我们把前面的技术点串联在一起,看看Retrofit背后的技术原理全貌:
-
通过Java动态代理技术,创建出Service接口类对象。
-
当该Service接口类对象的方法被调用时(假设返回值类型为ReturnT),
-
解析该方法的函数签名及注解,结合Retrofit对象中的baseUrl参数,构建出此HTTP请求的相关参数。 在构建HTTP参数时,会通过Retrofit对象中的Converter.Factory,对参数进行必要的转换(例如,把Java对象转换为JSON对象)。
-
使用前面构建出的HTTP参数,进一步构建出OkHttp的Call对象。
-
通过Retrofit对象中的CallAdapter.Factory,把OkHttp的Call对象转换为ReturnT类型的对象。
-
此时,根据是否是suspend函数或者ReturnT的具体类型,有两种结果:
-
HTTP网络请求立即执行(例如,Kotlin suspend函数,Java 8的CompletableFuture)。
-
HTTP网络请求将由返回的ReturnT对象的某个操作触发(例如,retrofit2.Call、RxJava的Observable)。
-
-
-
当Service接口类对象的方法调用返回时,有下列情况:
-
已经拿到服务器的返回结果(如,Kotlin suspend函数)。
-
HTTP网络请求已经开始执行,正在等待结果返回的通知(如,Java 8的CompletableFuture)。
-
HTTP网络请求未开始,由返回对象的进一步操作来触发(如,retrofit2.Call、RxJava的Observable)。
-
-
当HTTP请求结果返回时,也会通过Retrofit对象中的Converter.Factory,对返回结果进行转换 (例如,把JSON字符串转换为Java对象)。
-
通过自定义CallAdapter.Factory,并把它的对象添加到Retrofit对象中,即可支持Service接口类方法的自定义返回类型。
-
通过自定义Converter.Factory,并把它的对象添加到Retrofit对象中,即可支持自定义类型、注解的类型转换 (Java对象和String、RequestBody、ResponseBody之间的转换)。
在 Retrofit#create() 方法的实现中,会先调用 Retrofit#validateServiceInterface() 去验证传入的Service接口类是否合法。
默认情况下,只会验证Service接口类本身是否合法(包括其继承的interface), 例如,是否是interface,是否有泛型(不允许泛型); 而不会验证Service接口类中的方法是否合法,此时只会在该方法第一次被调用时才进行解析并验证。
可以调用 Retrofit.Builder#validateEagerly() 来强制启用对方法的解析和验证。 但这样做,可能有性能损耗,Retrofit#create()调用耗时也会更长,需要注意一下。
Retrofit 对 Kotlin 的支持主要是指在Service接口类中支持suspend函数, 见前面『引子』部分的示例代码。
要在Service接口类中支持suspend函数,需要解决如下问题:
-
识别Service接口类中的suspend函数
-
支持suspend函数的调用
我们知道,suspend函数在编译后,会在函数的参数列表最后增加一个 kotlin.coroutines.Continuation 类型的参数。 Retrofit就是利用这个特点来识别suspend函数的,相应代码在 retrofit2.RequestFactory.Builder#parseParameter() 这个方法中:
if (allowContinuation) {
try {
if (Utils.getRawType(parameterType) == Continuation.class) {
isKotlinSuspendFunction = true;
return null;
}
} catch (NoClassDefFoundError ignored) {
}
}
在 retrofit2.HttpServiceMethod#parseAnnotations() 方法中, 会根据是否为suspend函数来决定创建哪个 HttpServiceMethod 子类对象:
if (!isKotlinSuspendFunction) {
return new CallAdapted<>(requestFactory, callFactory, responseConverter, callAdapter);
} else if (continuationWantsResponse) {
return (HttpServiceMethod<ResponseT, ReturnT>)
new SuspendForResponse<>(
requestFactory,
callFactory,
responseConverter,
(CallAdapter<ResponseT, Call<ResponseT>>) callAdapter);
} else {
return (HttpServiceMethod<ResponseT, ReturnT>)
new SuspendForBody<>(
requestFactory,
callFactory,
responseConverter,
(CallAdapter<ResponseT, Call<ResponseT>>) callAdapter,
continuationBodyNullable);
}
如果当前Service接口类的方法为suspend函数,那么对其调用会最终
在 KotlinExtensions.kt 文件中,定义了多个扩展函数, 主要供前面的 SuspendForResponse 和 SuspendForBody 这两个类来调用:
inline fun <reified T> Retrofit.create(): T = create(T::class.java)
suspend fun <T : Any> Call<T>.await(): T
suspend fun <T : Any> Call<T?>.await(): T?
suspend fun <T> Call<T>.awaitResponse(): Response<T>
💡
|
作为 Retrofit 库的使用者,我们应该只会用到 Retrofit.create() 这个内联函数。 |
在构建Retrofit对象时,可以为其指定Callback Executor。如果没有指定,那么:
-
Android平台会使用默认的 MainThreadExecutor(主线程执行回调)
-
其它平台无Callback Executor
Callback Executor 用于控制 retrofit2.Call#enqueue() 在回调callback参数时在哪个线程执行 (参见 retrofit2.DefaultCallAdapterFactory.ExecutorCallbackCall类 的实现)。
Retrofit内置的 Converter.Factory 为 retrofit2.BuiltInConverters 。 如果当前平台支持Java 8 API(如Java 8+, Android N+),还有一个额外的 OptionalConverterFactory 。
retrofit2.BuiltInConverters#responseBodyConverter()支持如下类型:
-
okhttp3.ResponseBody(此类型支持 @retrofit2.http.Streaming)
-
Void
-
kotlin.Unit
💡
|
需要添加库依赖:com.squareup.retrofit2:converter-scalars:${versions.retrofit} |
ScalarsConverterFactory 的 #requestBodyConverter() 和 #responseBodyConverter() 支持Java的基础类型(如String, boolean, Boolean, long, Long等)。
public interface CallAdapter<R, T> {
Type responseType();
T adapt(Call<R> call);
}
CallAdapter 用于将 Call<R> 类型对象转换为 T 类型对象(这个T本身也可以是Call<R>, 见 DefaultCallAdapterFactory 实现的 CallAdapter)。
Retrofit内置了两个 CallAdapter.Factory :
-
DefaultCallAdapterFactory:仅支持 retrofit2.Call<T> 返回类型
-
CompletableFutureCallAdapterFactory:有两个 CallAdapter ,分别支持如下返回类型:
-
CompletableFuture<T>
-
CompletableFuture<retrofit2.Response<T>>
-
在Retrofit中,按网络请求发起时机,Service接口类方法可分为两类:
-
Service接口类方法被调用时立即发起网络请求:
-
CompletableFutureCallAdapterFactory 支持的 CompletableFuture<T> 返回类型
-
GuavaCallAdapterFactory 支持的 ListenableFuture<T> 返回类型
-
ScalaCallAdapterFactory 支持的 Future<T> 返回类型
-
kotlin suspend函数
-
-
Service接口类方法返回对象的某个方法被调用时才会发起网络请求:
-
DefaultCallAdapterFactory 支持的 Call<T> 返回类型
-
RxJava3CallAdapterFactory 等RxJava系列Factory支持的 Observable<T>等返回类型
-