前段时间研究了下 Retrofit
框架,也写了一些关于 Retrofit 的文章:
在阅读 Retrofit 源代码的时候收获还是挺多的,所以将其中的一些感想和收获提炼一下。我觉得本篇文章是我写的 Retrofit 系列最有价值的一篇,因为仅仅知道框架是怎么做的还不够,我们应该还要去想作者为什么这么做,有什么好处?解决什么问题?试着从设计的角度去思考,才能提高我们的设计能力,为我们将来能够编写高性能、高稳定性、扩展性的代码打好基础。
我们写代码的时候有多少人会考虑多线程同步的情况呢?可能多人都只考虑了单线程的情况。
例如,在 Retrofit 中的这段代码:
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;
}
serviceMethodCache
是一个 ConcurrentHashMap
,为什么要将解析注解的操作放在 synchronized
同步代码块中,因为如果多个线程执行到 result == null
,会触发多次 parseAnnotations
操作,而这个操作是比较耗时的,Retrofit 为了提高性能会将解析的结果放在 ConcurrentHashMap 集合中。
还有一个细节是,作者将 result != null
放在同步代码块之前,这样做的好处是避免 synchronized 外面再套一层 if(result == null),也就是我们在网上经常看到的 double check。
动态代理技术是 Retrofit 用到的最核心的技术。在使用 Retrofit 的过程中我们只需要声明接口就可以了,具体的实现类由动态代理创建。
代理模式由抽象主题(Subject)角色、代理主题(Proxy Subject)角色、真实主题(Real Subject)角色组成。
在 Retrofit 中 Real Subject
不是从外部传递给 InvocationHandler
的,而是在 InvocationHandler
内部动态创建的。
这样的话,使用者(Client)就非常简单了,只需要创建 抽象主题
就可以了,也就是我们创建的 interface
。
Retrofit 把动态代理使用的淋漓尽致,在调用接口方法之前,对方法上的注解进行解析,然后组装网络请求需要的参数,然后再发送网络请求。
其实 JDK 中也有很多地方使用了动态代理,比如 Annotation
注解。本质上 Annotation 就是一个 interface
。例如我们创建一个 NotNull
注解
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface NotNull {}
它的 class 字节码如下所示:
public interface com.chiclaim.annotation.NotNull extends java.lang.annotation.Annotation {
}
实际上注解就是一个继承了 java.lang.annotation.Annotation
的接口。
既然注解是一个接口,那它的实现类从哪里来,我们创建一个注解,但是没有创建实现类。
例如我们通过反射获取注解实例的时候,没有实现类,这个实例是怎么产生的。实际上 JDK 是通过动态代理来创建的。
下面我们来看一个简单的例子,通过反射获取方法参数的注解:
public class ReflectionTest {
void test(@NotNull List list) {
}
private static void getMethodParameterAnnotation() throws Exception {
Method method = ReflectionTest.class.getDeclaredMethod("test", List.class);
Annotation[][] ass = method.getParameterAnnotations();
for (Annotation[] as : ass) {
for (Annotation annotation : as) {
System.out.println(annotation); // @com.chiclaim.annotation.NotNull()
}
}
}
}
我们通过输出获取不到什么有效的信息,我们可以通过 debug 看看遍历时的 annotation 是什么:
如果我们看到 $Proxy
的前缀一般来说就是使用到了动态代理,其中的 h
变量就是 InvocationHandler
,这个我们已经在分析 动态代理原理 的时候介绍过了。
Retrofit 中建造者模式也是使用的非常多。比如构建一个 Retrofit 对象等。
val retrofit: Retrofit = Retrofit.Builder()
.baseUrl(API_URL)
.addConverterFactory(GsonConverterFactory.create())
.build()
其实我们在使用很多开源框架的时候很少让我们直接通过构造器来创建对象,很多都是通过 Builder
来创建的。
一般来说,通过建造者模式扩展性更好,将来你可能需要添加参数,如果一开始就使用构造器或静态工厂,等到类需要更多参数才添加建造者模式,就会无法控制,那些过时的构造器或者静态工厂显得十分不协调,因此,通常最好一开始就是用建造者模式。
Retrofit 中使用了适配器模式,使得 Retrofit 的扩展性非常好。
我们知道在定义接口函数的时候,函数的返回类型是 Call
,例如:
public interface GitHub {
@GET("/repos/{owner}/{repo}/contributors")
Call> contributors(
@Path("owner") String owner,
@Path("repo") String repo);
}
随着 RxJava
的流行,Retrofit
想要整合 RxJava
怎么办?
public interface GitHub {
@GET("/repos/{owner}/{repo}/contributors")
Observable> contributors(
@Path("owner") String owner,
@Path("repo") String repo);
}
Retrofit
作者已经考虑到这点了,通过 CallAdapter
来实现。
通过适配器的方式将结果适配成你需要的类型。例如后面 Kotlin 协程比较流行,要整合协程怎么办?
interface GitHub {
@GET("/repos/{owner}/{repo}/contributors")
suspend fun contributors(
@Path("owner") String owner,
@Path("repo") String repo
):List;
}
JakeWharton
写了一个 retrofit2-kotlin-coroutines-adapter
很方便的将协程整合在一起,后来 Retrofit 直接将这个协程的整合直接内置 Retrofit 里面,这样我们在使用的时候就不用 addCallAdapterFactory
了
Retrofit 里的 CallAdapter 非常灵活,不仅允许开发者自定义 CallAdapter,而且还可以对该 Adapter 进行再次封装。例如使用了 RxJava2CallAdapter
后,我们想自定义 CallAdapter 达到统一封装对网络错误的处理和指定被观察者执行线程的目的。
关于自定义 CallAdapter 更多的信息,可以查看:
CallEnqueueObservable.CallCallback
也是用到了适配器模式:
private static final class CallCallback implements Disposable, Callback {
private final Call> call;
private final Observer super Response> observer;
private volatile boolean disposed;
boolean terminated = false;
CallCallback(Call> call, Observer super Response> observer) {
this.call = call;
this.observer = observer;
}
@Override public void onResponse(Call call, Response response) {
if (disposed) return;
try {
observer.onNext(response);
if (!disposed) {
terminated = true;
observer.onComplete();
}
} catch (Throwable t) {
Exceptions.throwIfFatal(t);
if (terminated) {
RxJavaPlugins.onError(t);
} else if (!disposed) {
try {
observer.onError(t);
} catch (Throwable inner) {
Exceptions.throwIfFatal(inner);
RxJavaPlugins.onError(new CompositeException(t, inner));
}
}
}
}
@Override public void onFailure(Call call, Throwable t) {
if (call.isCanceled()) return;
try {
observer.onError(t);
} catch (Throwable inner) {
Exceptions.throwIfFatal(inner);
RxJavaPlugins.onError(new CompositeException(t, inner));
}
}
@Override public void dispose() {
disposed = true;
call.cancel();
}
@Override public boolean isDisposed() {
return disposed;
}
}
适配的目标 Target
是 Disposable, Callback 的结合体,因为后面 observer.onSubscribe
和 call.enqueue
需要用到:
CallCallback callback = new CallCallback<>(call, observer);
observer.onSubscribe(callback);
if (!callback.isDisposed()) {
call.enqueue(callback);
}
CallCallback 里的具体实现是通过传递进来的 Call、Observer 两个对象来实现的,这也是一个典型的适配器模式。
更多关于适配器模式的内容,有兴趣的可以参考我之前的文章:《设计模式 ~ 适配器模式分析与实战》
我们在阅读很多源码的时候,会发现对于源码中的一些核心组件类,作者使用的时候一般不会直接通过 new
的方式来创建对象,要么通过 简单工厂
或者 工厂方法
模式来创建对象。
例如, Retrofit 中的 CallAdapter 就是通过 工厂方法
来创建的:
public final class RxJava2CallAdapterFactory extends CallAdapter.Factory {
// 通过简单工厂来创建对象
public static RxJava2CallAdapterFactory create() {
return new RxJava2CallAdapterFactory(null, false);
}
// 通过简单工厂来创建对象
public static RxJava2CallAdapterFactory createAsync() {
return new RxJava2CallAdapterFactory(null, true);
}
// 构造方法私有
private RxJava2CallAdapterFactory(@Nullable Scheduler scheduler, boolean isAsync) {
this.scheduler = scheduler;
this.isAsync = isAsync;
}
@Override public @Nullable CallAdapter, ?> get(
Type returnType, Annotation[] annotations, Retrofit retrofit) {
// 省略其他代码...
// 创建 RxJava2CallAdapter 对象
return new RxJava2CallAdapter(responseType, scheduler, isAsync, isResult, isBody, isFlowable,
isSingle, isMaybe, false);
}
}
我们发现这是一个典型的 简单工厂
和 工厂模式
的集合的例子。
不管是哪种形态的工厂模式,它们的核心原则都是:屏蔽对象的创建细节,客户端只负责“消费”对象,不关心对象的创建,从而达到解耦的目的。
更多关于工厂模式的内容,有兴趣的可以参考我之前的文章:《设计模式 ~ 工厂模式剖析与实战》
装饰模式(Decorator Pattern)又称包装模式。装饰模式以对客户端透明的方式扩展对象功能。装饰模式能动态地给一个对象添加一些额外的职责。就增加功能来说,装饰模式比继承的方式更加灵活。
Retrofit 也有很多地方使用了装饰器模式。比如 DefaultCallAdapterFactory.ExecutorCallbackCall
:
static final class ExecutorCallbackCall implements Call {
final Executor callbackExecutor;
// 实际上是 OkHttpCall
final Call delegate;
ExecutorCallbackCall(Executor callbackExecutor, Call delegate) {
this.callbackExecutor = callbackExecutor;
this.delegate = delegate;
}
@Override public void enqueue(final Callback callback) {
Objects.requireNonNull(callback, "callback == null");
delegate.enqueue(new Callback() {
@Override public void onResponse(Call call, final Response response) {
callbackExecutor.execute(() -> {
if (delegate.isCanceled()) {
callback.onFailure(ExecutorCallbackCall.this, new IOException("Canceled"));
} else {
callback.onResponse(ExecutorCallbackCall.this, response);
}
});
}
@Override public void onFailure(Call call, final Throwable t) {
callbackExecutor.execute(() -> callback.onFailure(ExecutorCallbackCall.this, t));
}
});
}
// 省略其他代码...
}
上面的 delegate
实际上就是 OkHttpCall
, 它实现了 Call
接口,ExecutorCallbackCall
也是实现了 Call
接口。
然后将 OkHttpCall
传递给 ExecutorCallbackCall
的构造方法,用于增强 OkHttpCall
的功能(指定 OkHttpCall 回调所在的线程)
这就是典型的 装饰器模式
。
更多关于装饰器模式的内容,有兴趣的可以参考我之前的文章:《设计模式 ~ 装饰模式探究》
本文从设计模式的角度分析了 Retrofit 相关源码,希望对你有帮助。
另外本文涉及到的代码都在我的 AndroidAll GitHub 仓库中。该仓库除了 Retrofit,还有Android 程序员需要掌握的技术栈,如:程序架构、设计模式、性能优化、数据结构算法、Kotlin、Flutter、NDK、Router、RxJava、Glide、LeakCanary、Dagger2、Retrofit、OkHttp、ButterKnife、Router 等等,持续更新,欢迎 star。