Android IPC基础概念之(序列化)介绍

来源:互联网 发布:那个网络射击游戏好玩 编辑:程序博客网 时间:2024/04/29 16:30

前言

当我们需要通过 Intent 或 Binder 传输数据时需要先把数据或者要传输的对象完成序列化操作, 这时就需要使用 Serializable 或者 Parcelable。还有的时候我们需要把对象持久化到存储设备上或者通过网络传输给其他客户端,这时候也需要使用 Serializable 来完成对象的持久化。

Serializable 接口

Serializable 是 Java 提供的一个序列化接口,它是一个空接口,为对象提供标准的序列化和反序列化操作。使用 Serializable 来实现序列化相当简单,只需要实现 Serializable 接口并声明一个 serialVersionUID 即可;实际上,这个 serialVersionUID 也不是必需的,我们不声明这个 serialVersionUID 同样也可以实现序列化,但是这将会对反序列化过程产生影响。

public class Student implements Serializable {    private static final long serialVersionUID = 519067123721295773L;    public int age;    public String name;    public Student(int age, String name) {        this.age = age;        this.name = name;    }    @Override    public String toString() {        return super.toString() + " age:" + age + " name:" + name;    }}

序列化过程的示例:

// 序列化Student student = new Student(18, "张三");ObjectOutputStream out = null;try {    out = new ObjectOutputStream(new FileOutputStream(Environment.getExternalStorageDirectory().getPath() + "/studentCache"));    out.writeObject(student);    Log.i("序列化", "student: " + student);} catch (IOException e) {    e.printStackTrace();} finally {    try {        if (out != null) {            out.close();        }    } catch (IOException e) {        e.printStackTrace();    }}

反序列化过程示例:

// 反序列化ObjectInputStream in = null;try {    in = new ObjectInputStream(new FileInputStream(Environment.getExternalStorageDirectory().getPath() + "/studentCache"));    Student newStudent = (Student) in.readObject();    Log.i("反序列化---", "newStudent: " + newStudent);} catch (Exception e) {    e.printStackTrace();} finally {    if (in != null) {        try {            in.close();        } catch (IOException e) {            e.printStackTrace();        }    }}

上述代码演示了采用 Serializable 方式序列化对象的典型过程,很简单,只需要把实现了 Serializable 接口的 Student 对象写到文件中就可以快速恢复了,恢复后的对象 newStudent 和 student 的内容完全一样,但是二者并不是同一个对象。通过 log 就可以看出。

 I/序列化: student: com.wiwide.aidldemo.bean.Student@121a3f63 age:18 name:张三 I/反序列化---: newStudent: com.wiwide.aidldemo.bean.Student@1000bd51 age:18 name:张三

开始提到的 serialVersionUID 是用来辅助序列化和反序列化过程的,原则上序列化后的数据中的 serialVersionUID 只有和当前类的 serialVersionUID 相同才能够正常地被反序列化。

serialVersionUID 工作机制:

序列化的时候系统会把当前类的 serialVersionUID 写入到序列化的文件中(也可能是其他的中介),当反序列化的时候系统会去检测文件中的 serialVersionUID,看它是否和当前类的 serialVersionUID 一致,如果一致就说明序列化类的版本和当前类的版本是相同的,这个时候可以成功得反序列化;否则就说明当前类和序列化的类相比发生了某些变换,比如成员变量的数量、类型可能发生了改变,这个时候是无法正常反序列化的。

分析结论

一般来说,我们应该手动指定 serialVersionUID 的值,比如 1L,也可以让 IDE 根据当前类的结构去自动生成它的 hash 值,这样序列化和反序列化时两者的 serialVersionUID 是相同的,因此可以正常进行反序列化。如果不手动指定 serialVersionUID 的值,反序列化时当前类有所改变,比如增加或者删除了某些成员变量,那么系统就会重新计算当前类的 hash 值并把它赋值给 serialVersionUID,这个时候当前类的 serialVersionUID 就和序列化的数据中的 serialVersionUID 不一致,于是反序列化失败,程序就会出现 crash。

所以,我们可以明显感觉到 serialVersionUID 的作用,当我们手动指定了它之后,就可以在很大程度上避免反序列化过程的失败。比如当版本升级后,我们可能删除了某个成员变量也可能增加了一些新的成员变量,这个时候我们的反序列化过程仍然能够成功,程序仍然能最大限度地恢复数据,相反,如果不指定 serialVersionUID 的话,程序则会挂掉。

当然,我们还要考虑另外一种情况,如果类的结构发生了非常规性的改变,比如修改了类名,修改了成员变量的类型,这个时候尽管 serialVersionUID 验证通过了,但是反序列化过程还是会失败,因为类结构有了毁灭性的改变,根本无法从老版本的数据中还原出一个新的类结构的对象。

注意

  1. 静态成员变量属于类,不属于对象,所以不会参与序列化过程。
  2. 用 transient 关键字标记的成员变量不参与序列化过程。

Parcelable 接口

Parcelable 也是一个接口,只要实现这个接口,一个类的对象就可以实现序列化并可以通过 Intent 和 Binder 传递。

在 Android Studio 的 Settings 界面的 Plugins (插件)标签下安装 Parcelable 插件,如图:

这里写图片描述

然后在要序列化的类中,通过 Alt + Insert 打开代码自动生成窗口,选择 Parcelable 按照提示即可自动生成完整的代码。

这里写图片描述

public class Book implements Parcelable{    public int bookId;    public String bookName;    public Book(int bookId, String bookName) {        this.bookId = bookId;        this.bookName = bookName;    }    @Override    public int describeContents() {        return 0;    }    @Override    public void writeToParcel(Parcel dest, int flags) {        dest.writeInt(this.bookId);        dest.writeString(this.bookName);    }    protected Book(Parcel in) {        this.bookId = in.readInt();        this.bookName = in.readString();    }    public static final Creator<Book> CREATOR = new Creator<Book>() {        @Override        public Book createFromParcel(Parcel source) {            return new Book(source);        }        @Override        public Book[] newArray(int size) {            return new Book[size];        }    };}

这里先说一下 Parcel,Parcel 内部包装了可序列化的数据,可以在 Binder 中自由传输。从上述代码中可以看出,在序列化过程中需要实现的功能有序列化、反序列化和内容描述。序列化功能由 writeParcel 方法来完成,最终是通过 Parcel 中的一系列 write 方法来完成的,反序列化功能由 CREATOR 来完成,其内部标明了如何创建序列化对象和数组,并通过 Parcel 的一系列 read 方法来完成反序列化过程;内容描述功能由 describeContents 方法来完成,几乎在所有情况下这个方法都应该返回 0,仅当当前对象中存在文件描述符时,此方法返回 1。

系统已经为我们提供了许多实现了 Parcelable 接口的类,它们都是可以直接序列化的,比如:Intent、Bundle、Bitmap 等,同时 List 和 Map 也可以序列化,前提是它们里边的每个元素都是可序列化的。

总结

既然 Serializable 和 Parcelable 都能够实现序列化并且都可以用于 Intent 间的数据传递,那么二者应该如何选择呢?

Serializable
Java 中的序列化接口,使用简单但开销很大,序列化过程和反序列化过程需要大量的 I/O 操作。
一般需要将对象序列化到存储设备中或者将对象序列化后通过网络传输时使用 Serializable 。(当然 Parcelable 也可以实现,过程会稍微复杂一些)

Parcelable
Android 中的序列化方式,缺点使用起来稍微麻烦,不过可以借助插件生成代码,但效率很高。
主要用在内存序列化上,除了上述两种情况推荐使用 Serializable,其他情况首选 Parcelable 。