Skip to content

Bundle在Binder中的传输

追踪 Bundle 的延迟解包、ClassLoader、Binder/FD 元数据和错误边界。

基于android-17.0.0_r1
AndroidBundleParcelBinderJava框架

Bundle在Binder中的传输 ​

Bundle 在 Binder 中不是立即展开的 Map。Android 17 的 BaseBundle 收到 Parcel 后可以只保存 mParcelledData,第一次访问值时才 unparcel;Map、ClassLoader、lazy value、FD/Binder flags 和 defuse 状态共同决定解析和清理。

本文承接 Java-Parcel序列化 和 Parcel序列化详解,聚焦真实 Bundle 到 Parcel 再到 Bundle 的主线。

1. 双状态 ​

1.1 Map与Parcel ​

源码文件:frameworks/base/core/java/android/os/BaseBundle.java

java
// exactly one of mMap / mParcelledData is null
ArrayMap<String, Object> mMap = null;
volatile Parcel mParcelledData = null;
private boolean mParcelledByNative;
boolean mOwnsLazyValues = true;
private int mLazyValues;
private ClassLoader mClassLoader;

Bundle 正常情况下只由 Map 或 Parcel 中的一方持有内容。mParcelledByNative 影响读取格式,ClassLoader 决定 Parcelable 实例化。

Parcelled 是正常的延迟状态,不是错误。

2. 写入路径 ​

2.1 Bundle协议 ​

源码文件:frameworks/base/core/java/android/os/Bundle.java

Bundle.writeToParcel 调用 BaseBundle.writeToParcelInner;CREATOR.createFromParcel 调用 Parcel.readBundle。接收方可以保留序列化内容,不立即创建所有 Parcelable。

2.2 ClassLoader ​

java
@Override
public void setClassLoader(ClassLoader loader) {
    super.setClassLoader(loader);
}

访问自定义 Parcelable 前必须设置正确 ClassLoader,否则可能找不到 CREATOR 或抛 BadParcelableException。ClassLoader 是接收方解析状态,不是发送方的全局属性。

3. 延迟解包 ​

3.1 unparcel ​

源码文件:frameworks/base/core/java/android/os/BaseBundle.java

java
final void unparcel(boolean itemwise) {
    synchronized (this) {
        final Parcel source = mParcelledData;
        if (source != null) {
            Preconditions.checkState(mOwnsLazyValues);
            initializeFromParcelLocked(
                    source, true, mParcelledByNative);
        }
        if (itemwise) {
            for (int i = 0; i < mMap.size(); i++) {
                getValueAt(i, null);
            }
        }
    }
}

普通 getter 先确保 Map 存在;lazy value 只有对应 key 首次访问时才解码。itemwise=true 才强制全部值实例化。

3.2 Parcel清理 ​

initializeFromParcelLocked 没有 lazy value 时立即 recycle source;仍有 lazy value 时以 WeakReference 保存 Parcel。最后一个 lazy value 解码后可提前 destroy Parcel,不必等 Bundle GC。

4. Binder与FD ​

4.1 Binder值 ​

Bundle 中的 IBinder 最终通过 Parcel strong Binder 对象表传输,接收方得到原 Java Binder 或 BinderProxy。它不是 Map 字节复制出来的新对象。

4.2 FD状态 ​

Bundle 维护 FLAG_HAS_FDS 和 FLAG_HAS_FDS_KNOWN,可在不展开全部 value 时观察 fd 元数据。Parcelable 的内容会演进,hasFileDescriptors 不应被当成跨版本固定业务属性。

5. 错误策略 ​

5.1 BadParcelable ​

initializeFromParcelLocked 捕获 BadParcelableException。默认继续抛出;开启 defuse 时记录错误并可能清空 Map。defuse 只改变失败处理,不会修复损坏数据。

5.2 Defusable时机 ​

FLAG_DEFUSABLE 只应在最终目的地使用。中间 system process 提前吞掉异常,可能删除原本要交给 app 解析的元素。

5.3 类型为空 ​

缺失 key、显式 null、类型不匹配和反序列化失败都可能让 getter 返回 null,但日志和异常不同。带 Class 参数的 getter 能在解包时提供更严格的类型验证。

6. 测试边界 ​

源码文件:frameworks/base/core/tests/coretests/src/android/os/BundleTest.java

BundleTest 把含 string/int/ParcelFileDescriptor 的 Bundle 写入 Parcel,创建接收 Bundle 后先断言 isParcelled,再访问值并断言解包完成;同时检查 FD flags 和 hasIntent。它验证延迟状态、FD 元数据和 Parcel 回收,不覆盖所有自定义 ClassLoader 和恶意 Parcelable。

bash
rg -n "mParcelledData|mLazyValues|unparcel|initializeFromParcelLocked"   frameworks/base/core/java/android/os/BaseBundle.java
rg -n "writeToParcel|CREATOR|setClassLoader|FLAG_HAS_FDS"   frameworks/base/core/java/android/os/Bundle.java
rg -n "isParcelled|createBundleParcel|FLAG_HAS_FDS|readFromParcel"   frameworks/base/core/tests/coretests/src/android/os/BundleTest.java

排查 Bundle 跨 Binder 错误时,先确认 parcelled/Map 状态、ClassLoader、lazy Parcel ownership 和 FD/Binder flags,再区分 BadParcelableException 与 defuse 结果。