Parcelable自定义
自定义 Parcelable 的协议由写入顺序和 CREATOR 读取顺序共同定义。Java Parcel 提供两条主要路径:通用 writeParcelable 会写类名,读取时依赖 ClassLoader 查找 CREATOR;typed object 不写类名,调用方直接提供 Creator,开销更小但类型必须由接口 schema 预先确定。
本文面向已经读过 Java-Parcel序列化、AIDL语法详解 和 Stub与Proxy代码生成 的读者。本文追踪 Java Parcelable 接口、Parcel 两条编码路径、flags、ClassLoader 和错误边界,不讨论 Kotlin 插件等第三方生成器。
1. Parcelable契约
1.1 writeToParcel
源码文件:frameworks/base/core/java/android/os/Parcelable.java
public interface Parcelable {
int describeContents();
void writeToParcel(Parcel dest, int flags);
interface Creator<T> {
T createFromParcel(Parcel source);
T[] newArray(int size);
}
}实现必须公开 static non-null CREATOR。writeToParcel 与 createFromParcel 必须使用同一字段顺序和类型;Parcel 不保存字段名来自动纠正顺序。
1.2 flags
PARCELABLE_WRITE_RETURN_VALUE 表示对象是返回值/out/inout,某些资源型 Parcelable 可在写出后释放 owner;PARCELABLE_ELIDE_DUPLICATES 允许父对象负责重复数据。flags 是上下文协议,不是对象业务状态。
1.3 describeContents
若序列化包含 fd,describeContents 必须包含 CONTENTS_FILE_DESCRIPTOR;Bundle/Parcel 可以据此预判 FD。错误返回 0 会让上层错误判断资源类型。
2. Generic路径
2.1 写类名
源码文件:frameworks/base/core/java/android/os/Parcel.java
public final void writeParcelable(
Parcelable p, int flags) {
if (p == null) {
writeString(null);
return;
}
writeParcelableCreator(p);
p.writeToParcel(this, flags);
}通用路径先写 class/creator 标识,再写对象字段。它适合运行时类型未知的容器,但会增加类名/反射/ClassLoader 成本。
2.2 ClassLoader读取
readParcelable(ClassLoader, Class) 读取 creator 名称,使用传入 ClassLoader 查找 class 与 CREATOR,再验证结果是否符合期望 Class。错误 loader、缺少 public static CREATOR 或 creator 类型错误会抛 BadParcelableException。
3. Typed路径
3.1 Null marker
源码文件:frameworks/base/core/java/android/os/Parcel.java
public final <T extends Parcelable> void writeTypedObject(
T val, int flags) {
if (val != null) {
writeInt(1);
val.writeToParcel(this, flags);
} else {
writeInt(0);
}
}typed path 只写 null marker 与字段,不写类名;读取端必须使用同一个 Creator。
3.2 读取Creator
readTypedObject(CREATOR) 先读 marker,非零时调用 Creator.createFromParcel。AIDL generator 知道参数/返回类型,通常选择 typed path,提高效率并减少 ClassLoader 不确定性。
4. 自定义样例
4.1 写入
示例协议:自定义 Record(非仓库实现)
public final class Record implements Parcelable {
private final int id;
private final String name;
@Override
public void writeToParcel(Parcel out, int flags) {
out.writeInt(id);
out.writeString(name);
}
@Override
public int describeContents() {
return 0;
}
}字段顺序是 id 后 name,任何版本的 reader 必须按相同顺序读取。本文样例只说明协议结构,不声称已加入 AOSP 源码。
4.2 Creator读取
示例协议:自定义 Record(非仓库实现)
源码协议参考:frameworks/base/core/java/android/os/Parcelable.java
public static final Parcelable.Creator<Record> CREATOR =
new Parcelable.Creator<>() {
public Record createFromParcel(Parcel in) {
return new Record(
in.readInt(), in.readString());
}
public Record[] newArray(int size) {
return new Record[size];
}
};读取少字段会留下 dataAvail,读取多字段会越界或污染后续对象;AIDL 生成 structured Parcelable 会加入 size prefix/overflow 检查,而手写 Parcelable 必须自行维护兼容协议。
5. AIDL声明
5.1 Unstructured
源码文件:system/tools/aidl/aidl_language_y.yy
语法样例:AIDL unstructured parcelable
parcelable Record;unstructured 声明告诉 AIDL 外部 Java/C++ 类型已自行实现序列化。Java backend 可以引用已有 Parcelable;稳定 AIDL/NDK/Rust 对 unstructured parcelable 有额外限制。
5.2 Structured
源码文件:system/tools/aidl/aidl_language_y.yy
语法样例:AIDL structured parcelable
parcelable Record {
int id;
String name;
}structured parcelable 由 AIDL 生成字段读写和 size/compatibility 逻辑。自定义手写 Parcelable 与 structured AIDL 是两种 owner,不应同时定义冲突 wire format。
6. 错误与安全
错误 ClassLoader、缺少 CREATOR、字段顺序不一致、长度未校验和恶意 size 都可能抛 BadParcelableException 或导致后续 Parcel 位置错乱。Android 17 typed getter 可传 Class 进行运行时类型验证,减少错误强转。
含 fd 的 Parcelable 必须正确 report CONTENTS_FILE_DESCRIPTOR,并按 return-value flags 管理 ownership;否则可能导致 fd 泄漏或错误关闭。
7. 测试边界
源码文件:frameworks/base/core/tests/coretests/src/android/os/ParcelTest.java
ParcelTest 覆盖 typed arrays、错误长度、Binder arrays 和 Parcelable round-trip;BundleTest 使用自定义 Parcelable 验证 lazy parcelled 状态与 ClassLoader。测试能证明具体输入/断言,不能证明任意手写字段演进都兼容。
rg -n "interface Parcelable|CREATOR|PARCELABLE_WRITE_RETURN_VALUE|CONTENTS_FILE_DESCRIPTOR" frameworks/base/core/java/android/os/Parcelable.java
rg -n "writeParcelable|readParcelable|writeTypedObject|readTypedObject" frameworks/base/core/java/android/os/Parcel.java
rg -n "Parcelable|CREATOR|BadParcelableException|typed" frameworks/base/core/tests/coretests/src/android/os/ParcelTest.java frameworks/base/core/tests/coretests/src/android/os/BundleTest.java排查 Parcelable 失败时,先确认 generic/typed 路径、ClassLoader/Creator、null marker、字段顺序和 size,再检查 flags/FD ownership;不要只比较 Java class 名。
