Retrofit2 目前已经越来越主流稳定了,它终于完全抛弃了其它的网络库而是使用了OkHttp3作为依赖,功能也更加插件化了。经常听到动态代理这个词而不知所云,现在知识水平高了,分析一下,以飨(xiǎng)读者。
如果用一个词来概括Retrofit的话,那么“拼装”就是最准确的,它借鉴了以前服务器编程中的动态代理技术,通过接口在运行时生成字节码;接着通过注解拼装HTTP请求;最后包装了OkHttp,实现了对Rx、线程的adaption。
本文主要是过程(practice),而不是教程(tutorial),所以希望各位下载代码实践。
本文基于retrofit:2.0.0-beta3
与jdk1.8.0_05
进行分析
对字节码保存的字节码保存工具类,用这个可以导出class文件,当然你也可以在jvm
ProxyUtils.saveProxyClass("123.class", "Github", new Class[] { GitHub.class });
Retrofit2的源码,注意它是基于maven的,最好用Idea导入
git clone --depth=1 https://github.com/square/retrofit.git
还不知道Retrofit怎么用?先看看Retrofit解析JSON数据吧
代理是一种设计模式,分为静态代理与动态代理。
没有代理前,调用是这样的:
使用后,它们的调用顺序是这样的:
通过在中间加了一层代理,将业务逻辑与实现细节分离,方便了上层开发者。
我需要去有关部门办很多手续,但是不想来回跑,于是找到了一家中介。
我 <-- 次性提供中介要求的几个证件 --> 中介
中介 <-- 进行繁琐的填表跑腿工作 --> 有关部门
通过代理,我不用了解跑腿的细节,就可以完成业务,这个就是代理的优点,面向业务而忽略细节。当然这个是有代价的(比如需要交跑腿费),在编码中,代理类加大了编码量。
我需要访问国外的网站,但是速度很慢,于是找到了一个服务商。
我 <-- 一键配置pac --> 中转服务器
中转服务器 <-- 进行繁琐的开发、运维工作 --> 外网
通过代理,我只用复制一个pac,就可以完成业务,而不用去想怎么与防火墙斗智斗勇,同样这个是需要代价的
通过用户手动
编码或者在编译时
自动生成。
在 AppCompatActivity 中的AppCompatDelegate
抽象类就是静态代理,而抽象类的实例化是在静态语句块 static{} 区中根据版本号实现的,这样写可以避免 Activity 本身的业务过于冗余,同时将抽象类(也可能是接口)与实现类相分离,这样上层开发者可以专注于业务,而不用管具体的实现。
AppCompatActivity - 委任 - 委任实现
在Android中的IPC访问中,如果需要跨进程访问方法,需要使用AIDL通信,当调用者调用远程方法时,调用的是 Stub
类,也就是远程进程的桩,接着这个桩通过 ASHMEM(匿名共享内存)
进行转发,最后方法的实现还是在远程服务器中。
调用者 - Stub接口(由AIDL生成) - 远程服务
上面的 stub, delegate 都是静态代理,本身是抽象类的,让调用者实现了面向接口业务编程。
动态代理是java中的字节码生成技术。通过接口生成代理类,并将代理类的实现交给 InvocationHandler
作为具体的实现。
动态代理借助java的反射特性,通过易写的接口与注解,帮助用户自动生成实际的对象,接口中方法真正的调用交给InvocationHandler。
Input | interface |
---|---|
↓ | ↓ |
Proxy.newProxyInstance | 对接口进行包装,内部方法由handler委任 |
↓ | ↓ |
Output | interface(with InvocationHandler) |
当用户调用生成接口中的方法时,实际上调用了InvocationHandler中的invoke
方法
现在我们将动态代理前的接口文件,与代理后的运行生成的反编译后的文件进行对比
官方的示例接口
public interface GitHub {
@GET("/repos/{owner}/{repo}/contributors") Call<List<Contributor>> contributors( @Path("owner") String owner, @Path("repo") String repo);
}
官方对接口的调用
Call<List<Contributor>> call = github.contributors("square", "retrofit");
它在Retrofit中的create中作为参数传入,通过调用下面方法读取接口信息并在运行时生成字节码并用classloader加载
Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[] { service },
new InvocationHandler() {
private final Platform platform = Platform.get();
@Override public Object invoke(Object proxy, Method method, Object... args)
.....
//开始处理注解并拼装成OkHttp的call
return loadMethodHandler(method).invoke(args);
}
});
}
这是动态代理生成后用工具dump出并反编译的class(下面省略了Object自带的方法,以及大量的异常捕捉)
public final class Github extends Proxy implements SimpleService.GitHub {
private static Method m0;//自带的hashCode方法
private static Method m1;//自带的equals方法
private static Method m2;//自带的toString
private static Method m3;//真正的接口中的方法
static {
try {
//省略Object自带方法的初始化
m0,m1,m2 = ...
//接口中真正的方法
m3 = Class.forName("com.example.retrofit.SimpleService$GitHub")
.getMethod("contributors",
new Class[] { Class.forName("java.lang.String"), Class.forName("java.lang.String") });
} catch (Throwable e) {
throw new NoSuchMethodError(e.getMessage());
} catch (Throwable e2) {
throw new NoClassDefFoundError(e2.getMessage());
}
}
public Github(InvocationHandler invocationHandler) {
super(invocationHandler);
}
//这里的参数实际上就是用户输入的"square", "retrofit"
public final Call contributors(String str, String str2) {
RuntimeException e;
try {
//this.h是InvocationHandler,也就是上文重写的Handler
//可以看出实际调用的是handler的`invoke`方法
return (Call) this.h.invoke(this, m3, new Object[] { str, str2 });
} catch (Throwable e) {
throw e;
}
}
....Object自带方法的代理...
}
可以看出InvocationHandler是最重要的修饰,Handler中的invoke进行了实际处理,而这些是我们重写过的,至于具体怎么处理的,这些坑后续再补。
代理生成的过程使用了sun包中的闭源方法,反编译大致看了一下,先把接口通过ProxyGenerator.generateProxyClass()
根据Class文件的规范拼装生成了byte[]
字节码,接着用native方法defineClass0()
转换为对象,可以看出动态代理本质上是生成大量样板代码
的过程。相比于静态代理,动态代理可以减少编写代理类(比如XXXImpl)的工作量。
不会,经过测试。retrofit构造与动态代理加起来的时间只有 0.65ms,相对于 16ms ,相比于网络请求,完全不是一个数量级。如果你是强迫症的话,可以让它单例化。