安卓序列化:Parcelable 和 Serializable

在开发中经常会用 Intent 和 Binder 进行数据传输时就要用到 Parcelable 和 Serializable,以及有的时候需要把对象持久化到存储设备上,就需要用到 Serializable。

Serializable 的使用比较简单,我们先来介绍如何使用 Serializable 来完成对象的序列化

Serializable

Serializable 是 Java 提供的一个序列化接口, 是一个空接口。需要序列化的对象只需要实现这个接口,并且选择性地声明 serialVersionUID 即可,当然也可以不声明。

public class User implements Serializable {
    private static final long serialVersionUID = 9187239014106741L;
    private int age;
    private String name;
    private boolean isMale;
    ...
}

序列化和反序列化

User 类实现了 Serializable 接口, 因此它是可以被序列化和反序列化的, 从上面的实现来看 是非常简单的。然后进行对象的序列化和反序列化也非常简单,通过 ObjectOutputStreamObjectInputStream 可轻松实现:

// 序列化
User user = new User();
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("cache.txt"));
out.writeObject(user);

// 反序列化
ObjectInputStream in = new ObjectInputStream(new FileInputStream("cache.text"));
User newUser = (User) in.readObject();
in.close();

只需要实现 Serializable 的 User 对象写到文件然后读取文件得到反序列化的新对象 newUser。得到的新对象 newUser 和原来的 user 内容完全一样,但两者不是同一个对象。

而对于系统的默认序列化和反序列化过程,则是内部执行 writeObjectreadObject 来实现的。我们可以通过重写这两个方法来改变系统的默认序列化过程,一般不用自己重写:

private void readObject(java.io.ObjectOutputStream) throws IOException{
    // ...自定义反序列化代码
}

private void writeObject(java.io.ObjectInputStream) throws IOException, ClassNotFoundException{
    // ...自定义序列化代码
}

serialVersionUID 工作机制
上面提到 serialVersionUID 可以选择性实现,那这个到底是干嘛的,在序列化和反序列化过程中起到了什么作用呢?
理论上来讲,既然系统提供了的并且没有废弃,那它必须是有用的。serialVersionUID 是用来辅助序列化和反序列化过程的,原则上序列化后的数据中的 serialVersionUID 和当前类的 serialVersionUID 相同才能被正常的反序列化。
* serialVersionUID 具体的工作机制是这样的:序列化的时候,系统会把当前类的 serialVersionUID 写入序列化的文件中(也可能是其它中介),当反序列化的时候系统会检查文件中的 serialVersionUID 是否和当前类的 serialVersionUID 一致,如果一致则反序列化成功,否则会报错提示 serialVersionUID 不一致。

尽量手动指定 serialVersionUID 的值,类型是 long,用 public static 修饰。如果不手动指定,系统会根据类的 hash 值去计算出一个 serialVersionUID 值。这是如果反序列化时当前类有所改变,比如增加或删除某个成员变量,系统就会重新计算当前类的 hash 值,这时当前类的 serialVersionUID 和序列化数据中的 serialVersionUID 就不一致,则反序列化失败,导致系统 crash。
所以我们手动指定 serialVersionUID 的值,可以很大程度上避免反序列化过程的失败。必须版本升级后我们删除了某个成员变量或增加了一些成员变量,这时反序列化依然会成功并最大限度的恢复了数据。但是如果类的结构发生了非常规性改变,比如修改类名、某个字段的类型改变,这时即使 serialVersionUID 验证通过,反序列化依然失败。

static 和 transient 修饰的变量

序列化只对类中的 filed 字段属性进行序列化,且序列化的只是类的实例对象的属性类型及值。
* static:静态变量属于类,被所有对象共享,非某个对象私有,不会被持久化
* transient:代表瞬间的意思,表示不会被持久化

Parcelable

Parcelable 是安卓提供的一个序列化接口, 相比 Serializable, Parcelable 的使用相对比较复杂, 但是效率要比 Serializable 高很多。

下面是 Parcelable 典型用法:

public class User implements Parcelable {
    private int age;
    private String name;
    private boolean isMale;
    private Book book;

    private User(Parcel in){
        age = in.readInt();
        name = in.readString();
        isMale = in.readInt() == 1;
        in.readParcelable(Thread.currentThread().getContextClassLoader())
    }

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

    @Override
    public void writeToParcel(Parcel out, int flags) {
        out.writeInt(age);
        out.writeString(name);
        out.writeInt(isMale ? 1 : 0);
        out.writeParcelable(book, 0);
    }

    public static final Parcelable.Creator<User> CREATOR = new Parcelable.Creator<User>(){
        @Override
        public User createFromParcel(Parcel in) {
            return new User(in);
        }

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

可以看到 Parcel 类在序列化和反序列化过程中有中有重要的作用,它内部封装了可序列化的数据,Parcel 可以在 Binder 中自由传输。整个序列化过程是通过 writeToParcel() 方法来实现的, 内部用 Parcel 的一系列write方法来完成;反序列化功能则由 CREATOR 实现的,内部标明了如果创建序列化对象和数组,并通过 Parcel 的一系列read完成反序列化。describeContents() 是内容描述,仅当当前对象存在文本描述符时返回1,其它情况都返回0。

  • 需要注意的是,在 User(Parcel in) 方法中,因为 book 是另一个可序列化对象,所以它的反序列化过程需要传递当前线程的上下文类加载器,否则会报找不到类异常。

借用下《安卓开发艺术探索》的图,感谢作者

系统已经为我们提供了很多实现 Parcelable 接口的类,Intent、Bundle、Bitmap等,以及 ListMap 也可以序列化,前提是它的每个元素都是可序列化的。

Parcelable 和 Serializable 区别

  • Serializable 是 Java 的序列化接口,使用简单,但是开销大,序列化和反序列都需要用到大量的 IO 操作,因此效率较低,通过序列化到存储设备中,可以实现持久化。
  • Parcelable 是 Android 提供的序列化接口,也是 Android 推荐的序列化方式,因此是首选。使用比较麻烦,效率高,主要序列化到内存。

虽然 Parcelable 的性能要强于 Serializable ,但是仍然有特殊的情况需要使用 Serializable ,而不去使 用 Parcelable。因为 Parcelable 无法将数据进行持久化,因此在将数据保存在磁盘的时候,仍然需要使用后者,因为前者无法很好的将数据进行持久化。(原因是在不同的Android版本当中,Parcelable 可能会不同,因此数据的持久化方面仍然是使用 Serializable

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值