Android 序列化 Serializable 和 Parcelable

本文介绍了序列化的基本概念及其应用场景,对比了 Java 中 Serializable 接口与 Android 特有的 Parcelable 接口的区别,深入探讨了这两种序列化方式的具体实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

什么是序列化


序列化 (Serialization)将对象的状态信息转换为可以存储或传输的形式的过程。在序列化期间,对象将其当前状态写入到临时或持久性存储区。以后,可以通过从存储区中读取或反序列化对象的状态,重新创建该对象。

二进制序列化保持类型保真度,这对于在应用程序的不同调用之间保留对象的状态很有用。例如,通过将对象序列化到剪贴板,可在不同的应用程序之间共享对象。您可以将对象序列化到流、磁盘、内存和网络等等。远程处理使用序列化“通过值”在计算机或应用程序域之间传递对象。


简单地说,“序列化”就是将运行时的对象状态转换成二进制,然后保存到流、内存或者通过网络传输给其他端。


Serializable 和 Parcelable 接口可以完成对象的序列化过程。


使用场景:

  • 通过 Intent 和 Binder 传输数据(在内存中序列化)
  • 需要把对象持久化到存储设备
  • 通过网络传输给其他客户端



Serializable 接口

Serializable 是 Java 所提供的一个序列化接口,它是一个空接口,为对象提供标准的序列化和反序列化操作。

public interface Serializable {
}
想让一个对象实现序列化,只需要这个类实现 Serializable 接口并声明一个 serialVersionUID 即可,实际上,甚至这个 serialVersionUID 也不是必需的,我们不声明这个 serialVersionUID 同样也可以实现序列化,但是这将会对反序列化过程产生影响。

public class User implements Serializable {
    private static final long serialVersionUID = 519123124215593L;

    public int userId;
    public String userName;
    public boolean isMale;
}

通过 Serializable 方式来实现对象的序列化,实现起来非常简单,几乎所有工作都被系统自动完成了。如何进行对象的序列化和反序列化也非常简单,只需要采用 ObjectOutputStream 和 ObjectInputStream 即可轻松实现。

// 序列化过程
User user = new User(0, "jake", true);
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("cache.txt"));
out.writeObject(user);
out.close();

// 反序列化过程
ObjectInputStream in = new ObjectInputStream(new FileInputStream("cache.txt"));
User newUser = (User) in.readObject();
in.close();
上述代码演示了采用 Serializable 方式序列化对象的典型过程,很简单,只需要把实现了 Serializable 接口的 User 对象写到文件中就可以快速恢复了,恢复后的对象 newUser 和 user 的内容完全一样,但是两者并不是同一个对象。


serialVersionUID 


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


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


序列化过程中不会保存 static 和 transient 修饰的属性,前者很好理解,因为静态属性属于类管理,不属于对象状态,不会参与序列化过程;而后者则是 Java 的关键字,专门用来标识不序列化的属性。



Parcelable 接口

Parcelable 是 Android 特有的序列化接口

public interface Parcelable {
    //writeToParcel() 方法中的参数,用于标识当前对象作为返回值返回
    //有些实现类可能会在这时释放其中的资源
    public static final int PARCELABLE_WRITE_RETURN_VALUE = 0x0001;

    //writeToParcel() 方法中的第二个参数,它标识父对象会管理内部状态中重复的数据
    public static final int PARCELABLE_ELIDE_DUPLICATES = 0x0002;

    //用于 describeContents() 方法的位掩码,每一位都代表着一种对象类型
    public static final int CONTENTS_FILE_DESCRIPTOR = 0x0001;

    //描述当前 Parcelable 实例的对象类型
    //比如说,如果对象中有文件描述符,这个方法就会返回上面的 CONTENTS_FILE_DESCRIPTOR
    //其他情况会返回一个位掩码
    public int describeContents();

    //将对象转换成一个 Parcel 对象
    //参数中 dest 表示要写入的 Parcel 对象
    //flags 表示这个对象将如何写入
    public void writeToParcel(Parcel dest, int flags);

