android开发使用Serializable需要注意serialVersionUID的细节问题
android开发使用Serializable需要注意serialVersionUID的细节问题
平时Android开发过程,我们经常需要接触对象系列化和反系列化问题,实现方法有大家知道的两种:即一种实现Serializable接口,另一种是实现Parcelable接口。而对于这两种实现的优缺点也行大家都耳熟能详了。但这两种的实现原理和细节问题大家又知道多少呢?下面简单介绍一下:
1.实现Serializable接口实现系列化和反系列化
1-1. Serializable实现原理
介绍:当我们一个类实现Serializable接口,就可以具有系列化和反系列化功能,是不是有点奇怪?好像我没有做什么呀?到底是怎么实现的呢?其实实现Serializable持久化的具体实现是我们自己或者系统内部使用ObjectInputStream 或者ObjectInputStream操作对象保存到文件的时候才进行的系列化和反系列化,所以真正的实现就是ObjectInputStream 或者ObjectInputStream这两个类。
原理:比如ObjectInputStream 反系列化的实现大致是:new ObjectInputStream -> readObject -> readObject0 -> readOrdinaryObject(readNonProxyDesc实例化ObjectStreamClass对象该构造函数里会尝试加载重写的readObject等方法 -> initNonProxy会判断serialVersionUID等) -> desc.newInstance -> readSerialData -> defaultReadFields(如果重写了readObject方法就调用invokeReadObject)
1-2. 需要注意serialVersionUID
作用:最好给定一个明确的serialVersionUID,不然系统会根据该类的所有可系列化的字段生成一个hash值作为serialVersionUID,这样下次版本迭代新增或者删除字段就会导致serialVersionUID发生变化,版本迭代的时候最好确保serialVersionUID不变,不要改动,这样使用ObjectInputStream 或者ObjectInputStream进行系列化和反系列化时才不会发生异常导致崩溃(上面分析了initNonProxy方法里面会判断serialVersionUID)。
注意:当然如果不小心改动了serialVersionUID,没有使用ObjectInputStream 或者ObjectInputStream进行落盘系列化和反系列化操作,因为app运行过程中即使进行了系列化和反系列化也是一次性的所以也是不影响的,不会因为serialVersionUID不同导致崩溃这个问题。
类似local class incompatible: stream classdesc serialVersionUID = 65959533030892356, local class serialVersionUID = 3195137497115073498问题就是serialVersionUID不同引起的,异常就是在ObjectStreamClass类的initNonProxy方法里抛出来的。
2.实现Parcelable接口实现系列化和反系列化
1-1. Parcelable实现原理
介绍:通过Parcelable实现系列化和反系列化是不会落盘到本地文件的,只会保存到运行时的一块native内存中。与Serializable会通过ObjectInputStream 或者ObjectInputStream落盘到本地文件不同,这是本质区别,相信都知道这点区别。
原理:实现Parcelable接口时系统会要求我们实现writeToParcel方法,这个方法就是用来将对象系列化到native内存的。一个字段一个字段得write,是要和反系列化列的顺序必须保存一致的(Serializable实现不要求顺序,因为它是通过反射操作字段的同时保存了每个字段offset位置),可见Parcelable内部实现起来简单很多。同时还要求创建一个CREATOR静态内部类实例,这个实例系统是给系统反系列化成对象时使用的,要求我们按照writeToParcel实现的顺序read出每个字段的内容。整个实现就是这样,具体native实现frameworks/native/libs/binder/Parcel.cpp,保存在mDatas变量。