Android开发之漫漫长途 X——Android序列化

该文章是一个系列文章,是本人在Android开发的漫漫长途上的一点感想和记录,我会尽量按照先易后难的顺序进行编写该系列。该系列引用了《Android开发艺术探索》以及《深入理解Android 卷Ⅰ,Ⅱ,Ⅲ》中的相关知识,另外也借鉴了其他的优质博客,在此向各位大神表示感谢,膜拜!!!另外,本系列文章知识可能需要有一定Android开发基础和项目经验的同学才能更好理解,也就是说该系列文章面向的是Android中高级开发工程师。


前言

上一篇中我们比较详尽的分析了ServiceManager。那么本篇我们来讲一下Android序列化的相关知识。为什么跨度那么大,因为“任性”?其实不是的,同志们还记得上两篇出现的Parcel吗,Parcel是一个容器,他可以包含数据或者是对象引用,并且能够用于Binder的传输。同时支持序列化以及跨进程之后进行反序列化,同时其提供了很多方法帮助开发者完成这些功能。从上面的描述可以看出Parcel是进程间通信的数据载体。我们常常需要持久化一些对象,除了数据库等持久化方案之外,把对象转换成字节数组并通过流的方式存储在本地也是一个不错的方法,另外当我们需要通过Intent和Binder传输数据是就需要使用序列化后的数据。

Java中的Serializable

Serializable 是Java所提供的一个序列化接口,它是一个空接口,为对象提供标准的序列化和反序列化操作。使用Serializable来实现序列化相当简单,只需要在需要序列化的类实现Serializable接口并在其中声明一个类似下面的标识即可自动实现默认的序列化过程。

public class Person extends PersonParent implements Serializable {
    private static final long serialVersionUID = 1L;

    //静态域
    public static int static_field;
    //transient域
    public transient int transient_field;
    //一个普通的域
    public String desc;

    public Person(String desc) {
        this.desc = desc;
    }

    static class PersonSerializableProxy implements Serializable{
        private String desc;

        private PersonSerializableProxy(Person s) {
            this.desc = s.desc;
        }

        /**
         * 与writeReplace相同,ObjectInputStream会通过反射调用 readResolve()这个方法,
         * 决定是否替换反序列化出来的对象。
         * @return
         */
        private Object readResolve() {
            return new Person(desc);
        }

    }

    /**
     *
     * 在序列化一个对象时,ObjectOutputStream会通过反射首先调用writeReplace这个方法,
     * 在这里我们可以替换真正送去序列的对象,
     * 如果我们没有重写,那序列化的对象就是最开始的对象。
     * @return
     */
    private Object writeReplace() {
         //序列化Person的时候我们并没有直接写入Person对象,而是写入了PersonSerializableProxy对象
        return new PersonSerializableProxy(this);
    }

    /**
     * 这里主要是为了防止攻击,任何以Person声明的对象字节流都是流氓!!
     * 因为我在writeReplace中已经把序列化的实例指向了SerializableProxy
     * @param stream
     * @throws InvalidObjectException
     */
    private void readObject(ObjectInputStream stream) throws InvalidObjectException {
        throw new InvalidObjectException("proxy requied!");
    }

    public static void main(String[] args) throws IOException, ClassNotFoundException {
        Person person = new Person("desc");
        person.transient_field = 100;
        person.static_field = 10086;

        ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("cache.txt"));
        outputStream.writeObject(person);
        outputStream.flush();
        outputStream.close();

        person.static_field = 10087;


        ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("cache.txt"));
        Person deserialObj = (Person) objectInputStream.readObject();
        System.out.println(deserialObj);
}


}
class PersonParent{
    private String name;
    //PersonParent类要么继承自Serializable,要么需要提供一个无参构造器。
    public PersonParent() {
    }

    public PersonParent(String name) {
        this.name = name;
    }
}

不过在使用中也需要注意以下几个问题:

  1. serialVersionUID用来标识当前序列化对象的类版本,建议每一个实现Serialization的类都指定该域。当然如果我们没有指定,JVM会根据类的信息自动生成一个UID。
  2. 被transient描述的域和类的静态变量是不会被序列化的,序列化是针对类实例。
  3. 需要进行序列化的对象所有的域都必须实现Serializable接口,不然会直接报错NotSerializableException。当然,有两个例外:域为空 或者域被transient描述是不会报错的。
  4. 如果一个实现了Serializable类的对象继承自另外一个类,那么这个类要么需要继承自Serializable,要么需要提供一个无参构造器。
  5. 反序列化产生的对象并不是通过构造器创建的,那么很多依赖于构造器保证的约束条件在对象反序列化时都无法保证。比如一个设计成单例的类如果能够被序列化就可以分分钟克隆出多个实例...

Android中的Parcelable

相对于Serializable而言,Parcelable的使用要复杂一些

public class Book implements Parcelable {
    private String name;

    public Book(String name) {
        this.name = name;
    }

    protected Book(Parcel in) {
        name = in.readString();
    }
    
    //反序列化功能由CREATOR完成,在CREATOR的内部标明的如何创建序列对象和数组
    public static final Creator<Book> CREATOR = new Creator<Book>() {
        //从Parcel中反序列化对象
        @Override
        public Book createFromParcel(Parcel in) {
            //其内部调用Parcel的一系列readXXX方法实现反序列化过程
            return new Book(in);
        }
        //创建序列化数组
        @Override
        public Book[] newArray(int size) {
            return new Book[size];
        }
    };