    //实现类必须有一个 Creator 属性,用于反序列化,将 Parcel 对象转换为 Parcelable 
    public interface Creator<T> {

        public T createFromParcel(Parcel source);

        public T[] newArray(int size);
    }

    //对象创建时提供的一个创建器
    public interface ClassLoaderCreator<T> extends Creator<T> {
        //使用类加载器和之前序列化成的 Parcel 对象反序列化一个对象
        public T createFromParcel(Parcel source, ClassLoader loader);
    }
}

实现 Parcelable 接口的类必须有一个 CREATOR 类型的静态变量,下面是一个实例:

public class ParcelableGroupBean implements Parcelable {

    private String mName;
    private List<String> mMemberNameList;
    private User mUser;

    /**
     * 需要我们手动创建的构造函数
     * @param name
     * @param memberNameList
     * @param user
     */
    public ParcelableGroupBean(String name, List<String> memberNameList, User user) {
        mName = name;
        mMemberNameList = memberNameList;
        mUser = user;
    }

    /**
     * 1.内容描述
     * @return
     */
    @Override
    public int describeContents() {
        //几乎都返回 0,除非当前对象中存在文件描述符时为 1
        return 0;
    }

    /**
     * 2.序列化
     * @param dest
     * @param flags 0 或者 1
     */
    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeString(mName);
        dest.writeStringList(mMemberNameList);
        dest.writeParcelable(mUser, flags);
    }

    /**
     * 3.反序列化
     */
    public static final Creator<ParcelableGroupBean> CREATOR = new Creator<ParcelableGroupBean>() {
        /**
         * 反序列创建对象
         * @param in
         * @return
         */
        @Override
        public ParcelableGroupBean createFromParcel(Parcel in) {
            return new ParcelableGroupBean(in);
        }

        /**
         * 反序列创建对象数组
         * @param size
         * @return
         */
        @Override
        public ParcelableGroupBean[] newArray(int size) {
            return new ParcelableGroupBean[size];
        }
    };

    /**
     * 4.自动创建的的构造器,使用反序列化得到的 Parcel 构造对象
     * @param in
     */
    protected ParcelableGroupBean(Parcel in) {
        mName = in.readString();
        mMemberNameList = in.createStringArrayList();
        //反序列化时,如果熟悉也是 Parcelable 的类,需要使用它的类加载器作为参数,否则报错无法找到类
        mUser = in.readParcelable(User.class.getClassLoader());
    }

}

实现了 Parcelable 接口的类在序列化和反序列化时会被转换为 Parcel 类型的数据 。

Parcel 是一个载体,它可以包含数据或者对象引用,然后通过 IBinder 在进程间传递。


从上述代码可以看出,在序列化的过程中需要实现的功能有序列化、反序列化和内容描述。

序列化功能由 writeToParcel 方法来完成,最终是通过 Parcel 中的一系列 write 方法来完成的;


反序列化功能由 CREATOR 来完成,其内部标明了如何创建序列化对象和数组,并通过 Parcel 的一系列 read 方法来完成反序列化过程;


内容描述功能由 describeContents 方法来完成,几乎在所有情况下这个方法都应该返回0,仅当当前对象中存在文件描述符时,此方法返回1。


需要注意的是,在 ParcelableGroupBean(Parcel in) 方法中,由于 User 是另一个可序列化对象,所以它的反序列化过程需要传递当前线程的上下文类加载器,否则会报无法找到类的错误。



Serializable 和 Parcelable 的区别:


Serializable 的使用比较简单,但是开销很大,序列化和反序列化过程需要大量 I/O 操作;而 Parcelable 则相对复杂一些,会有4个方法需要实现。


一般在保存数据到 SD 卡或者网络传输时建议使用 Serializable 即可,虽然效率差一些,好在使用方便。


而在运行时数据传递时建议使用 Parcelable,比如 Intent,Bundle 等,Android 底层做了优化处理,效率很高。






评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值