Android进程通信 - 序列化Serialzable与Parcelable

序列化简介

定义

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

简单而言:
序列化是将对象转换成字节流
反序列化是将字节流转化成对象。

用途

主要用途:

  • 序列化将对象写成字节流持久化保存在内存、文件、数据库中
  • 将序列化对象通过网络传输到其他客户端

在Android开发中,Intent、Bundle和Binder数据传输时,其对象都需要序列化。例:

ublic Intent putExtra(String name, Parcelable value) {...}
public Intent putExtra(String name, Serializable value) {...}

在Android序列化的方式有两种

  • Serializable 接口
  • Parcelable 接口

Serializable 接口

Serializable 接口是java提供一个序列化空接口,序列化过程也很简单,只需实现Serializable接口即可:

//空接口
public interface Serializable {

}

public class User implements Serializable{

    private String userName;
    private String userId;

    public String getUserName() {
        return userName;
    }
    public void setUserName(String userName) {
        this.userName = userName;
    }
    public String getUserId() {
        return userId;
    }
    public void setUserId(String userId) {
        this.userId = userId;
    }
}



一个对象的序列化和反序列化实现:

  • 使用ObjectOutputStream进行对象序列化
  • 使用ObjectInputStream字节流反序列化成对象

为何是用ObjectOutputStream进行序列化,ObjectInputStream进行反序列化呢?其实可以这样理解(个人理解,方便记忆)

工厂的核心职责就是生产产品,把一个产品比作一个对象,当要生产某款产品时,需要从外采购物料货(Input);生产成完整产品时,此时需要货(Output)到客户手里。
小结:
对象:Object
对象序列化:出货(Output)
反序列化成对象:进货(Input)

例子:

  /**
     * 序列化
     */
    public synchronized static boolean saveUser(User user,String path){

        try {
            File file = new File(path);
            //判断文件是否存在
            if (file.exists()){
                //删除
                file.delete();
            }
            //创建文件
            file.createNewFile();
            FileOutputStream fileOutputStream = new FileOutputStream(path);
            ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
            objectOutputStream.writeObject(user);
            objectOutputStream.close();
          return true;
        } catch (IOException e) {
            e.printStackTrace();
        }
        return false;
    }


    /**
     * 反序列化
     */
    public synchronized static User getUser(String path){
        try {

            FileInputStream fileInputStream = new FileInputStream(path);
            ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);
            User user = (User) objectInputStream.readObject();
            objectInputStream.close();
            return user;
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }
        return null;
    }


 public void onClick(View view) throws IOException {
        String path = Environment.getExternalStorageDirectory() + "/" + "user.txt";
        switch (view.getId()){
            case R.id.but_serialization:
                //序列化
                User user = new User();
                user.setUserName("张三");
                user.setUserId("11");
                boolean saveUser = saveUser(user,path);
                Log.i(TAG,"序列化是否成功:"+saveUser);
                break;
            case R.id.but_deserialization:
                //反序列化
                User user1 = getUser(path);
                if (user1==null) return;
                Log.i(TAG,user1.getUserName()+"--"+user1.getUserId());
                break;

        }
    }

输出结果:
这里写图片描述

其实,使用Serializable序列化还有个很重要的参数:serialVersionUID,是一个随机数,会影响反序列化的过程。
它的作用就是防止类的序列化写入文件的版本号与反序列化类的版本号不一致而导致程序crash,最终反序列失败。
当一个类使用了Serializable序列化,如果没有手动设置serialVersionUID值,其实在IDE上也会提示警告:
这里写图片描述

上面的例子是没有设置serialVersionUID(默认情况下也没有serialVersionUID),我们可以在User类中增加或删除某个字段,再去反序列化看看是什么结果。

public class User implements Serializable{

    private String userName;
    private String userId;
    private String sex;

这里写图片描述
这里写图片描述
从日志上看,反序列并没有成功,报了InvalidClassException异常错误,并提供我们序列化和反序列化中的serialVersionUID的值是不一样的。
既然是serialVersionUID不一致而导致序列化失败,那么我们就为User类设置一个serialVersionUID值,重新序列化后,再删除或添加某个变量,最后看看反序列化的结果。
在代码上快速自动生成serialVersionUID的方法:把光标放在类上,选中类名,Alt+Enter弹出提示,然后直接导入完成。
这里写图片描述

public class User implements Serializable{

    private static final long serialVersionUID = 1536609946676788617L;
    private String userName;
    private String userId;
 }

最终会发现是可以反序列化成功的,不会出现因添加或删除某个字段的反序列化失败问题。

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);
    }
}

Parcel是一个包装类,内部包装了可序列化的数据,在序列化和反序列化中扮演着中间转换角色。

public class User2 implements Parcelable{
    private String userName;
    private String userId;
    private List<Book> mBooks;
    private static class Book{

   }

    /**
     * 1、当前对象的内容描述
     * @return 一般返回0
     */
    @Override
    public int describeContents() {
        return 0;
    }

    /**
     * 2、将对象写入序列化结构中(序列化)
     * @param dest
     * @param flags 0或1 ,一般为0,1表示不能立即释放资源
     */
    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeString(this.userName);
        dest.writeString(this.userId);
        dest.writeList(this.mBooks);
    }

    public User2() {
    }
    
    /**
     * 反序列化
     */
    public static final Creator<User2> CREATOR = new Creator<User2>() {

        /**
         * 反序列化类对象
         * @param source
         * @return
         */
        @Override
        public User2 createFromParcel(Parcel source) {
            return new User2(source);
        }

        /**
         * 反序列化成数组
         * @param size
         * @return
         */
        @Override
        public User2[] newArray(int size) {
            return new User2[size];
        }
    };

    /**
     * 使用反序列的Parcel进行取值
     * @param in
     */
    protected User2(Parcel in) {
        this.userName = in.readString();
        this.userId = in.readString();
        this.mBooks = new ArrayList<Book>();
        in.readList(this.mBooks, Book.class.getClassLoader());
    }
}

在AS中,可以用插件自动生成Parcelable实现代码。
这里写图片描述

总结

Serializable 和Parcelable都可以实现序列化进行数据传递。Serializable只需实现接口(保险点设置serialVersionUID值)即可,而Parcelable实现序列化使用则相对复杂些,当效率比Serializable高,Android底层做了相应的优化。

保存到SD卡、数据库或网络传输一般使用Serializable序列化,虽然效率低些,但使用很方便。
Intent、Bundle、Binder间的数据传递建议使用Parcelable,Android在这块做了内存序列化优化,效率高。

参考

安卓开发艺术探索
https://blog.csdn.net/u011240877/article/details/72455715
https://blog.csdn.net/lixiang_Y/article/details/54946199?locationNum=2&fps=1
https://blog.csdn.net/u011240877/article/details/72455715

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值