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
// 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
@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
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。
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 结果。
