盒子
盒子
文章目录
  1. interface的动态代理
    1. Proxy不能代理class的原因
  2. class的动态代理

安卓动态代理

动态代理在java里面算是一种比常用的技术,它和静态代理的区别在于静态代理需在编译的时候代理类就已经确定了,而动态代理的代理类是在运行的时候动态生成的。

例如使用retrofit的时候我们只需要定义好interface:

1
2
3
4
public interface GitHubService {
@GET("users/{user}/repos")
Call<List<Repo>> listRepos(@Path("user") String user);
}

然后就可以在运行的时候创建出这个接口的实例:

1
2
3
4
5
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://api.github.com/")
.build();

GitHubService service = retrofit.create(GitHubService.class);

这个实例其实就是接口的代理,它的原理是利用Proxy.newProxyInstance

interface的动态代理

Proxy是java内置的一个类,我们可以用它来创建接口的动态代理,具体的用法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 定义interface
public interface ITestInterface {
void foo();
}

// 创建InvocationHandler用于代理interface的方法
InvocationHandler handler = new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) {
Log.d(TAG, "invoke " + method, new Exception());
return null;
}
};

// 创建ITestInterface的实例:
ITestInterface testInterface = (ITestInterface) Proxy.newProxyInstance(
getClassLoader(),
new Class[]{ITestInterface.class},
handler);

然后我们调用testInterface.foo()最终就会去到InvocationHandler.invoke方法里面。

安卓里面最终是在ClassLinker::CreateProxyClass里面创建了实现ITestInterface接口的子类。从堆栈上看它生成的类名叫$Proxy1:

1
2
3
4
5
6
7
04-23 16:58:32.641  4205  4205 D testtest: invoke public abstract void me.linjw.demo.ITestInterface.foo()
04-23 16:58:32.641 4205 4205 D testtest: java.lang.Exception
04-23 16:58:32.641 4205 4205 D testtest: at me.linjw.demo.MainActivity$1.invoke(MainActivity.java:23)
04-23 16:58:32.641 4205 4205 D testtest: at java.lang.reflect.Proxy.invoke(Proxy.java:1006)
04-23 16:58:32.641 4205 4205 D testtest: at $Proxy1.foo(Unknown Source)
04-23 16:58:32.641 4205 4205 D testtest: at me.linjw.demo.MainActivity.onCreate(MainActivity.java:32)
...

$Proxy1.foo方法里面调用了Proxy.invoke,而这个invoke方法实际就是调用了我们注册的InvocationHandler:

1
2
3
4
5
// Android-added: Helper method invoke(Proxy, Method, Object[]) for ART native code.
private static Object invoke(Proxy proxy, Method method, Object[] args) throws Throwable {
InvocationHandler h = proxy.h;
return h.invoke(proxy, method, args);
}

JVM里面可以通过设置环境变量的方式将动态生成的类保存下来,但是安卓里面并没有这样的机制。我们可以通过反射打印这个生成类的结构(打印代码具体见dumpsysClass方法):

1
2
3
4
5
6
7
8
04-23 20:40:10.580 13546 13546 D ProxyDemo: class $Proxy1 extends Proxy implements ITestInterface {
04-23 20:40:10.580 13546 13546 D ProxyDemo: public static final Class[] interfaces;
04-23 20:40:10.580 13546 13546 D ProxyDemo: public static final Class[][] throws;
04-23 20:40:10.580 13546 13546 D ProxyDemo: public final equals(Object arg0) { ... }
04-23 20:40:10.580 13546 13546 D ProxyDemo: public final foo() { ... }
04-23 20:40:10.580 13546 13546 D ProxyDemo: public final hashCode() { ... }
04-23 20:40:10.580 13546 13546 D ProxyDemo: public final toString() { ... }
04-23 20:40:10.580 13546 13546 D ProxyDemo: }

从打印的信息来看我们可以知道生成的$Proxy1类是Proxy的之类并且实现了我们需要代理的ITestInterface接口,它的foo方法我们没有办法打印出实际的字节码,但是从堆栈上看可以猜测大概是这样的:

1
2
3
void foo() {
Proxy.invoke(this, ITestInterface.class.getMethod("foo"), null);
}

Proxy不能代理class的原因

