Android 序列化之 Serializable 和 Parcelable

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u010289802/article/details/81232883

简介

序列化是指把 Java 对象转换为字节序列并存储到一个存储媒介的过程。反之,把字节序列恢复为 Java 对象的过程则称之为反序列化。

作用

  • 永久性保存对象,保存对象的字节序列到本地文件中;
  • 通过序列化对象在网络中传递对象;
  • 通过序列化在进程间传递对象。

使用

1.Serializable

只需要实现 Serializable 接口,并提供一个序列化版本id (serialVersionUID) 即可。

public class User implements Serializable{
    private static final long serialVersionUID = -2457171891775313955L;
    
    private String name;
    private int age;

    public User(String name, int age) {
        this.name = name;
        this.age = age;
    }

    /**
     * Getter and Setter 方法
     */
}
/**
 * 存储Serializable数据到本地
 */
private void storageSerializableData() {
    List<User> mUsers = new ArrayList<>();
    mUsers.add(new User("a", 3));
    mUsers.add(new User("b", 4));

    FileOutputStream fos = null;
    ObjectOutputStream oos = null;

    try {
        File file = new File(Environment.getExternalStorageDirectory().toString() + "/" + "users.dat");
        if (!file.exists()) {
            file.createNewFile();
        }
        fos = new FileOutputStream(file.toString());
        oos = new ObjectOutputStream(fos);
        oos.writeObject(mUsers);
    } catch (Exception e) {
        Log.i("TAG",e.toString());
    } finally {
        try {
            if (oos != null)
                oos.close();
            if (fos != null)
                fos.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
/**
 * 读取本地的Seriablizable数据
 */
private void getSerializableData() {
    FileInputStream fis=null;
    ObjectInputStream ois=null;
    File file = new File(Environment.getExternalStorageDirectory().toString()
            + "/" + "users.dat");
    if (file.exists()){
        try {
            fis=new FileInputStream(file.toString());
            ois=new ObjectInputStream(fis);
            List<User> mUsers = (List<User>) ois.readObject();
            for (User user : mUsers){
                Log.i("User",user.getName());
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

即使不指定 serialVersionUID 也可以实现序列化,那为什么还要求指定呢?

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

serialVersionUID 的详细工作过程是这样的:序列化的时候系统会把当前类的 serialVersionUID 写入序列化的二进制文件中,当反序列化的时候系统会检测文件中的 serialVersionUID 是否和当前类的 serialVersionUID 一致,如果一致就说明序列化的类的版本和当前类的版本是相同的,这个时候可以成功反序列化;否则说明当前类和反序列化的类相比发生了某些变化,比如成员变量的数量、类型发生了变化,这个时候是无法正常反序列化的。

一般来说,我们应该手动指定 serialVersionUID 的值,比如1L,也可以让 IDE 根据当前类的结构自动去生成它的 hash 值,这样序列化和反序列化时两者的 serialVersionUID 是相同的,因此可以正常进行反序列化操作。如果不手动指定 serialVersionUID 的值,反序列化时当前类有些改变,比如增加或者删除了某些成员变量,那么系统就会重新计算当前类的hash值并把它赋值给serialVersionUID,这个时候当前类的 serialVersionUID 就和反序列化数据中的 serialVersionUID 不一致,就会造成反序列化失败的结果。所以,手动指定 serialVersionUID 可以在很大程度上避免反序列化的失败。比如当版本升级后,我们可能删除了某个成员变量也可能增加了一些新的成员变量,这个时候我们的反序列化过程依然能够成功,程序仍然能够最大限度地回复数据;相反,如果不指定serialVersionUID的话,程序会发生Crash。

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

对于使用序列化还有两点需要注意: 

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

2.Parcelable

Parcelable接口是Android SDK提供的一种专门用于Android应用中对象的序列化和反序列化的方式,相比于Seriablizable具有更好的性能。实现Parcelable接口的对象就可以实现序列化并可以通过 Intent 和 Binder 传递。

扫描二维码关注公众号,回复: 3503733 查看本文章
public class User implements Parcelable{
    private String name;
    private int age;

    //从序列化后的对象中创建原始对象
    protected User(Parcel in) {
        name = in.readString();
        age = in.readInt();
    }

    public User(String name, int age) {
        this.name = name;
        this.age = age;
    }

    /**
     * Getter and Setter 方法
     */

    public static final Creator<User> CREATOR = new Creator<User>() {
        //从序列化后的对象中创建原始对象
        @Override
        public User createFromParcel(Parcel in) {
            return new User(in);
        }

        //创建指定长度的原始对象数组
        @Override
        public User[] newArray(int size) {
            return new User[size];
        }
    };

    //返回当前对象的内容描述,几乎所有情况都返回0,仅在当前对象中存在文件描述符时返回1
    @Override
    public int describeContents() {
        return 0;
    }

    //将当前对象写入序列化结构中
    @Override
    public void writeToParcel(Parcel parcel, int i) {
        parcel.writeString(name);
        parcel.writeInt(age);
    }
}

实现Parcelable接口主要可以分为一下几步:

1)implements Parcelable。
2)重写writeToParcel方法,将你的对象序列化为一个Parcel对象,即:将类的数据写入外部提供的Parcel中,打包需要传递的数据到Parcel容器保存,以便从Parcel容器获取数据。
3)重写describeContents方法,内容接口描述,默认返回0即可。
4)实例化静态内部对象CREATOR实现接口Parcelable.Creator 。
注意 : 若将Parcel看成是一个流,则先通过writeToParcel把对象写到流里面,再通过createFromParcel从流里读取对象,因此类实现的写入顺序和读出顺序必须一致。

Serializable 和 Serializable 区别

Android的Parcelable的设计初衷是因为Serializable效率过慢,为了在程序内不同组件间以及不同Android程序间(AIDL)高效的传输数据而设计,这些数据仅在内存中存在,Parcelable是通过IBinder通信的消息的载体。

区别 Serializable Parcelable
所属API JAVA API Android SDK API
原理 序列化和反序列化过程需要大量的I/O操作 序列化和反序列化过程不需要大量的I/O操作
开销 开销大 开销小
效率 很高
使用场景 序列化到本地或者通过网络传输 内存序列化

猜你喜欢

转载自blog.csdn.net/u010289802/article/details/81232883