Android动态代理详解

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

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

java 复制代码
public interface GitHubService {
  @GET("users/{user}/repos")
  Call<List<Repo>> listRepos(@Path("user") String user);
}

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

java 复制代码
Retrofit retrofit = new Retrofit.Builder()
    .baseUrl("https://api.github.com/")
    .build();

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

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

interface的动态代理

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

java 复制代码
// 定义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:

java 复制代码
11-02 16:58:32.641  4205  4205 D testtest: invoke public abstract void me.linjw.demo.ITestInterface.foo()
11-02 16:58:32.641  4205  4205 D testtest: java.lang.Exception
11-02 16:58:32.641  4205  4205 D testtest:      at me.linjw.demo.MainActivity$1.invoke(MainActivity.java:23)
11-02 16:58:32.641  4205  4205 D testtest:      at java.lang.reflect.Proxy.invoke(Proxy.java:1006)
11-02 16:58:32.641  4205  4205 D testtest:      at $Proxy1.foo(Unknown Source)
11-02 16:58:32.641  4205  4205 D testtest:      at me.linjw.demo.MainActivity.onCreate(MainActivity.java:32)
...

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

java 复制代码
// 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里面可以通过设置环境变量的方式将动态生成的类保存下来,但是安卓里面并没有这样的机制。我们可以通过反射打印这个生成类的结构

java 复制代码
11-02 20:40:10.580 13546 13546 D ProxyDemo: class $Proxy1 extends Proxy implements ITestInterface {
11-02 20:40:10.580 13546 13546 D ProxyDemo:     public static final Class[] interfaces;
11-02 20:40:10.580 13546 13546 D ProxyDemo:     public static final Class[][] throws;
11-02 20:40:10.580 13546 13546 D ProxyDemo:     public final equals(Object arg0) { ... }
11-02 20:40:10.580 13546 13546 D ProxyDemo:     public final foo() { ... }
11-02 20:40:10.580 13546 13546 D ProxyDemo:     public final hashCode() { ... }
11-02 20:40:10.580 13546 13546 D ProxyDemo:     public final toString() { ... }
11-02 20:40:10.580 13546 13546 D ProxyDemo: }

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

java 复制代码
void foo() {
    Proxy.invoke(this, ITestInterface.class.getMethod("foo"), null);
}

Proxy不能代理class的原因

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

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

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

java 复制代码
11-02 16:33:06.615  2966  2966 E testtest: err
11-02 16:33:06.615  2966  2966 E testtest: java.lang.IllegalArgumentException: me.linjw.demo.TestAbstractClass is not an interface
11-02 16:33:06.615  2966  2966 E testtest:      at java.lang.reflect.Proxy$ProxyClassFactory.apply(Proxy.java:635)
11-02 16:33:06.615  2966  2966 E testtest:      at java.lang.reflect.Proxy$ProxyClassFactory.apply(Proxy.java:602)
11-02 16:33:06.615  2966  2966 E testtest:      at java.lang.reflect.WeakCache$Factory.get(WeakCache.java:230)
11-02 16:33:06.615  2966  2966 E testtest:      at java.lang.reflect.WeakCache.get(WeakCache.java:127)
11-02 16:33:06.615  2966  2966 E testtest:      at java.lang.reflect.Proxy.getProxyClass0(Proxy.java:438)
11-02 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去实现。

java 复制代码
// 定义需要代理的类
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:

java 复制代码
11-02 22:25:56.108  3357  3357 D ProxyDemo: on TestClass.foo
11-02 22:25:56.110  3357  3357 D ProxyDemo: invoke public void me.linjw.demo.proxy.TestClass$Enhancer$.foo()
11-02 22:25:56.110  3357  3357 D ProxyDemo: java.lang.Exception
11-02 22:25:56.110  3357  3357 D ProxyDemo:     at me.linjw.demo.proxy.MainActivity$2.intercept(MainActivity.java:45)
11-02 22:25:56.110  3357  3357 D ProxyDemo:     at leo.android.cglib.proxy.MethodProxyExecuter.executeInterceptor(MethodProxyExecuter.java:15)
11-02 22:25:56.110  3357  3357 D ProxyDemo:     at me.linjw.demo.proxy.TestClass$Enhancer$.foo(Unknown Source:19)
11-02 22:25:56.110  3357  3357 D ProxyDemo:     at me.linjw.demo.proxy.MainActivity.onCreate(MainActivity.java:51)

👀关注公众号:Android老皮!!!欢迎大家来找我探讨交流👀

相关推荐
还鮟1 小时前
CTF Web的数组巧用
android
小蜜蜂嗡嗡2 小时前
Android Studio flutter项目运行、打包时间太长
android·flutter·android studio
aqi002 小时前
FFmpeg开发笔记(七十一)使用国产的QPlayer2实现双播放器观看视频
android·ffmpeg·音视频·流媒体
zhangphil4 小时前
Android理解onTrimMemory中ComponentCallbacks2的内存警戒水位线值
android
你过来啊你4 小时前
Android View的绘制原理详解
android
移动开发者1号7 小时前
使用 Android App Bundle 极致压缩应用体积
android·kotlin
移动开发者1号7 小时前
构建高可用线上性能监控体系:从原理到实战
android·kotlin
ii_best12 小时前
按键精灵支持安卓14、15系统,兼容64位环境开发辅助工具
android
美狐美颜sdk12 小时前
跨平台直播美颜SDK集成实录:Android/iOS如何适配贴纸功能
android·人工智能·ios·架构·音视频·美颜sdk·第三方美颜sdk
恋猫de小郭17 小时前
Meta 宣布加入 Kotlin 基金会,将为 Kotlin 和 Android 生态提供全新支持
android·开发语言·ios·kotlin