背景 问题起源 在公司看到这样的代码(已脱敏):
1 2 3 4 5 myService.registerCallback(object : IMyCallback.Stub() { fun myCallback () { ... } })
这段代码创建了一个匿名的 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 2 3 4 5 6 7 8 9 10 11 12 13 public final class Parcel { ... public final void writeStrongBinder (IBinder val) { nativeWriteStrongBinder(mNativePtr, val); } ... }
这是一个 native 方法,调用进入 JNI 层。
2. JNI 层转换 1 2 3 4 5 6 7 8 9 10 static void android_os_Parcel_writeStrongBinder (JNIEnv* env, jclass clazz, jlong nativePtr, jobject object) { Parcel* parcel = reinterpret_cast <Parcel*>(nativePtr); if (parcel != NULL ) { const status_t err = parcel->writeStrongBinder (ibinderForJavaObject (env, object)); if (err != NO_ERROR) { signalExceptionForError (env, clazz, err); } } }
注意这里的 ibinderForJavaObject 调用,它是 Java Binder 对象和 Native IBinder 之间的桥梁。
3. Java 对象到 Native 对象的映射 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 sp<IBinder> ibinderForJavaObject (JNIEnv* env, jobject obj) { if (obj == NULL ) return NULL ; if (env->IsInstanceOf (obj, gBinderOffsets.mClass)) { JavaBBinderHolder* jbh = (JavaBBinderHolder*) env->GetLongField (obj, gBinderOffsets.mObject); if (jbh == nullptr ) { ALOGE ("JavaBBinderHolder null on binder" ); return nullptr ; } return jbh->get (env, obj); } if (env->IsInstanceOf (obj, gBinderProxyOffsets.mClass)) { return getBPNativeData (env, obj)->mObject; } ALOGW ("ibinderForJavaObject: %p is not a Binder object" , obj); return NULL ; }
关键点分析:
每个 Java Binder 对象内部持有一个 JavaBBinderHolder(通过 gBinderOffsets.mObject 字段)
JavaBBinderHolder 负责管理对应的 Native JavaBBinder 对象
对于 BinderProxy(代表远端 Binder),直接返回其内部的 Native 对象
4. JavaBBinderHolder 的 get 方法 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 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 class JavaBBinderHolder {public : sp<JavaBBinder> get (JNIEnv* env, jobject obj) { sp<JavaBBinder> b; { AutoMutex _l(mLock); b = mBinder.promote (); } if (b) return b; b = sp<JavaBBinder>::make (env, obj); { AutoMutex _l(mLock); if (sp<JavaBBinder> b2 = mBinder.promote (); b2) return b2; if (mVintf) { ::android::internal::Stability::markVintf (b.get ()); } if (mSetExtensionCalled) { jobject javaIBinderObject = env->CallObjectMethod (obj, gBinderOffsets.mGetExtension); sp<IBinder> extensionFromJava = ibinderForJavaObject (env, javaIBinderObject); if (extensionFromJava != nullptr ) { b.get ()->setExtension (extensionFromJava); } } mBinder = b; ALOGV ("Creating JavaBinder %p (refs %p) for Object %p, weakCount=%" PRId32 "\n" , b.get (), b->getWeakRefs (), obj, b->getWeakRefs ()->getWeakCount ()); } return b; } ... };
这段代码的逻辑:
首先尝试将弱引用 mBinder 提升为强引用
如果已存在,直接返回
如果不存在,创建新的 JavaBBinder 对象
使用双重检查锁定模式确保线程安全
将新的 JavaBBinder 保存为弱引用
5. 关键的 GlobalRef 创建 这里会创建一个 JavaBBinder 对象,创建时同步创建了 GlobalRef:
1 2 3 4 5 6 7 8 9 10 11 12 13 class JavaBBinder : public BBinder {public : JavaBBinder (JNIEnv* env, jobject object) : mVM (jnienv_to_javavm (env)), mObject (env->NewGlobalRef (object)) { ALOGV ("Creating JavaBBinder %p\n" , this ); gNumLocalRefsCreated.fetch_add (1 , std::memory_order_relaxed); gcIfManyNewRefs (env); } ... }
在 JavaBBinder 的构造函数中,通过 env->NewGlobalRef(object) 创建了 JNI 全局引用!
实践应用:同进程大数据传输 在实际开发中,Binder 机制还有一个实用的技巧:利用 Binder 对象在同进程内传递大量数据。
场景描述 在开发中遇到这样一个场景:需要在同进程内将大量数据(如几 MB 的图片或序列化数据)通过 Intent 传递给二级页面的 Activity。传统的做法是将数据放入 Intent 的 extras 中,但 Intent 有大小限制(约 1MB),大数据会导致 TransactionTooLargeException。
解决方案 利用 Binder 对象在同进程内的特殊处理机制来解决这个问题。以下是示例代码:
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 class BinderPassDataActivity : Activity () { companion object { fun wrapData (data : ByteArray ) : Bundle { return Bundle().apply { putBinder("token" , DataBinder(data )) } } fun unwrapData (data : Bundle ?) : ByteArray? { return (data ?.getBinder("token" ) as ? DataBinder)?.data } } override fun onCreate (states: Bundle ?) { super .onCreate(states) val data = unwrapData(intent.getBundleExtra("data" )) Log.d(tag(), "data = ${data?.decodeToString()} " ) } class DataBinder (val data : ByteArray) : Binder() }
总结 通过本文的分析,我们解答了开头提出的两个问题:
Q1:远端进程是否会持有强引用? 会 。当 Binder 对象通过 writeStrongBinder 写入 Parcel 时,Native 层会立即创建 JavaBBinder 对象,并在其构造函数中通过 NewGlobalRef 建立 JNI 全局引用。这个引用是 JVM 层面的强引用,会阻止 Java 对象被 GC 回收。
Q2:引用建立的时机是什么? 在 Parcel.writeStrongBinder() 调用时立即建立 。具体的调用链为:
1 2 3 4 5 6 writeStrongBinder (Java) → nativeWriteStrongBinder (JNI) → ibinderForJavaObject → JavaBBinderHolder::get → new JavaBBinder → NewGlobalRef ← 引用在此建立
因此,不存在 writeStrongBinder 和 transact 之间的引用空窗期。
设计思考 虽然从设计角度看,让远端进程控制本地对象生命周期似乎不太合理,但这种设计有其必要性:
保证回调可靠性 :确保远端调用本地回调时,对象仍然有效
自动清理机制 :当远端释放引用时,Native 层的 JavaBBinder 会被销毁,同时释放 GlobalRef
双向引用管理 :Binder 驱动会跟踪跨进程引用,当远端进程死亡时,会通知本地清理资源
理解 Binder 的引用机制,对于编写稳定的 Android 跨进程通信代码至关重要。
参考 Binder系列1—Binder Driver初探 - Gityuan博客 | 袁辉辉的技术博客
Binder子系统之调试分析(三) - Gityuan博客 | 袁辉辉的技术博客