Android Binder 引用机制分析

背景

问题起源

在公司看到这样的代码(已脱敏):

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 对象的引用链,对比以下两种场景:

  1. 场景 A:创建 Binder 对象但不写入 Parcel
  2. 场景 B:创建 Binder 对象并调用 Parcel.writeStrongBinder()

实验结果

场景 A - 未使用 Parcel 引用:

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

场景 B - 使用 Parcel 引用:

对比发现,使用 Parcel 写入后,多了一个 Cleaner 引用。这表明系统在 Native 层建立了对 Java 对象的引用关系。

初步结论

从实验结果可以看出:

  1. writeStrongBinder 确实会在 Native 层建立对 Java Binder 对象的引用
  2. 这种引用通过 Cleaner 机制管理,说明系统有自动清理的机制
  3. 引用是在写入 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 {
...

/**
* Write an object into the parcel at the current dataPosition(),
* growing dataCapacity() if needed.
*/
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;

// Instance of Binder?
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);
}

// Instance of BinderProxy?
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);
// must take lock to promote because we set the same wp<>
// on another thread.
b = mBinder.promote();
}

if (b) return b;

// b/360067751: constructor may trigger GC, so call outside lock
b = sp<JavaBBinder>::make(env, obj);

{
AutoMutex _l(mLock);
// if it was constructed on another thread in the meantime,
// return that. 'b' will just get destructed.
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;
}

...
};

这段代码的逻辑:

  1. 首先尝试将弱引用 mBinder 提升为强引用
  2. 如果已存在,直接返回
  3. 如果不存在,创建新的 JavaBBinder 对象
  4. 使用双重检查锁定模式确保线程安全
  5. 将新的 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 /* Java Binder */ 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 ← 引用在此建立

因此,不存在 writeStrongBindertransact 之间的引用空窗期。

设计思考

虽然从设计角度看,让远端进程控制本地对象生命周期似乎不太合理,但这种设计有其必要性:

  1. 保证回调可靠性:确保远端调用本地回调时,对象仍然有效
  2. 自动清理机制:当远端释放引用时,Native 层的 JavaBBinder 会被销毁,同时释放 GlobalRef
  3. 双向引用管理:Binder 驱动会跟踪跨进程引用,当远端进程死亡时,会通知本地清理资源

理解 Binder 的引用机制,对于编写稳定的 Android 跨进程通信代码至关重要。

参考

Binder系列1—Binder Driver初探 - Gityuan博客 | 袁辉辉的技术博客

Binder子系统之调试分析(三) - Gityuan博客 | 袁辉辉的技术博客


Android Binder 引用机制分析
https://neo.mufanc.xyz/posts/38859/
作者
Mufanc
发布于
2025年6月21日
许可协议