数据的序列化在Android开发中占据着重要的地位,无论是在进程间通信、本地数据存储又或者是网络数据传输都离不开序列化的支持。而针对不同场景选择合适的序列化方案对于应用的性能有着极大的影响。
广义上讲,序列化是将数据结构或者对象转换成可用于存储或者传输的数据格式的过程,在序列化期间,数据结构或者对象将其状态信息写入到临时或者持久性存储区中;反序列化是将序列化过程中生成的数据还原成数据结构或者对象的过程。
Serializable接口是Java语言的特性,是最简单也是使用最广泛的序列化方案之一,只有实现了Serializable接口的Java对象才可以实现序列化。这边需要注意的一点是Serializable接口是一个标识接口,无需实现方法,Java便会对这个对象进行序列化操作。缺点是使用反射机制,在序列化的过程中会创建很多临时对象,容易触发垃圾机制,序列化的过程比较慢,对于性能要求很严格的场景不建议使用这种方案。
在这里实现了Serializable接口的对象才可以序列化,将Java对象转换成字节序列,而对应的反序列化则是将字节序列恢复成Java对象的过程。
在需要序列化的类中会用到serialVersionUID去标识这个序列化对象,即仅当序列化后的数据中的SerialVersionUID与当前类的serialVersionUID相同时才能被正常的反序列化。
import java.io.*;
public class User implements Serializable{
private static final long serialVersionUID= 123456;
public int userId;
public String userName;
public boolean isMale;
public User(int userId,String userName,boolean isMale){
this.userId=userId;
this.userName=userName;
this.isMale = isMale;
}
public boolean toSerial(User user) throws IOException{
ObjectOutputStream out=null;
boolean status=false;
try{
out = new ObjectOutputStream(new FileOutputStream("cache.txt"));
out.writeObject(user);
status=true;
}catch(FileNotFoundException e){
System.out.println("NO FILE");
}finally{
if(out!=null)
out.close();
}
return status;
}
public User toObject(String filename) throws IOException{
ObjectInputStream in=null;
boolean status=false;
User user=null;
try{
in = new ObjectInputStream(new FileInputStream(filename));
user=(User) in.readObject();
}catch(ClassNotFoundException e){
System.out.println("No file");
}finally{
if(in!=null)
in.close();
}
return user;
}
public static void main(String[] args) throws IOException{
User user = new User(0,"jake",true);
System.out.println(user.toSerial(user));
System.out.println(user.toObject("cache.txt").getClass());
}
}
需要注意的:静态成员变量是属于类而不属于对象的,所以显然它不会参与到对象的序列化过程中。其次用transient关键字标记的成员变量不参与到序列化过程中。最后,这种序列化方式是基于磁盘或者网络的。
Parcelable是Android SDK提供的,它是基于内存的,由于内存读写速度高于硬盘,因此Android中的跨进程对象的传递一般使用Parcelable。
import android.os.Parcel;
import android.os.Parcelable;
public class User implements Parcelable {
public int userId;
public String userName;
public boolean isMale;
public User(int userId,String userName,boolean isMale) {
this.userId=userId;
this.userName=userName;
this.isMale=isMale;
}
public static final Creator CREATOR = new Creator() {
@Override
public User createFromParcel(Parcel in) {
return new User(in);
}
@Override
public User[] newArray(int size) {
return new User[size];
}
};
public User(Parcel in) {
userId=in.readInt();
userName = in.readString();
isMale=in.readInt()==1;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel out, int flags) {
out.writeInt(userId);
out.writeString(userName);
out.writeInt(isMale?1:0);
}
}
从上面可以看出,实现一个Parcelable接口,需要实现以下几个方法:
静态的parcelable.Creator接口,这个接口包含两个方法
两种对象序列化方法的对比
Serializable是Java中的序列化接口,其使用起来简单但开销较大(因为Serializable在序列化过程中使用了反射机制,故而会产生大量的临时变量,从而导致频繁的GC),并且在读写数据过程中,它是通过IO流的形式将数据写入到硬盘或者传输到网络上。
而Parcelable则是以IBinder作为信息载体,在内存上开销比较小,因此在内存之间进行数据传递时,推荐使用Parcelable,而Parcelable对数据进行持久化或者网络传输时操作复杂,一般这个时候推荐使用Serializable。
另外Serializable在使用时比较简单,而Parcelable在使用时需要手动去实现接口中的方法,为了规避使用Parcelable接口时的麻烦,我们下面介绍一个插件,从而自动生成对应的代码。
安装完成后,重启Android Studio使其生效,接着编写Java实体类BookItem
public class BookItem {
public String mName;
public long mLastTime;
public String mTitle;
public String mPath;
}
在Generate界面中点击Parceable,该插件自动帮我们将BookItem类转换成实现Parceable接口的形式,免去开发者手动编写的麻烦,生成代码如下:
public class BookItem implements Parcelable {
public String mName;
public long mLastTime;
public String mTitle;
public String mPath;
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(this.mName);
dest.writeLong(this.mLastTime);
dest.writeString(this.mTitle);
dest.writeString(this.mPath);
}
public BookItem() {
}
protected BookItem(Parcel in) {
this.mName = in.readString();
this.mLastTime = in.readLong();
this.mTitle = in.readString();
this.mPath = in.readString();
}
public static final Parcelable.Creator CREATOR = new Parcelable.Creator() {
@Override
public BookItem createFromParcel(Parcel source) {
return new BookItem(source);
}
@Override
public BookItem[] newArray(int size) {
return new BookItem[size];
}
};
}
下面讲到的是广义上的序列化方案,不同于前面两种狭义或者说是对象序列化方案,接下来的几种方案针对于数据的传输和存储过程中的序列化方案。
1.SQLite
SQLite是一款轻量级的关系型数据库,它的运行速度极快、占用资源很少—通常只需要几百kb的内存即可。主要用于存储复杂的关系型数据,Android支持原生支持SQLite数据库相关操作(SQLiteOpenHelper),不过由于原生API接口并不友好,所以产生了不少封装了SQLite的ORM框架。
2.SharedPreferences
SharedPreferences是Android平台上提供的一个轻量级存储API,一般用于存储常用的配置信息,其本质是一个键值对存储,支持常用的数据类型如boolean、float、int、long以及String的存储和读取。
使用SharedPreferences读取和存储操作如下:
读取:
1) 获取Sharedpreferences对象
SharedPreferences mPreferences = context.getCSharedPreferences(PREFERENCES_NAME,Context.MODE_PRIVATE);
2.通过SharedPReferences对象读取存储在SharedPreferences中的数据
mPreferences.getBoolean(key,defValue);
存储:
1)获取SharedPreferences.Editor对象
SharedPreferences.Editor editor = mPreferences.edit();
2)通过SharedPreferences.Editor对象写入数据到SharedPreferences中。
mEditor.putBoolean(key,b);
3)调用commit函数将写入的数据提交,从而完成数据存储操作。
mEditor.commit();
3.JSON
JSON全称是JavaSript Object Notation,是一种轻量级的数据交互格式,由于其相对于XML,体积更小,在网络上传输时更加介绍浏览,被广泛用于移动端。大部分APP与服务端的通信都是使用JSON格式进行交互。
4.Protocol Buffers 及Nano-Proto-Buffers
Protocol Buffers是Google设计的语言无关、平台无关的一种轻便高效的序列化结构数据存数格式,类似XML,但更小、更快、更简单、很适合做数据存储或者RPC数据交换的格式。它可用于通讯协议,数据存储等领域的与语言无关、平台无关,可扩展的序列化结构数据格式。
在移动端应该使用Nano-Proto-Buffers版本。因为普通的Protocol Buffers会生成非常冗长的代码,可能会增加APP内存占用,导致APK体积增长、性能下降,不注意的话,会很快遇到64K方法数限制问题。
5.FlatBuffers
Google FlatBuffers是Google为游戏开发或者其他对性能敏感的应用程序创建的开源的,跨平台的,高效的序列化函数库,它提供了对C++/Java等语言接口的支持。FlatBuffers是一个注重性能和资源使用的序列化类库。相比较Protocol Buffers而言,它更适合移动设备。
FlatBuffers的特性主要有:
FlatBuffers利用自身特殊的编码格式,子啊一定程度上减少内存的使用,优化了数据读取性能。同时,对于数据结构的前向后向兼容提供了良好的可扩展性。