Android数据序列化
- Seriallizable
- Parcelable
- Serializable和Parcelable的使用
- SQLiteDatabase
- SharedPreferences
- JSON
- Protocol Buffers及Nano-Proto-Buffers
- FlatBuffers
数据序列化在Android应用开发中占据着举足轻重的位置,无论是进程间通信、本地数据存储还是网络数据传输等,都离不开序列化的支持。
广义上讲,序列化是将数据结构或者对象转换成可用于存储或者传输的数据格式的过程,在序列化期间,数据结构或者对象将其状态信息写入到临时或者持久性存储区中;反序列化是将序列化过程中生成的数据还原成数据结构或者对象的过程。
Seriallizable
Serializable是Java语言的特性,它是最简单的也是使用最广泛的序列化方案之一,只有实现了Serialiable接口的Java对象才可以实现序列化。这种类型的序列化是将Java对象转换成字节序列的过程,而反序列化则是将字节序列恢复成Java对象的过程。
Serializable接口是一种标识接口,也就是无需实现方法,Java便会对这个对象进行序列化操作。它的缺点是使用反射机制,在序列化的过程中会创建很多临时对象,容易触发垃圾回收,序列化的过程比较慢,对于性能要求很严格的场景不建议使用这种方案。
在安卓源码中,Serializable是一个空接口。
//安卓源码中的Serializable接口代码;
public interface Serializable {
}
序列化过程中会使用名为serialVersionUID的版本号和序列化的类相关联,serialVersionUID在反序列化过程中用于验证序列化对象的发送者和接收者是否为该对象加载了与序列化兼容的类对象,如果接收者加载的对象的serialVersionUID和发送者加载的对象的serialVersionUID取值不同,则序列化过程会出现InvalidClassException异常,这种情况在网络通信两个节点间的序列化特别需要注意。最佳实践是显示指定serialVersionUID的值(IDE为我们随机生成)。也就是说,如果我们没有显示指定一个serialVersionUID,程序会根据类名、接口名、属性和方法自动生成一个。
如果不手动指定serialVersionUID的值,反序列化时当前类有所改变,比如增加或者删除了某些成员变量,那么系统就会重新计算当前类的hash值并把它赋值给serialVersionUID,这个时候当前类的serialVersionUID就和序列化的数据中的serialVersionUID不一致,于是反序列化失败,程序就会出现crash。当我们手动指定了serialVersionUID以后,就可以在很大程度上避免反序列化过程的失败。比如当版本升级后,我们可能删除了某个成员变量,也可能增加了一些新的成员变量,这个时候我们的反向序列化过程仍然能够成功,程序仍然能够最大限度地恢复数据。相反,如果不指定serialVersionUID的话,程序就会挂掉。当然,我们还要考虑另一种情况,如果类结构发生了非常规性改变,比如修改了类名,修改了成员变量的类型,这个时候尽管serialVersionUID验证通过了,但是反序列化过程还是会失败,因为类结构有了毁灭性的改变,根本无法从老版本的数据中还原出一个新的类结构的对象。
以下两点需要特别提一下:首先静态成员变量属于类不属于对象,所以不会参与序列化过程;其次用transient关键字标记的成员变量不参与序列化过程。
public class CustomBean implements Serializable{
private static final long serialVersionUID = 31413413;
public int mTime;
public String mName;
}
Parcelable
Serializable是JDK提供的接口,这种序列化方式是基于磁盘或者网络的,而Parcelable是Android SDK提供的,是基于内存的,由于内存读写速度高于磁盘,因此在Android中跨进程对象的传递一般使用Parcelable。
在安卓源码中Parcelable的代码:
public interface Parcelable {
@IntDef(flag = true, prefix = { "PARCELABLE_" }, value = {
PARCELABLE_WRITE_RETURN_VALUE,
PARCELABLE_ELIDE_DUPLICATES,
})
@Retention(RetentionPolicy.SOURCE)
public @interface WriteFlags {}
public static final int PARCELABLE_WRITE_RETURN_VALUE = 0x0001;
public static final int PARCELABLE_ELIDE_DUPLICATES = 0x0002;
@IntDef(flag = true, prefix = { "CONTENTS_" }, value = {
CONTENTS_FILE_DESCRIPTOR,
})
@Retention(RetentionPolicy.SOURCE)
public @interface ContentsFlags {}
public static final int CONTENTS_FILE_DESCRIPTOR = 0x0001;
public @ContentsFlags int describeContents();
public void writeToParcel(Parcel dest, @WriteFlags int flags);
public interface Creator<T> {
public T createFromParcel(Parcel source);
public T[] newArray(int size);
}
public interface ClassLoaderCreator<T> extends Creator<T> {
public T createFromParcel(Parcel source, ClassLoader loader);
}
}
实现Parcelable不容易,需要写大量的模版代码,这使得对象代码变得难以阅读和维护。在Android Studio中可以安装一个Android Parcelable code generator的插件。在使用该插件的时候,只需要在需要序列化的实体类中点击鼠标右键,选择Generate,在弹出的generate弹窗中选择Parcelable选项即可。该插件会自动帮我们将实体类转换成实现了Parcelable接口的形式。
Parcel内部包装了可序列化的数据,可以在Binder中自由传输。序列化功能由writeToParcel方法来完成,最终是通过Parcel中的一系列write方法来完成的;发序列化功能由CREATOR来完成,其内部标明了如何创建序列化对象和数组,并通过Parcel的一系列read方法来完成反序列化过程;内容描述功能由describeContent方法来完成,几乎在所有情况下这个方法都应该返回0,仅当当前对象中存在文件描述符时,此方法返回1.
要实现Parcelable接口,需要实现以下几个方法:
- describeContent:接口内容的描述,一般默认返回0即可;
- writeToParcel:序列化的方法,将类的数据写入到Parcel容器中;
- 静态的Parcelable.Creator接口,这个接口包含两个方法:
- createFromParcel:反序列化的方法,将Parcel还原成Java对象;
- newArray:提供给外部类反序列化这个数组使用;
public class AreaBean implements Parcelable {
public int time;
public String name;
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(this.time);
dest.writeString(this.name);
}
public AreaBean() {
}
protected AreaBean(Parcel in) {
this.time = in.readInt();
this.name = in.readString();
}
public static final Parcelable.Creator<AreaBean> CREATOR = new Parcelable.Creator<AreaBean>() {
@Override
public AreaBean createFromParcel(Parcel source) {
return new AreaBean(source);
}
@Override
public AreaBean[] newArray(int size) {
return new AreaBean[size];
}
};
}
Serializable和Parcelable的使用
Serializable是Java中的序列化接口,其使用起来简单但是开销很大,序列化和反序列化过程需要大量I/O操作。Parcelable是Android中的序列化方式,因此更适合用在Android平台上。在讲对象序列化到存储设备中或者将对象序列化后通过网络传输时,建议使用Serializable。
SQLiteDatabase
SQLite是一款轻量级的关系型数据库,它的运算速度极快、占用的资源很少--通常只需要几百kb的内存即可,是和在移动设备上使用,Android和IOS都内置了SQLite数据库。
SQLite数据库主要用来存储复杂的关系型数据,Android系统原生支持SQLite数据库相关操作,但其API不太友好,需要开发者编写很多样板代码。而且在安全性方面,由于Android应用程序数据库的默认目录是/data/data/PACKAGE_NAME(包名)/database,而这个目录在手机Root之后是可以直接访问到的,因此敏感信息存储到数据库之前需要进行加密,使用时再进行解密。
SharedPreferences
SharedPreferences是Android平台提供的一个轻量级的存储API,一般用来保存应用的一些常用配置信息,其本质是一个键值对存储。SharedPreferences支持常用数据类型如boolean、float、int、long、以及String的存储和读取。由于不需要复杂的数据转换操作,SharedPreferences相比其他序列化更高效。
在Android系统中,SharedPreferences中的信息是以XML文件的形式保存在/data/data/PACKAGE_NAME(包名)/shared_prefs目录下的。正常情况下,其他APP或者设备使用者无法访问这个目录的,但一旦Android设备Root之后,这个目录就可以轻易访问到了。因此,如果保存敏感信息,在SharedPreferences中就必须进行加密存储。一般有两种方案:
- 在对数据进行存储之前进行加密,在读取后进行解密;
- 直接使用SharedPreferences的安全封装类secure-preferences(一个第三方库),无须开发者手动编写加密解密代码。
JSON
JSON是一种轻量级的数据交换格式,Android SDK原生支持JSON格式的解析和序列化。
Protocol Buffers及Nano-Proto-Buffers
Protoxol Buffers是Google设计的语言无关、平台无关的一种轻便高效的序列化结构数据存储格式,类似XML,但更小、更快、更简单,很适合做数据存储或者RPC数据交换的格式。
FlatBuffers
FlatBuffers是Google为游戏开发或其他对性能敏感的应用程序创建的开源的、跨平台的、高效的序列化函数库,它提供了对C++/Java等语言接口的支持。FlatBuffers是一个注重性能和资源使用的序列化类库。