一句话总结
静态代理就像“代购跑腿”——提前签合同,动态代理则是“闪送小哥”——随叫随到,Android 系统里两者分工明确,一个干重活,一个接急单!
一、代理模式的本质与分类
代理模式作为一种结构型设计模式,其核心思想是为另一个对象提供一个替身或占位符,以控制对这个对象的访问。它在不改变原有类的情况下,增加了额外的功能(如日志、权限控制、缓存等),实现了开闭原则。在Android开发中,代理模式通常分为两种实现方式:静态代理和动态代理。
二、静态代理:编译时确定的“专职替身”
1. 原理与应用
静态代理需要手动或通过工具为每个目标类创建一个代理类,并且代理类与目标类必须实现同一个接口。这种代理关系在编译时就已确定。代理类内部持有目标对象的引用,在调用代理方法时,它会先执行自己的逻辑,然后再将请求转发给真正的目标对象。
在Android的AIDL跨进程通信(IPC)机制中,静态代理是其核心。当定义一个 AIDL 接口后,编译器会自动生成一个 Stub 和一个 Proxy 类。Proxy 类就是客户端进程中的静态代理,它将方法调用、参数打包成 Parcel 对象,通过 Binder 驱动发送到服务端。
// frameworks/base/core/java/android/app/ActivityManagerNative.java
class ActivityManagerProxy implements IActivityManager {
private final IBinder mRemote;
public ActivityManagerProxy(IBinder remote) {
mRemote = remote;
}
// 代理方法:将请求打包并通过Binder发送到服务端
public void startActivity(...) throws RemoteException {
Parcel data = Parcel.obtain();
data.writeInterfaceToken(IActivityManager.descriptor);
mRemote.transact(START_ACTIVITY_TRANSACTION, data, null, 0);
}
}
2. 优缺点分析
-
优点:
- 高性能:由于代理类在编译时生成,它直接调用目标方法,没有反射开销,执行效率高。
- 代码直观:代理关系清晰,易于理解和调试。
-
缺点:
- 代码冗余:如果接口方法众多,或者需要为多个接口创建代理,会产生大量的重复代码。
- 维护困难:一旦接口发生改变,代理类也必须手动修改或重新生成。
三、动态代理:运行时生成的“万能替身”
1. 原理与应用
动态代理在运行时通过反射机制,为目标接口自动生成一个代理类。所有对接口方法的调用都会被统一转发到一个**InvocationHandler**的 invoke() 方法中。开发者只需在这个 invoke() 方法里编写统一的逻辑,即可对所有方法进行拦截和增强。
在Android生态中,Retrofit框架是动态代理的经典应用。当调用 Retrofit.create(MyApi.class) 时,它并不会返回 MyApi 的具体实现类,而是生成一个动态代理对象。这个代理对象拦截了所有 MyApi 接口的方法调用,并根据方法上的注解,将方法名、参数等信息统一转换成HTTP请求,发送到网络。
Java
// Retrofit 动态代理的核心(简化版)
public <T> T create(final Class<T> service) {
return (T) Proxy.newProxyInstance(
service.getClassLoader(),
new Class<?>[] { service },
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) {
// 根据方法注解解析HTTP请求
// ...
return sendHttpRequest(url, args);
}
});
}
2. 优缺点分析
-
优点:
- 代码精简:一个
InvocationHandler可以处理多个接口的所有方法,极大减少了模板代码。 - 高度灵活:可以在运行时动态添加或修改代理逻辑,非常适合AOP(面向切面编程)场景,如日志记录、性能监控等。
- 代码精简:一个
-
缺点:
- 性能损耗:动态代理底层依赖反射,会带来一定的性能开销。
- 局限性:Java原生动态代理只能代理接口,无法代理没有实现接口的普通类。对于需要代理类的情况,通常需要借助CGLib等第三方库,它通过生成目标类的子类来实现代理。
四、性能对比与选择建议
1. 性能对比
尽管现代JVM对反射进行了优化,但动态代理在性能上仍然不及静态代理。在Android平台上,由于设备性能的差异,这种差距会更加明显。一个简单的性能测试表明,在相同调用次数下,动态代理的耗时是静态代理的2-3倍。
| 代理类型 | 性能开销 | 核心技术 | 适用场景 |
|---|---|---|---|
| 静态代理 | 低 | 编译时代码生成 | 性能敏感、AIDL跨进程通信 |
| 动态代理 | 高 | 运行时反射 | 动态功能扩展、AOP、解耦 |
2. 选择建议
- 跨进程通信(AIDL) :强制且高效,必须使用静态代理。
- 高频调用或性能关键:优先选择静态代理,以避免反射带来的性能损耗。
- 业务逻辑解耦与动态增强:当需要统一处理大量相似接口或实现AOP功能时,动态代理是更灵活、更优雅的选择,例如在Retrofit、OkHttp的拦截器等场景。
- Hook 系统服务:在插件化或热修复等领域,动态代理常用于“偷梁换柱”,通过替换系统服务的接口实例,来实现对系统API调用的拦截和重定向,从而实现动态功能扩展。
五、逆向与Hook实战:动态代理的强大之处
动态代理在逆向工程中扮演着关键角色。以 Hook ActivityManager 的 startActivity 为例,我们可以利用动态代理在不修改系统源码的前提下,拦截所有 startActivity 的调用。
其核心思想是:找到系统全局的 ActivityManager 实例,创建一个动态代理对象,并用该代理对象替换掉原有的实例。这样,当任何地方调用 startActivity 时,请求都会先被代理对象拦截,然后由我们自定义的 InvocationHandler 来处理。
// 动态代理实现 Hook(简化版)
IActivityManager am = ActivityManagerNative.getDefault();
// 创建动态代理
Object proxy = Proxy.newProxyInstance(
am.getClassLoader(),
am.getClass().getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if ("startActivity".equals(method.getName())) {
// 在这里可以添加自定义逻辑,比如日志、权限检查等
Log.d("Hook", "拦截 startActivity 调用!");
}
// 将请求转发给原始对象
return method.invoke(am, args);
}
});
// 通过反射替换掉系统的 ActivityManager 实例
// ...