背景
问题起源
在公司看到这样的代码(已脱敏):
1 | myService.registerCallback(object : IMyCallback.Stub() { |
这段代码创建了一个匿名的 AIDL 回调对象并注册到远程服务。这里存在一个潜在的疑问:如果 IMyCallback 对象会被 GC 回收,这种写法是否有问题?
直观上分析,对象传递给远端后,本地不再持有该匿名对象的引用。如果远端尝试回调时,本地对象已被回收,就会触发 DeadObjectException。无论是从实际用法上看,还是如 LLM 所言,似乎都暗示系统会强制保存一份引用。但从设计角度思考,让一个远程进程控制本地对象的生命周期似乎又不太合理。
本文将从源码层面深入分析 Binder 对象发送到远端后的引用行为,解答以下核心问题:
- 宏观问题:远端进程是否会持有强引用?
- 微观问题:如果有强引用,建立的时机是什么?是在
Parcel.writeStrongBinder时,还是Binder.transact之后?如果在Binder.transact之后,这中间是否存在微小的时间窗口,使得对象没有被任何引用持有?
实验分析
为了验证 Binder 对象的引用行为,我设计了一个简单的实验:观察 Binder 对象在写入 Parcel 前后的内存引用变化。
实验设计
实验通过 Heap Dump 分析 Binder 对象的引用链,对比以下两种场景:
- 场景 A:创建 Binder 对象但不写入 Parcel
- 场景 B:创建 Binder 对象并调用
Parcel.writeStrongBinder()
实验结果
场景 A - 未使用 Parcel 引用:

在这种场景下,Binder 对象只有标准的 Java 对象引用关系,没有额外的系统级引用。
场景 B - 使用 Parcel 引用:

对比发现,使用 Parcel 写入后,多了一个 Cleaner 引用。这表明系统在 Native 层建立了对 Java 对象的引用关系。
初步结论
从实验结果可以看出:
writeStrongBinder确实会在 Native 层建立对 Java Binder 对象的引用- 这种引用通过
Cleaner机制管理,说明系统有自动清理的机制 - 引用是在写入 Parcel 时建立的,而非在
transact时才建立
但具体是如何建立的?引用的生命周期如何管理?还需要深入源码分析。
源码分析
调用链梳理
为了理解 Binder 引用的建立机制,我们需要从 Java 层跟踪到 Native 层,梳理完整的调用链。
1. Java 层入口
从 Parcel 的 writeStrongBinder 方法入手:
1 | public final class Parcel { |
这是一个 native 方法,调用进入 JNI 层。
2. JNI 层转换
1 | static void android_os_Parcel_writeStrongBinder(JNIEnv* env, jclass clazz, jlong nativePtr, jobject object) |
注意这里的 ibinderForJavaObject 调用,它是 Java Binder 对象和 Native IBinder 之间的桥梁。
3. Java 对象到 Native 对象的映射
1 | sp<IBinder> ibinderForJavaObject(JNIEnv* env, jobject obj) |
关键点分析:
- 每个 Java Binder 对象内部持有一个
JavaBBinderHolder(通过gBinderOffsets.mObject字段) JavaBBinderHolder负责管理对应的 NativeJavaBBinder对象- 对于 BinderProxy(代表远端 Binder),直接返回其内部的 Native 对象
4. JavaBBinderHolder 的 get 方法
1 | class JavaBBinderHolder |
这段代码的逻辑:
- 首先尝试将弱引用
mBinder提升为强引用 - 如果已存在,直接返回
- 如果不存在,创建新的
JavaBBinder对象 - 使用双重检查锁定模式确保线程安全
- 将新的
JavaBBinder保存为弱引用
5. 关键的 GlobalRef 创建
这里会创建一个 JavaBBinder 对象,创建时同步创建了 GlobalRef:
1 | class JavaBBinder : public BBinder |
在 JavaBBinder 的构造函数中,通过 env->NewGlobalRef(object) 创建了 JNI 全局引用!
实践应用:同进程大数据传输
在实际开发中,Binder 机制还有一个实用的技巧:利用 Binder 对象在同进程内传递大量数据。
场景描述
在开发中遇到这样一个场景:需要在同进程内将大量数据(如几 MB 的图片或序列化数据)通过 Intent 传递给二级页面的 Activity。传统的做法是将数据放入 Intent 的 extras 中,但 Intent 有大小限制(约 1MB),大数据会导致 TransactionTooLargeException。
解决方案
利用 Binder 对象在同进程内的特殊处理机制来解决这个问题。以下是示例代码:
1 | class BinderPassDataActivity : Activity() { |
总结
通过本文的分析,我们解答了开头提出的两个问题:
Q1:远端进程是否会持有强引用?
会。当 Binder 对象通过 writeStrongBinder 写入 Parcel 时,Native 层会立即创建 JavaBBinder 对象,并在其构造函数中通过 NewGlobalRef 建立 JNI 全局引用。这个引用是 JVM 层面的强引用,会阻止 Java 对象被 GC 回收。
Q2:引用建立的时机是什么?
在 Parcel.writeStrongBinder() 调用时立即建立。具体的调用链为:
1 | writeStrongBinder (Java) |
因此,不存在 writeStrongBinder 和 transact 之间的引用空窗期。
设计思考
虽然从设计角度看,让远端进程控制本地对象生命周期似乎不太合理,但这种设计有其必要性:
- 保证回调可靠性:确保远端调用本地回调时,对象仍然有效
- 自动清理机制:当远端释放引用时,Native 层的
JavaBBinder会被销毁,同时释放GlobalRef - 双向引用管理:Binder 驱动会跟踪跨进程引用,当远端进程死亡时,会通知本地清理资源
理解 Binder 的引用机制,对于编写稳定的 Android 跨进程通信代码至关重要。