在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