Android进阶知识(四):IPC基本概念之序列化机制

Android进阶知识(四):IPC基本概念之序列化机制

  IPC中的基础概念主要包含三方面内容:Serializable接口、Parcelable接口以及Binder,理解了IPC的基础概念之后才能理解跨进程通信的各种方式。其中Seralizable接口和Parcelable接口可以完成对象的序列化过程。当需要通过Intent和Binder传输数据时就需要使用Parcelable或者Seralizable,还有时候需要把对象持久化到存储设备上或者网络传输给其他客户端,这个时候也需要使用Serializable来完成对象的持久化。

一、Serializable接口

  Serializable是Java所提供的一个序列化空接口,为对象提供标准的序列化和反序列化操作。使用Serializable对一个对象实现序列化,只需要这个类实现Serializable接口并声明一个serialVersionUID即可,实际上,甚至这个serialVersionUID也不是必需的(后面说明原因)。

public class Data implements Serializable {

    private static final long serialVersionUID = 1L;

    private boolean success;

    private String data;

    public boolean isSuccess() { return success; }

    public void setSuccess(boolean success) { this.success = success; }

    public String getData() { return data; }

    public void setData(String data) { this.data = data; }
}

  通过Serializable方式来实现对象的序列化,系统会自动完成对象的序列化和反序列化,只需要对序列化的对象进行操作即可,如下为一种操作示例。

// 序列化过程
Data data = new Data();
ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("cache.txt"));
outputStream.writeObject(data);
outputStream.close();

// 反序列化过程
ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("cache.txt"));
Data data1 = (Data) inputStream.readObject();
inputStream.close();

  需要注意的是,上述例子中恢复后的对象newData和data的内容虽然相同,但是两者不是同一个对象。
在这里插入图片描述
  前面提到的即使不指定serialVersionUID也可以实现序列化,那么指定的意义在哪里?如果指定的话,后面的数字代表什么?
  serialVersionUID是用来辅助序列化和反序列化过程的,原则上序列化后的数据中的serialVersionUID只有和当前类的serialVersionUID一致才能够正常地被反序列化
在这里插入图片描述
  serialVersionUID的工作机制是:序列化的时候系统会把当前类的serialVersionUID写入到序列化文件中(也可能是其他中介),当反序列化的时候系统会检测文件中的serialVersionUID,看是否和当前类的serialVersionUID一致,如果一致就说明序列化的类的版本和当前类的版本是相同的,这个时候可以成功反序列化;否则说明当前类和序列化的类相比发生了某些变化,这个时候反序列化会报InvalidClassException的异常。
在这里插入图片描述
  如果不指定serialVersionUID的值,序列化时系统会计算当前类的hash并把它赋值给serialVersionUID,再进行序列化;反序列化时当前类有所改变,那么系统会重新计算当前类的hash值并把它赋值给serialVersionUID,这个时候当前类的serialVersionUID和序列化的serialVersionUID不一致,那么反序列化就会失败,程序会出现crash
  因此serialVersionUID的作用在于,当指定了其值,可以在很大程度上避免反序列化的失败,即使进行了版本升级,程序仍能以最大的限度恢复数据。相反如果不指定,那么程序就会挂掉
在这里插入图片描述
  需要注意的是,静态成员变量属于类不属于对象,所以不会参与序列化过程;其次用transient关键字标记的成员变量不参与序列化过程

二、Parcelable接口

  Parcelable也是一个序列化接口,不同于Serializable是一个空接口,Parcelable接口中有抽象方法。一个典型的用法如下。

public class BookP implements Parcelable {

    private int id;
    private String name;
    private Book book;

    public BookP(int id, String name) {
        this.id = id;
        this.name = name;
    }

    protected BookP(Parcel in) { // 反序列化
        id = in.readInt();
        name = in.readString();
        book = in.readParcelable(Thread.currentThread().getContextClassLoader());
    }

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

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

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

    @Override
    public void writeToParcel(Parcel dest, int flags) { // 序列化
        dest.writeInt(id);
        dest.writeString(name);
        dest.writeParcelable(book, 0);
    }
}

  其中,Parcel内部包装了可序列化的数据,可以在Binder中自由传输。上述代码中,实现序列化的过程需要实现的功能有序列化、反序列化和内容描述。内容描述功能方法中一般返回0,仅当当前对象中存在文件描述符的时候,返回1。Parcelable的方法说明如下。

方法 功能 标记位
createFromParcel(Parcel in) 从序列化后的对象中创建原始对象
newArray(int size) 创建指定长度的原始对象数组
BookP(Parcel in) 从序列化后的对象中创建原始对象
WriteToParcel(Parcel out, int flags) 将当前对象写入序列化结构中,其中flags表示有两种值:0或者1。为1时表示当前对象需要作为返回值返回,不能立即释放资源;几乎所有情况都是0。 FARECLABLE_WRITE_RETURN_VALUE(1)
describeContents() 返回当前对象的内容描述。如果含有文件描述符,返回1;否则返回0。几乎所有情况都为0。 CONTENTS_FILE_DESCRIPTOR(1)

三、Parcelable和Serializable使用上的取舍

在这里插入图片描述
  Parcelable和Serializable接口都能够实现序列化,并且可用于Intent间的数据传递,那么二者如何选取?
  Serializable是Java中的序列化接口,其使用起来简单但是开销很大,序列化和反序列化过程需要大量I/O操作。
  Parcelable是Android中的序列化方式,更适用于Android平台,其缺点是使用起来稍微麻烦,但是效率很高,这是Android推荐的序列化方式。
  Parcelable主要有用在内存序列化上,通过Parcelable将对象序列化到
存储设备
中或者将对象序列化后通过网络传输也可以,但是过程稍显复杂,因此这两种情况下建议使用Serializable

参考资料:《Android开发艺术探索》

原创文章 78 获赞 25 访问量 1万+

猜你喜欢

转载自blog.csdn.net/weixin_38196407/article/details/90018420