在Android中有两种接口可以实现序列化操作:Serializable和Parcelable。前者是Java API中带的,后者则是Android API中的。我们一次来学习一下。
Serializable接口
利用这个接口是实现序列化很简单,只需在相应的类中实现这个接口即可,而且不用实现任何方法,如下
public class Item implements Serializable{
public int id;
public String msg;
Item(int id,String msg){
this.id = id;
this.msg = msg;
}
}
用法很简单,但有一点需要注意,我们在看这个接口介绍时需要有一个serialVersionUID,或者我们看源码时,有些实现了该接口的类都有这个ID,如ArrayList:
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
private static final long serialVersionUID = 8683452581122892189L;
...
}
但用过的朋友可能会发现,不写这个UID也能正常实现序列化和反序列话,那么这个UID有什么用呢?这里有一点需要注意的是,如果我们不写这个UID,是不是真的就没有呢,事实上,如果不写编译器会自动根据类的内容计算一个hash值,作为这个类的UID。那么这个值又有什么用呢?既然是ID肯定是作为一个标识符,只要标识符一样,我们在反序列的时候即使类有改变,也能尽可能的还原数据,示例如下:
我们先手动指定一个UID
public class Item implements Serializable{
public static final long serialVersionUID = 1L;
public int id;
public String msg;
Item(int id,String msg){
this.id = id;
this.msg = msg;
}
}
在执行序列化操作:
try {
File file = new File(getFilesDir(),"Serializable");
if (file.exists())
file.delete();
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(file));
out.writeObject(new Item(10,"a"));
out.close();
} catch (IOException e) {
e.printStackTrace();
}
此时我们如果不做更改,直接反序列化,肯定是可以的。但是我们现在对Item类添加一个成员变量,但不改变其UID:
public class Item implements Serializable{
public static final long serialVersionUID = 1L;
public int id;
public String msg;
public String msg2;
Item(int id,String msg,String msg2){
this.id = id;
this.msg = msg;
this.msg2 = msg2;
}
}
此时在再执行反序列化操作,如下
try {
ObjectInputStream in = new ObjectInputStream(new FileInputStream(new File(getFilesDir(),"Serializable")));
Item item = (Item) in.readObject();
in.close();
Toast.makeText(this,item.id+item.msg,Toast.LENGTH_SHORT).show();
} catch (Exception e) {
Toast.makeText(this,e.getMessage(),Toast.LENGTH_SHORT).show();
}
发现是可以正确解析到数据的,此时如果我们删掉那个手动指定的UID会如何呢?结果会出现下面异常:
大致意思就是序列化文件里的UID为1,而本地类的UID变成了这么一长串数字,可见我们即使不写这个ID,编译器也会给我们加上,一旦类发生改变,ID就会改变,导致反序列化失败,若是UID相同,即使类有改变,也会尽可能的恢复数据。但是并不是所有情况都可以,比如类名的改变,自然是不可能恢复的
Parcelable接口
相比Serializable,这个接口就复杂的多了,一个简单的例子如下:
public class Item implements Parcelable{
public int id;
public String msg;
Item(int id,String msg){
this.id = id;
this.msg = msg;
}
protected Item(Parcel in) {
id = in.readInt();
msg = in.readString();
}
public static final Creator<Item> CREATOR = new Creator<Item>() {
@Override
public Item createFromParcel(Parcel in) {
return new Item(in);
}
@Override
public Item[] newArray(int size) {
return new Item[size];
}
};
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(id);
dest.writeString(msg);
}
}
对比得知,Serializable的序列化方式并不指明具体的实现方式,而Parcelable则根据具体的方法来实现,比较清晰。如序列化过程由writeToParcel完成,其实就是将各个数据写到一个Parcel对象里,反序列化由一个静态的Creator实例化对象完成,调用其中的createFromParcel方法即可。虽然要实现的方法比较复杂,但是如果你使用的是Android Studio,其中的代码智能补全可以帮你把所有事情都完成。下面我问就写一个例子,应用一下。
Intent intent = new Intent();
intent.putExtra("item",new Item(10,"s"));
intent.setClass(getApplicationContext(),SecondActivity.class);
startActivity(intent);
Item item = getIntent().getParcelableExtra("item");
Toast.makeText(this,item.id+item.msg,Toast.LENGTH_SHORT).show();
还有一点我们要清楚,Parcelable是Android里面的,所以不能用于IO序列化对象,但能用在Intent传递数据或其他方面。另外Parcelable中还有一个describeContents方法,这个方法在含有文件描述符时应该返回1,其余几乎所有情况都返回0.
两种序列化的比较
一般而言,Serializable时借助IO的,需要大量IO操作,比较消耗性能,Parcelable都是在内存中完成的,效率比较高。但是Parcelable不能进行数据持久化,这时还需要Serializable
网友评论