Parcelable使用时java.lang.RuntimeException: Parcel android.os.Parcel@xxxx: Unmarshalling unknown typ...

背景

记一次 Parcelable 序列化不一致导致的运行时错误

错误截图

在 Android 开发过程中,我们经常会使用 Parcelable 接口来实现对象在组件(如 Activity 或 Fragment)之间的高效传递。虽然 Parcelable 性能优异,但其手动序列化和反序列化的机制也容易因疏忽引发问题。

最近在开发中就遇到了一个看似简单却耗费了不少排查时间的问题——在通过 Intent 传递一个自定义 Parcelable 对象时,应用在反序列化阶段崩溃,报错信息指向 Parcel 读取异常。

问题场景

问题出现在一个名为 MediaItem 的可序列化类中。由于业务需求变化,我为该类新增了两个字段:widthheight(宽度和高度),并在 writeToParcel() 方法中正确添加了写入逻辑:

@Override
public void writeToParcel(Parcel dest, int flags) {
    dest.writeLong(id);
    dest.writeString(mimeType);
    dest.writeParcelable(uri, 0);
    dest.writeParcelable(thumbUri, 1);
    dest.writeLong(size);
    dest.writeLong(duration);
    dest.writeInt(height);
    dest.writeInt(width);
}

然而,在反序列化构造函数中,我遗漏了对这两个新字段的读取

private MediaItem(Parcel source) {
    id = source.readLong();
    mimeType = source.readString();
    uri = source.readParcelable(Uri.class.getClassLoader());
    thumbUri = source.readParcelable(Uri.class.getClassLoader());
    size = source.readLong();
    duration = source.readLong();
    height = source.readInt();
    width = source.readInt();
}

注意:虽然上面的代码看起来“读写顺序一致”,但如果你在添加字段时只写了 writeInt(height)writeInt(width),却忘了在构造函数中调用 readInt() 来读取它们,就会导致后续字段的读取错位,从而引发 BadParcelableException 或类型不匹配错误。

根本原因

Parcelable 的序列化机制依赖于写入和读取顺序的严格一致。当你向 Parcel 中写入数据时,系统会按顺序存储;而在反序列化时,也必须按照完全相同的顺序逐个读取。

一旦写入和读取的字段数量或顺序不匹配,就会导致:

  • 后续字段读取错位(例如把 int 当作 long 读取)
  • 抛出 Parcel android.os.Parcel@4265a958: Unmarshalling unknown type code 等诡异异常
  • 应用崩溃且堆栈信息不易定位问题根源

解决方案

确保 writeToParcel()Parcelable 构造函数中的读写操作一一对应、顺序一致

修改后的完整代码如下:

public class MediaItem implements Parcelable {
    private long id;
    private String mimeType;
    private Uri uri;
    private Uri thumbUri;
    private long size;
    private long duration;
    private int height;
    private int width;

    // Parcelable 构造函数:必须按写入顺序读取
    private MediaItem(Parcel source) {
        id = source.readLong();
        mimeType = source.readString();
        uri = source.readParcelable(Uri.class.getClassLoader());
        thumbUri = source.readParcelable(Uri.class.getClassLoader());
        size = source.readLong();
        duration = source.readLong();
        height = source.readInt();  // 新增:读取 height
        width = source.readInt();   // 新增:读取 width
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeLong(id);
        dest.writeString(mimeType);
        dest.writeParcelable(uri, 0);
        dest.writeParcelable(thumbUri, flags);
        dest.writeLong(size);
        dest.writeLong(duration);
        dest.writeInt(height);      // 新增:写入 height
        dest.writeInt(width);       // 新增:写入 width
    }

    @Override
    public int describeContents() {
        return 0;
    }

    public static final Creator<MediaItem> CREATOR = new Creator<MediaItem>() {
        @Override
        public MediaItem createFromParcel(Parcel source) {
            return new MediaItem(source);
        }

        @Override
        public MediaItem[] newArray(int size) {
            return new MediaItem[size];
        }
    };
}

预防建议

  1. 写完 writeToParcel 后立即检查反序列化构造函数,确保每一步读取都对应一次写入。
  2. 使用 Android Studio 插件(如 ParcelableGenerator)来自动生成 Parcelable 代码,减少人为错误。
  3. 在修改已有 Parcelable 类时,务必重新测试序列化流程,尤其是在跨进程或跨 Activity 传递时。

总结

这个错误虽小,却极具迷惑性。它不会在编译期报错,而是在运行时才暴露,且错误信息往往不够直观。关键在于理解 Parcelable 的底层机制:它不是基于字段名,而是基于写入/读取的顺序

只要记住:“写一个,读一个;顺序不能乱”,就能有效避免此类问题。

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

木易 士心

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值