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

在 Android 开发过程中,我们经常会使用 Parcelable 接口来实现对象在组件(如 Activity 或 Fragment)之间的高效传递。虽然 Parcelable 性能优异,但其手动序列化和反序列化的机制也容易因疏忽引发问题。
最近在开发中就遇到了一个看似简单却耗费了不少排查时间的问题——在通过 Intent 传递一个自定义 Parcelable 对象时,应用在反序列化阶段崩溃,报错信息指向 Parcel 读取异常。
问题场景
问题出现在一个名为 MediaItem 的可序列化类中。由于业务需求变化,我为该类新增了两个字段:width 和 height(宽度和高度),并在 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];
}
};
}
预防建议
- 写完
writeToParcel后立即检查反序列化构造函数,确保每一步读取都对应一次写入。 - 使用 Android Studio 插件(如 ParcelableGenerator)来自动生成
Parcelable代码,减少人为错误。 - 在修改已有
Parcelable类时,务必重新测试序列化流程,尤其是在跨进程或跨 Activity 传递时。
总结
这个错误虽小,却极具迷惑性。它不会在编译期报错,而是在运行时才暴露,且错误信息往往不够直观。关键在于理解 Parcelable 的底层机制:它不是基于字段名,而是基于写入/读取的顺序。
只要记住:“写一个,读一个;顺序不能乱”,就能有效避免此类问题。
1163

被折叠的 条评论
为什么被折叠?



