1 Retrofit简单介绍和用法示例
1.1 Retrofit简介
Retrofit是Square公司推出的一个Http客户端开源框架,主要用于Android和Java应用程序。其核心的理念是将Http的网络接口转化为对应的Java/Kotlin接口,借鉴Java开发中的REST风格注解,将网络请求的方法参数和接口入参对应成一个个注解和注解入参。Retrofit具备如下优点(项目的地址github.com/square/retr...
- REST风格的基于注解的java接口定义Http网络接口和网络接口入参
- Retrofit在编译时通过注解器和运行时确保类型安全
- 强大的数据转化能力,通过Converter机制把请求体转为Java/Kotlin对象,并将返回体解析为对应的对象
- 灵活的CallAdapter适配器模式,使得接口不仅限于基础的Call类型,可以无缝衔接如协程、Rxjava等异步编程体系
- 与OkHttp比,不需要处理线程切换了,回调切换到主线程调用
1.2 Retrofit使用简介
首先还是库的导入工作,根据上面提供的项目地址可以看到,需要在gradle中导入如下库:
kotlin
implementation("com.squareup.retrofit2:retrofit:3.0.0")
// 选择性导入,根据项目需求导入对应的converter
implementation("com.squareup.retrofit2:retrofit:converter-gson:3.0.0")
然后通过build模式创建Retrofit实例(实际可以添加很多自定义的项目配置项):
kotlin
val retrofit = new Retrofit.Builder()
.baseUrl("https://api.github.com")
.build()
通过Kotlin接口定义我们的网络接口:
kotlin
interface GitHubService {
@GET("users/{user}/repos")
fun listRepos(@Path("user") String user):Call<List<Repo>>
}
根据前面创建的Retrofit实例和定义的GitHubService接口可以开始真正的网络接口执行部分了:
kotlin
val service = retrofit.create(GitHubService.class)
service.listRepos("UserName").enqueue(object : Callback<List<Repo>> {
override fun onResponse(call: Call<List<Repo>>, response: Response<List<Repo>>) {
// 数据成功回调(主线程中)
}
override fun onFailure(call: Call<List<Repo>>, t: Throwable) {
// error 处理逻辑
}
})
可以看到当我们定义的网络接口返回为Call类型时,实际就是以OkHttp的方式进行回调处理。
2 Retrofit源码实现分析
2.1 网络接口请求主流程分析
从1.2的使用介绍中可以看到retrofit2.Retrofit#create方法真正返回了GitHubService接口实例,后续的接口请求也是依赖于GitHubService实例进行的,那么先看看这个create方法里面做了些什么吧:
java
public <T> T create(final Class<T> service) {
validateServiceInterface(service);
return (T)
Proxy.newProxyInstance(
service.getClassLoader(),
new Class<?>[] {service},
new InvocationHandler() {
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);
}
args = args != null ? args : emptyArgs;
Reflection reflection = Platform.reflection;
return reflection.isDefaultMethod(method)
? reflection.invokeDefaultMethod(method, service, proxy, args)
: loadServiceMethod(service, method).invoke(proxy, args);
}
});
}
- 可以看到create方法的入参为Class,返回的类型为T,其中一共做了两个处理,首先是retrofit2.Retrofit#validateServiceInterface,然后动态代理传入的接口类,接下来先看看validateServiceInterface里面做了些什么:
java
private void validateServiceInterface(Class<?> service) {
// a、入参service类型检查不为接口直接抛异常
if (!service.isInterface()) {
throw new IllegalArgumentException("API declarations must be interfaces.");
}
Deque<Class<?>> check = new ArrayDeque<>(1);
check.add(service);
// b、循环检查传入的Service类型及其实现类的参数是否存在模板参数,有就抛出异常
while (!check.isEmpty()) {
Class<?> candidate = check.removeFirst();
if (candidate.getTypeParameters().length != 0) {
StringBuilder message =
new StringBuilder("Type parameters are unsupported on ").append(candidate.getName());
if (candidate != service) {
message.append(" which is an interface of ").append(service.getName());
}
throw new IllegalArgumentException(message.toString());
}
Collections.addAll(check, candidate.getInterfaces());
}
if (validateEagerly) {
Reflection reflection = Platform.reflection;
for (Method method : service.getDeclaredMethods()) {
if (!reflection.isDefaultMethod(method)
&& !Modifier.isStatic(method.getModifiers())
&& !method.isSynthetic()) {
// c、加载服务方法
loadServiceMethod(service, method);
}
}
}
}
2.根据流程1中的注释,完成必要的入参检查后,我们看看流程1的注释c处retrofit2.Retrofit#loadServiceMethod方法中做了些什么:
java
ServiceMethod<?> loadServiceMethod(Class<?> service, Method method) {
while (true) {
// Note: Once we are minSdk 24 this whole method can be replaced by computeIfAbsent.
// a、尝试从serviceMethodCache缓存中取,取到的是ServiceMethod对象直接返回
Object lookup = serviceMethodCache.get(method);
if (lookup instanceof ServiceMethod<?>) {
// Happy path: method is already parsed into the model.
return (ServiceMethod<?>) lookup;
}
if (lookup == null) {
// Map does not contain any value. Try to put in a lock for this method. We MUST synchronize
// on the lock before it is visible to others via the map to signal we are doing the work.
Object lock = new Object();
synchronized (lock) {
// b、将锁对象存入serviceMethodCache中
lookup = serviceMethodCache.putIfAbsent(method, lock);
if (lookup == null) {
// On successful lock insertion, perform the work and update the map before releasing.
// Other threads may be waiting on lock now and will expect the parsed model.
ServiceMethod<Object> result;
try {
// c、解析方法注解
result = ServiceMethod.parseAnnotations(this, service, method);
} catch (Throwable e) {
// Remove the lock on failure. Any other locked threads will retry as a result.
serviceMethodCache.remove(method);
throw e;
}
// d、更新缓存serviceMethodCache中注解解析结果
serviceMethodCache.put(method, result);
return result;
}
}
}
synchronized (lookup) {
// e、lookup不为空,表示serviceMethodCache缓存中已经有方法的解析结果了,检查缓存中结果
Object result = serviceMethodCache.get(method);
if (result == null) {
// The other thread failed its parsing. We will retry (and probably also fail).
continue;
}
return (ServiceMethod<?>) result;
}
}
}
- 从流程2中可以看出来,关键点在于注释c处的retrofit2.ServiceMethod#parseAnnotations方法的解析结果,接下来跟进parseAnnotations方法中,其实现如下:
java
static <T> ServiceMethod<T> parseAnnotations(Retrofit retrofit, Class<?> service, Method method) {
// a、解析Http方法的注解
RequestFactory requestFactory = RequestFactory.parseAnnotations(retrofit, service, 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.");
}
// b、返回解析后的Http接口方法
return HttpServiceMethod.parseAnnotations(retrofit, method, requestFactory);
}
- 流程3处的代码注释a处,retrofit2.RequestFactory#parseAnnotations会解析我们写的接口的方法注解和方法参数注解,生成RequestFactory并传入retrofit2.HttpServiceMethod#parseAnnotations方法中,看来最重要的部分都留在了流程3的注释b处,继续跟进这个方法看看其内部实现:
java
static <ResponseT, ReturnT> HttpServiceMethod<ResponseT, ReturnT> parseAnnotations(
Retrofit retrofit, Method method, RequestFactory requestFactory) {
boolean isKotlinSuspendFunction = requestFactory.isKotlinSuspendFunction;
boolean continuationWantsResponse = false;
boolean continuationBodyNullable = false;
boolean continuationIsUnit = false;
Annotation[] annotations = method.getAnnotations();
Type adapterType;
if (isKotlinSuspendFunction) {
Type[] parameterTypes = method.getGenericParameterTypes();
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>.
responseType = Utils.getParameterUpperBound(0, (ParameterizedType) responseType);
continuationWantsResponse = true;
} else {
if (getRawType(responseType) == Call.class) {
throw methodError(
method,
"Suspend functions should not return Call, as they already execute asynchronously.\n"
+ "Change its return type to %s",
Utils.getParameterUpperBound(0, (ParameterizedType) responseType));
}
continuationIsUnit = Utils.isUnit(responseType);
}
adapterType = new Utils.ParameterizedTypeImpl(null, Call.class, responseType);
annotations = SkipCallbackExecutorImpl.ensurePresent(annotations);
} else {
adapterType = method.getGenericReturnType();
}
// a、生成CallAdapter适配器实例
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>)");
}
if (requestFactory.httpMethod.equals("HEAD")
&& !Void.class.equals(responseType)
&& !Utils.isUnit(responseType)) {
throw methodError(method, "HEAD method must use Void or Unit as response type.");
}
// b、生成Convert转化器实例
Converter<ResponseBody, ResponseT> responseConverter =
createResponseConverter(retrofit, method, responseType);
okhttp3.Call.Factory callFactory = retrofit.callFactory;
if (!isKotlinSuspendFunction) {
// c、非协程方式,生成Call适配器对象实例
return new CallAdapted<>(requestFactory, callFactory, responseConverter, callAdapter);
} else if (continuationWantsResponse) {
//noinspection unchecked Kotlin compiler guarantees ReturnT to be Object.
// d、协程式的Respone适配器实例
return (HttpServiceMethod<ResponseT, ReturnT>)
new SuspendForResponse<>(
requestFactory,
callFactory,
responseConverter,
(CallAdapter<ResponseT, Call<ResponseT>>) callAdapter);
} else {
//noinspection unchecked Kotlin compiler guarantees ReturnT to be Object.
// e、协程式的Body适配器实例
return (HttpServiceMethod<ResponseT, ReturnT>)
new SuspendForBody<>(
requestFactory,
callFactory,
responseConverter,
(CallAdapter<ResponseT, Call<ResponseT>>) callAdapter,
continuationBodyNullable,
continuationIsUnit);
}
}
- 假设我们是按照前面Retrofit使用简介章节中给到的方式发起Http请求的,那么上面的流程4中实际返回的是一个CallAdapter类型的对象,现在回到Retrofit的create方法中,前面流程1到流程4介绍的是retrofit2.Retrofit#validateServiceInterface方法的处理逻辑,前面create方法还做动态代理处理,现在看看动态代理实际上执行了流程4返回的retrofit2.HttpServiceMethod.CallAdapted对象的invoke方法,经过调用会走到retrofit2.HttpServiceMethod.CallAdapted#adapt方法,该方法调用的流程4中注释a处生成的Adapter实例,这个实例的来源是retrofit2.DefaultCallAdapterFactory#get方法中返回的,我们一起看看这个get方法的实现:
java
public @Nullable CallAdapter<?, ?> get(
Type returnType, Annotation[] annotations, Retrofit retrofit) {
if (getRawType(returnType) != Call.class) {
return null;
}
if (!(returnType instanceof ParameterizedType)) {
throw new IllegalArgumentException(
"Call return type must be parameterized as Call<Foo> or Call<? extends Foo>");
}
final Type responseType = Utils.getParameterUpperBound(0, (ParameterizedType) returnType);
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 ExecutorCallbackCall<>(executor, call);
}
};
}
- 前面提到的etrofit2.HttpServiceMethod.CallAdapted#adapt就调用了流程5中retrofit2.DefaultCallAdapterFactory#get返回中的ExecutorCallbackCall对象,前面使用简介章节中的enqueue方法实际调用的是retrofit2.DefaultCallAdapterFactory.ExecutorCallbackCall#enqueue方法,其实现如下:
java
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(ExecutorCallbackCall.this, new IOException("Canceled"));
} else {
callback.onResponse(ExecutorCallbackCall.this, response);
}
});
}
@Override
public void onFailure(Call<T> call, final Throwable t) {
callbackExecutor.execute(() -> callback.onFailure(ExecutorCallbackCall.this, t));
}
});
}
其中delegate实例的类型是OkHttpCall类型,创建于retrofit2.HttpServiceMethod#invoke方法中,至此Retrofit完成了Call的请求入列,整个Retrofit主流程就分析完了。后续进入了OkHttp框架请求流程中,这一块在我的专栏《OkHttp源码解析》中有介绍,这里就不再重复赘述了,这也是我先写OkHttp后写这篇文章的原因。具体的实现细节可以移步这个链接中juejin.cn/post/752843...
2.2 Retrofit源码细节
2.2.1 Retrofit回调线程切换
OkHttp与Retrofit有个区别是,OkHttp的网络加载回调默认在子线程中,而Retrofit默认是在主线程回调网络返回结果(划重点啦,这一个面试中经常问到的问题)。我们来看下Retrofit框架为我们做了什么事情,可以默认在主线程给出网络回调。在2.1节介绍主流程的时候我们知道retrofit2.DefaultCallAdapterFactory#get方法返回了一个ExecutorCallbackCall对象,这个对象的构造函数有两个,其中一个是Executor,查看Executor参数传入的地方,实际来自于retrofit2.Retrofit.Builder#build方法初始化默认的CallAdaptFactory对象的时候传入的,具体看看build方法中和Executor相关代码:
java
Epublic Retrofit build() {
// 省略部分代码实现
...
Executor callbackExecutor = this.callbackExecutor;
if (callbackExecutor == null) {
// a、未设置的时候默认取Platform.callbackExecutor值
callbackExecutor = Platform.callbackExecutor;
}
BuiltInFactories builtInFactories = Platform.builtInFactories;
// Make a defensive copy of the adapters and add the default Call adapter.
List<CallAdapter.Factory> callAdapterFactories = new ArrayList<>(this.callAdapterFactories);
List<? extends CallAdapter.Factory> defaultCallAdapterFactories =
builtInFactories.createDefaultCallAdapterFactories(callbackExecutor);
callAdapterFactories.addAll(defaultCallAdapterFactories);
// 省略部分代码实现
}
可以看到上述代码注释a处的executor值,其中Platform.callbackExecutor值的定义为AndroidMainExecutor,其类实现如下:
java
final class AndroidMainExecutor implements Executor {
private final Handler handler = new Handler(Looper.getMainLooper());
@Override
public void execute(Runnable r) {
handler.post(r);
}
}
可见Retrofit可以在主线程回调网络数据返回实际上,底层依赖的是Handler机制。
2.2.2 注解处理
其实整个主流程分析的过程中都没真正讲注解的处理和解析,这里先把整个Retrofit中的注解先列出来:
1. HTTP 请求方法注解
注解 | 作用描述 |
---|---|
@GET |
发起 GET 请求 |
@POST |
发起 POST 请求 |
@PUT |
发起 PUT 请求 |
@DELETE |
发起 DELETE 请求 |
@PATCH |
发起 PATCH 请求 |
@HEAD |
发起 HEAD 请求 |
@OPTIONS |
发起 OPTIONS 请求 |
@HTTP |
自定义 HTTP 方法(需指定 method 、path 、hasBody ) |
2. URL 路径处理注解
注解 | 作用描述 |
---|---|
@Path |
动态替换 URL 中的路径占位符(如 /user/{id} ) |
@Url |
直接传入完整 URL(覆盖 BaseUrl) |
3. 请求参数注解
注解 | 作用描述 |
---|---|
@Query |
添加 URL 查询参数(单个) |
@QueryMap |
添加多个 URL 查询参数(Map 或对象) |
@QueryName |
添加无值的查询参数(如 ?key ) |
@Query + 集合 |
添加重复键的查询参数(如 ?ids=1&ids=2 ) |
4. 请求头注解
注解 | 作用描述 |
---|---|
@Header |
动态添加单个请求头(方法参数) |
@Headers |
添加静态请求头(类或方法上) |
@HeaderMap |
动态添加多个请求头(Map 参数) |
5. 请求体注解
注解 | 作用描述 |
---|---|
@Body |
将对象转换为请求体(如 JSON/XML) |
6. 表单编码注解
注解 | 作用描述 |
---|---|
@FormUrlEncoded |
标记请求体为表单(与 @Field 配合) |
@Field |
添加表单字段(单个) |
@FieldMap |
添加多个表单字段(Map) |
7. 多部分请求注解
注解 | 作用描述 |
---|---|
@Multipart |
标记请求体为多部分(文件上传) |
@Part |
添加多部分数据(如文件/文本) |
@PartMap |
添加多个多部分数据(Map) |
8. 其他辅助注解
注解 | 作用描述 |
---|---|
@Streaming |
将响应体作为流处理(大文件下载) |
@Tag |
为请求关联标签(用于分组管理) |
接下来我们看看这些注解在Retrofit框架中是如何被解析和转化成咱们的数据结构和代码的,在分析Retrofit主流程的2.1节流程3的注释a处,讲了是解析Http的注解,我们看看retrofit2.RequestFactory#parseAnnotations做了些什么:
java
static RequestFactory parseAnnotations(Retrofit retrofit, Class<?> service, Method method) {
return new Builder(retrofit, service, method).build();
}
看来真正的解析还要往里面再看看:
java
RequestFactory build() {
for (Annotation annotation : methodAnnotations) {
// a、解析方法注解
parseMethodAnnotation(annotation);
}
// 省略部分非关键代码
...
int parameterCount = parameterAnnotationsArray.length;
parameterHandlers = new ParameterHandler<?>[parameterCount];
for (int p = 0, lastParameter = parameterCount - 1; p < parameterCount; p++) {
// b、解析方法参数注解
parameterHandlers[p] =
parseParameter(p, parameterTypes[p], parameterAnnotationsArray[p], p == lastParameter);
}
// 省略部分非关键代码
...
return new RequestFactory(this);
}
具体的处理逻辑在注释的a、b两处方法内,其实就是逐个解析注解,实现不复杂,但是代码很多,这里就不一一列举出来了,读者感兴趣的话自行了解并阅读即可。
2.2.3 Retrofit对协程的支持实现
Retrofit原生是支持协程的,在真正开始介绍Retrofit协程的支持之前,我需要先介绍一个背景知识,当我们在写接口的时候,如果我们在定义接口的时候是这样的:
kotlin
interface GitHubService {
@GET("users/{user}/repos")
suspend fun listRepos(@Path("user") String user):Call<List<Repo>>
}
那么我们生成的动态代理实例中,lisRepos方法的参数会被协程编译器增加一个Continuation续体在方法参数的最后面,在2.2.2节的build方法注释b处,会处理这个增加的Continuation续体参数,我们看看这个是怎么处理的呢:
java
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) {
// a、当传入的参数类型为Continuation时,
// isKotlinSuspendFunction变量被置位为true
isKotlinSuspendFunction = true;
return null;
}
} catch (NoClassDefFoundError ignored) {
// Ignored
}
}
throw parameterError(method, p, "No Retrofit annotation found.");
}
return result;
}
从上面的注释和前面介绍的背景知识,可以了解到isKotlinSuspendFunction会被置位为true,那么这个变量到底有什么作用呢,如果你记忆力够好的话,你会记得在2.1章节分析主流程的时候,流程4处retrofit2.HttpServiceMethod#parseAnnotations方法注释c处用到了这个变量值。当isKotlinSuspendFunction是true的时候会返回retrofit2.HttpServiceMethod.SuspendForResponse#SuspendForResponse和retrofit2.HttpServiceMethod.SuspendForBody#SuspendForBody类型的适配器,这里以SuspendForResponse类型的返回为例,实际上适配器的adapt方法会调用retrofit2.KotlinExtensions#awaitResponse方法,这个方法实际上是Call接口类的一个扩展方法,他的实现如下:
kotlin
suspend fun <T> Call<T>.awaitResponse(): Response<T> {
return suspendCancellableCoroutine { continuation ->
continuation.invokeOnCancellation {
cancel()
}
enqueue(object : Callback<T> {
override fun onResponse(call: Call<T>, response: Response<T>) {
continuation.resume(response)
}
override fun onFailure(call: Call<T>, t: Throwable) {
continuation.resumeWithException(t)
}
})
}
}
可以看到很简单就完成了Retrofit的协程支持,当然Retrofit也支持了Rxjava流式编程,不得不说这个适配器模式还是很厉害的,可以实现Call到不同类型返回的转变,这种设计思想希望读者也能够领会到这个模式的魅力所在。
3 小结
Retrofit凭借其优雅的设计、强大的扩展性(CallAdapter/Converter机制)和优异的性能,已成为 Android开发中网络请求的首选框架,特别适合中大型项目和对代码质量要求较高的场景。这个框架设计的思想也有很多值得我们借鉴的地方,笔者自己在写这篇文章的时候再次阅读源码,也感觉受益不少,这也是我写这个系列文章的初衷之一。创作不易,欢迎各位一键三连啊,点赞、收藏、关注点起来,来跟着Muggle一起学习Android开发。