Skip to content

Parcelable自定义

追踪自定义 Parcelable 的 writeToParcel、CREATOR、typed/generic 读取、ClassLoader、FD flags 和错误边界。

基于android-17.0.0_r1
AndroidAIDLParcelableParcel

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

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

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

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(非仓库实现)

java
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

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

java
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

java
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。测试能证明具体输入/断言,不能证明任意手写字段演进都兼容。

bash
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 名。