    @Override
    public int describeContents() {
        return 0;
    }
    //序列化过程:
    //重写writeToParcel方法,我们要在这里逐一对需要序列化的属性用Parcel的一系列writeXXX方法写入
    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeString(name);
    }
}

从上述代码注释可以看出,写一个实现Parcelable接口的类还是比较麻烦的,和Serailable相比,我们需要在writeToParcel中按序写入各个域到流中,同样,在createFromParcel中我们需要自己返回一个Book对象。

Parcelable在使用上也与Serializable稍有不同

public class TestActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_test2);

        //获取一个Parcel容器
        Parcel parcel = Parcel.obtain();
        //需要序列化的对象
        Book book = new Book("c++");

        //把对象写入Parcel
        parcel.writeParcelable(book,0);
        //Parcel读写共用一个位置计数,这里一定要重置一下当前的位置
        parcel.setDataPosition(0);
        //读取Parcel
        Book book1 = parcel.readParcelable(Book.class.getClassLoader());
        Log.d("TestActivity",book1.toString());
    }
}

Parcelable的写

我们来看一下writeParcelable方法
[Parcel.java]

public final void writeParcelable(Parcelable p, int parcelableFlags) {
    //判断p是否为空
    if (p == null) {
        writeString(null);
        return;
    }
    //① 先写入p的类名
    writeParcelableCreator(p);
    //② 调用我们重写的writeToParcel方法,按顺序写入域
    p.writeToParcel(this, parcelableFlags);
}

public final void writeParcelableCreator(Parcelable p) {
    //① 先写入p的类名
    String name = p.getClass().getName();
    writeString(name);
}

Parcelable的读

我们来看readParcelable方法
[Parcel.java]

public final <T extends Parcelable> T readParcelable(ClassLoader loader) {
    //① 调用readParcelableCreator
    //这时获得就是我们自定义的CREATOR
    Parcelable.Creator<?> creator = readParcelableCreator(loader);

    if (creator == null) {
        return null;
    }
    // 判断当前creator是不是Parcelable.ClassLoaderCreator<?>的实例
    if (creator instanceof Parcelable.ClassLoaderCreator<?>) {
      //如果是的话,,我们调用reateFromParcel(this, loader);
      Parcelable.ClassLoaderCreator<?> classLoaderCreator =
          (Parcelable.ClassLoaderCreator<?>) creator;
      return (T) classLoaderCreator.createFromParcel(this, loader);
    }
    //调用我们自定义的CREATOR中重写的createFromParcel方法
    return (T) creator.createFromParcel(this);
}

public final Parcelable.Creator<?> readParcelableCreator(ClassLoader loader) {
    //首先把类名读取出来
    String name = readString();

    Parcelable.Creator<?> creator;
    //mCreators做了一下缓存,如果之前某个classloader把一个parcelable的Creator获取过
    //那么就不需要通过反射去查找了
    
    synchronized (mCreators) {
        HashMap<String,Parcelable.Creator<?>> map = mCreators.get(loader);
        if (map == null) {
            map = new HashMap<>();
            mCreators.put(loader, map);
        }
        creator = map.get(name);
        if (creator == null) {
            try {
                ClassLoader parcelableClassLoader =
                        (loader == null ? getClass().getClassLoader() : loader);
                //加载我们自己实现Parcelable接口的类
                Class<?> parcelableClass = Class.forName(name, false,
                        parcelableClassLoader);
                
                Field f = parcelableClass.getField("CREATOR");
                Class<?> creatorType = f.getType();
                creator = (Parcelable.Creator<?>) f.get(null);
            }
            catch (Exception e) {
                    //catch exception
            }
            if (creator == null) {
                throw new BadParcelableException("Parcelable protocol requires a "
                        + "non-null Parcelable.Creator object called "
                        + "CREATOR on class " + name);
            }

            map.put(name, creator);
        }
    }

    return creator;
}

我们的测试例子读取Parcel

Book book1 = parcel.readParcelable(Book.class.getClassLoader());

可以看到我们在使用
readParcelable的时候,传入的参数是Book类的类加载器,根据我们上面的代码,我们知道我们先会通过反射获取定义在Book类中的CREATOR属性,我们回想一下在Book类中是怎么定义CREATOR的

    public static final Creator<Book> CREATOR = new Creator<Book>() {
        //从Parcel中反序列化对象
        @Override
        public Book createFromParcel(Parcel in) {
            //其内部调用Parcel的一系列readXXX方法实现反序列化过程
            return new Book(in);
        }
        //创建序列化数组
        @Override
        public Book[] newArray(int size) {
            return new Book[size];
        }
    };

我们得到CREATOR属性后,调用它的createFromParcel方法,由多态可知调用的实际我们定义在CREATOR内的createFromParcel方法,在该方法内我们创建了Book对象(内部实现是通过Parcel的一系列readXXX方法)并返回。至此我们就得到了反序列化的对象


本篇总结

我们本篇详细分析了Android序列化相关知识,你可以使用Java中的Serializable也可以使用Parcelable。


下篇预告

下一篇文章是对前面所讲文章做一个小结。读者敬请期待哦。

此致,敬礼

posted @ 2018-01-10 09:46 马飞标 阅读(...) 评论(...) 编辑 收藏