Android序列化方式对比

Android 序列化

一、概念

序列化是一种将对象信息转换成可以进行传输及交互的过程。简单来说,序列化就是把运行时对象信息转换成二进制,然后保存到流、内存、SD卡或者通过网络传输到其它端。在安卓中,组件之间进行数据传递,便会用到序列化,比如Intent,Bundle传递对象时,还有Binder传递数据时。

二、Android中序列化的方式

Serializable接口

Serializable是Java提供的一个空接口

public interface Serializable {
}

用Serializable实现序列化非常简单,只需要实现Serializable接口即可。

Parcelable接口

Parcelable是安卓提供的一种序列化接口,相比Serializable来说要复杂很多

三、Android序列化具体实现方式

  • Serializable实现序列化

    public class User implements Serializable {
        private static final long serialVersionUID = 1L;
    
        private String name;
    
        private int age;
    
        private boolean isMale;
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public int getAge() {
            return age;
        }
    
        public void setAge(int age) {
            this.age = age;
        }
    
        public boolean isMale() {
            return isMale;
        }
    
        public void setMale(boolean male) {
            isMale = male;
        }
    }
    

    这样就可以对User进行序列化和反序列化,很简单。

    /**
     * 序列化对象
     *
     * @param obj 序列化对象
     * @param path 保存路径
     * @return
     */
    synchronized public static boolean saveObject(Object obj, String path) {
        if (obj == null) {
            return false;
        }
        ObjectOutputStream oos = null;
        try {
            oos = new ObjectOutputStream(new FileOutputStream(path));
            oos.writeObject(obj);
            oos.close();
            return true;
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (oos != null) {
                try {
                    oos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return false;
    }
    
    /**
     * 反序列化对象
     *
     * @param path
     * @param 
     * @return
     */
    synchronized public static <T> T readObject(String path) {
        ObjectInputStream ojs = null;
        try {
            ojs = new ObjectInputStream(new FileInputStream(path));
            return (T) ojs.readObject();
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        } finally {
            close(ojs);
        }
        return null;
    }
    

    通过上述方法便可以实现对User对象的序列化存储和反序列化读取。

    但是serialVersionUID是什么呢?

    通过Serializable实现序列化User类的时候,在反序列化的时候会得到一个新的User对象,其内容跟序列化的User完全一样,但它们是两个不同的User对象,

    而serialVersionUID充当什么角色呢?在反序列化的时候,系统首先会把序列化对象的serialVersionUID与当前反序列化的User对象的serialVersionUID进行比较,如果两者一致,则反序列化成功,否则会出现crash,报如下异常:

    java.io.InvalidClassException: Main; local class incompatible: stream
        classdesc serialVersionUID = 8711368828010083044,local class serial-
        VersionUID = 8711368828010083043

    这是因为系统在序列化的时候会把当前对象的serialVersionUID写入在一个序列化文件中,当反序列化的时候系统会去检测文件中的serialVersionUID,看它是否和当前对象的serialVersionUID一致,如果一致就说明序列化的类的版本和当前类的版本是相同的,这时候就可以成功反序列化,如果不相同,说明当前类和序列化的类相比发生了某些改变,比如新增或删除了某些属性等,这个时候便会出现上述异常。

    当然,我们不在类中明确指定serialVersionUID也是可以的,因为不手动指定serialVersionUID的话,系统会默认通过计算生成一个serialVersionUID值。

    一般来说,建议手动指定一个固定的serialVersionUID值,这样的话在序列化和反序列化的时候就不会出现前后serialVersionUID不一致的问题,也就不会出现crash,比如说,如果不指定serialVersionUID值,在序列化一个类对象之后,我们对该类进行了一系列的变换,如新增或删除某些属性,这时候,该类的hash值会发生变化,系统默认生成的serialVersionUID也会随之改变,因此在反序列化的时候前后serialVersionUID就不一致,从而导致反序列化失败。而当我们手动指定了该类的serialVersionUID值,就算在反序列化的时候该类已经新增或删除了一些属性,该类的serialVersionUID值也还是原来那个,这样程序在反序列化的时候便可以最大限度的恢复数据。

    这里还有一种情况需要考虑,那便是如果该类的结构发生了非常规性的改变,比如说修改了类名、改变了成员属性的类型,这个时候尽管serialVersionUID前后一致,但是反序列化依然失败,因为在反序列化的时候该类发生了毁灭性的改变,无法根据旧的数据还原一个新的类结构的对象。

  • Parcelable实现序列化

    Parcelable是安卓特有的一种序列化方式,这种方法实现序列化要比Serializable复杂很多。

    Parcelable也是一个接口,只要实现这个接口,一个类的对象便可以实现序列化并可以通过Intent和Binder进行数据传递,如:

    public class User implements Parcelable {
        private String name;
        private int age;
        private Company company;
    
        /**
         * 自动创建的的构造器,使用反序列化得到的 Parcel 构造对象
         */
        protected User(Parcel in) {
            name = in.readString();
            age = in.readInt();
            // 这里company是另一个可序列化对象,所以它的反序列化过程需要传递当前线程的上下文 类加载器
            company = in.readParcelable(Thread.currentThread().getContextClassLoader());
        }
    
        /**
         * 手动创建的构造器
         */
        public User(String name, int age, Company company) {
            this.name = name;
            this.age = age;
            this.company = company;
        }
    
         /**
          * 反序列化
          */
        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 dest, int flags) {
            dest.writeString(name);
            dest.writeInt(age);
            dest.writeParcelable(company,0);
        }
    }
    

    首先说明一下Parcel,Parcel内部包装了可序列化的数据,可以在Binder中自由传输。

    以上代码便可以实现序列化以及反序列化功能,可以看出,在序列化过程中需要实现的功能有序列化、反序列化、内容描述,其中writeToParcel方法完成序列化,反序列化功能则由CREATOR完成,其内部标明了如何创建序列化对象和数组,然后通过Parcel一些列read方法完成反序列化过程。而内容描述功能由describeContents方法实现,几乎在所有情况下该方法都应该返回0,仅当当前对象中有文件描述符时,该方法返回1。

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

四、总结

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

Serializable是Java中的接口,在使用上非常方便简单,但是开销很大,在序列化和反序列化过程中需要进行大量的I/O操作,因此在保存数据到存储设备上或进行网络传输时建议使用Serializable,虽然效率上差一点,但是方便使用。

Parcelable是安卓提供的序列化方式,因此更适用于Android平台,虽然使用上稍微麻烦,但是效率高,Android底层做了优化处理,因此Parcelable主要用在内存序列化上,比如运行时数据传递,Intent,Bundle传递数据等

你可能感兴趣的:(安卓序列化详解,Android序列化知识,android,java,Android开发)