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 小时前
Jetpack系列(二):Lifecycle与LiveData结合,打造响应式UI
android·android jetpack
kk爱闹3 小时前
【挑战14天学完python和pytorch】- day01
android·pytorch·python
每次的天空5 小时前
Android-自定义View的实战学习总结
android·学习·kotlin·音视频
恋猫de小郭5 小时前
Flutter Widget Preview 功能已合并到 master,提前在体验毛坯的预览支持
android·flutter·ios
断剑重铸之日6 小时前
Android自定义相机开发(类似OCR扫描相机)
android
随心最为安6 小时前
Android Library Maven 发布完整流程指南
android
岁月玲珑6 小时前
【使用Android Studio调试手机app时候手机老掉线问题】
android·ide·android studio
还鮟10 小时前
CTF Web的数组巧用
android
小蜜蜂嗡嗡12 小时前
Android Studio flutter项目运行、打包时间太长
android·flutter·android studio
aqi0012 小时前
FFmpeg开发笔记(七十一)使用国产的QPlayer2实现双播放器观看视频
android·ffmpeg·音视频·流媒体