由于interface是支持多实现的所以我们可以代理多个接口,这样生成的类就会实现多个接口:

1
2
3
4
ITestInterface testInterface = (ITestInterface) Proxy.newProxyInstance(
getClassLoader(),
new Class[]{ITestInterface.class}, // 这个数组可以传入多个interface进行代理
handler);

但是java并不支持多继承,动态生成的类已经继承Proxy了就不能再继承其他的类,所以Proxy并不能代理类或者抽象类:

1
2
3
4
5
6
7
8
04-23 16:33:06.615  2966  2966 E testtest: err
04-23 16:33:06.615 2966 2966 E testtest: java.lang.IllegalArgumentException: me.linjw.demo.TestAbstractClass is not an interface
04-23 16:33:06.615 2966 2966 E testtest: at java.lang.reflect.Proxy$ProxyClassFactory.apply(Proxy.java:635)
04-23 16:33:06.615 2966 2966 E testtest: at java.lang.reflect.Proxy$ProxyClassFactory.apply(Proxy.java:602)
04-23 16:33:06.615 2966 2966 E testtest: at java.lang.reflect.WeakCache$Factory.get(WeakCache.java:230)
04-23 16:33:06.615 2966 2966 E testtest: at java.lang.reflect.WeakCache.get(WeakCache.java:127)
04-23 16:33:06.615 2966 2966 E testtest: at java.lang.reflect.Proxy.getProxyClass0(Proxy.java:438)
04-23 16:33:06.615 2966 2966 E testtest: at java.lang.reflect.Proxy.newProxyInstance(Proxy.java:873)

class的动态代理

理解了Proxy代理接口的原理之后,如果我们想要对类做动态代理的话,可以模仿Proxy的原理运行时创建子类重写被代理类的方法去实现.实际上java有个开源项目cglib可以在运行的时候生成java字节码实现这个功能。但是由于安卓运行时使用的不是java字节码而是安卓自己的字节码,所以不能直接使用cglib去实现。

但是领英开源了dexmaker用于在运行时生成安卓字节码,有国内的大神基于它模仿cglib实现了CGLib-for-Android

可惜的是它有些小问题,当代理的类含有静态方法、抽象方法、final方法的时候会报异常.作者已经好几年没有更新它了,所以我自己fork了一个仓库出来fix了这些问题。它的用法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// 定义需要代理的类
public class TestClass {
public void foo() {
Log.d(MainActivity.TAG, "on TestClass.foo");
}
}

// 创建MethodInterceptor用于代理class的方法
Enhancer e = new Enhancer();
e.setSuperclass(TestClass.class);
e.setInterceptor(new MethodInterceptor() {
@Override
public Object intercept(Object o, Object[] objects, MethodProxy methodProxy) {
// 调用父类(即被代理类)的方法
methodProxy.invokeSuper(o, objects);
Log.d(TAG, "invoke " + methodProxy.getOriginalMethod(), new Exception());
return null;
}
});

// 创建代理实例
TestClass testClass = (TestClass) e.create(getCacheDir().getAbsolutePath());

// 调用方法
testClass.foo();

然后调用TestClass的方法就会去到MethodInterceptor:

1
2
3
4
5
6
7
04-24 22:25:56.108  3357  3357 D ProxyDemo: on TestClass.foo
04-24 22:25:56.110 3357 3357 D ProxyDemo: invoke public void me.linjw.demo.proxy.TestClass$Enhancer$.foo()
04-24 22:25:56.110 3357 3357 D ProxyDemo: java.lang.Exception
04-24 22:25:56.110 3357 3357 D ProxyDemo: at me.linjw.demo.proxy.MainActivity$2.intercept(MainActivity.java:45)
04-24 22:25:56.110 3357 3357 D ProxyDemo: at leo.android.cglib.proxy.MethodProxyExecuter.executeInterceptor(MethodProxyExecuter.java:15)
04-24 22:25:56.110 3357 3357 D ProxyDemo: at me.linjw.demo.proxy.TestClass$Enhancer$.foo(Unknown Source:19)
04-24 22:25:56.110 3357 3357 D ProxyDemo: at me.linjw.demo.proxy.MainActivity.onCreate(MainActivity.java:51)