Serializable和Parcelable接口可以完成對象的序列化過程,當我們需要通過Intent和Binder傳輸數據時就需要使用者兩種序列化方式。還有,我們需要對象持久化到存儲設備或者通過網絡傳輸給其他客戶端,這個使用也需要使用Serializale來完成對象的序列化。在Android應用開發中,這兩種方式都很常見,但兩者方式並不相同。
1.Serializable接口
Serializable接口是Java提供的一個序列化接口,它是一個空接口,爲對象提供標準的序列化和反序列化操作。使用Serializable來實現的對象的序列化相當簡單,只需要在類的生命中指定一個類似相面的標識即可自動實現默認的序列化過程。
private static final long serialVersionUID=871238749032
完整的對象序列化代碼示例如下:
//Model
public class User implements Serializable{
private static final long serialVersionUID=871238749032;
public int userId;
public String userName;
public String password;
}
//序列化到本地
User user=new User(0,"[email protected]","123456");
ObjectOutputStream out=new ObjectOutputStream(new FileOutputStream("user.obj"));
out.writeObject(user);
out.close;
//反序列化
ObjectInputStream in=new ObjectInputStream(new FileInputStream("user.obj"));
User user=(User)in.readObject();
in.close();
這種方式是Java提供的一種序列化方式,過程非常簡單,甚至有些開發人員都不需要聲明serialVersionUID也可以完成這個過程,但serialVersionUID到底需不需要指定呢?
需要!
Java API既然提供了這個serialVersionUID,那麼它必定是有用的。這個serialVersionUID是用來輔助序列化和反序列化過程的,原則上序列化後的數據中的serialVersionUID只有和當前類的serialVersionUID相同才能夠正常地被反序列化。
serialVersionUID的詳細工作過程是這樣的:序列化的時候系統會把當前類的serialVersionUID寫入序列化的二進制文件中,當反序列化的時候系統會檢測文件中的serialVersionUID是否和當前類的serialVersionUID一致,如果一致就說明序列化的類的版本和當前類的版本是相同的,這個時候可以成功反序列化;否則說明當前類和反序列化的類相比發生了某些變化,比如成員變量的數量、類型發生了變化,這個時候是無法正常反序列化的。
一般來說,我們應該手動指定serialVersionUID的值,比如1L,也可以讓IDE根據當前類的結構自動去生成它的hash值,這樣序列化和反序列化時兩者的serialVersionUID是相同的,因此可以正常進行反序列化操作。如果不手動指定serialVersionUID的值
反序列化時當前類有些改變,比如增加或者刪除了某些成員變量,那麼系統就會重新計算當前類的hash值並把它賦值給serialVersionUID,這個時候當前類的serialVersionUID就和反序列化數據中的serialVersionUID不一致,就會造成反序列化失敗的結果。所以,手動指定serialVersionUID可以在很大程度上避免反序列化過程的失敗。
比如當版本升級後,我們可能刪除了某個成員變量也可能增加了一些新的成員變量,這個時候我們的反序列化過程依然能夠成功,程序仍然能夠最大限度地回覆數據;相反,如果不指定serialVersionUID的話,程序會發生Crash。
當然,我們還需要考慮一種情況,如果類結構發生了非城規改變,比如修改了類名,修改了成員變量的類型,這個時候儘管serialVersionUID驗證通過了,但是反序列化過程仍然會失敗,因爲類的結構有了毀滅性的改變,根本無法從老版本的數據中還原出一個新的類結構的對象。
對於使用序列化還有兩點需要注意:
1.靜態成員變量屬於類不屬於對象,所以不參與序列化過程
2.用transient關鍵字標記的成員變量不參與序列化過程
2.Parcelable接口
Parcelable接口是Android SDK提供的一種專門用於Android應用中對象的序列化和反序列化的方式,相比於Seriablizable具有更好的性能。實現Parcelable接口的對象就可以實現序列化並可以通過Intent和Binder傳遞。
下面是一個完成的實現了Parcelable接口的類
public class User implements Parcelable{
public int userId;
public String userName;
public String password;
public Book book;
public User(int userId,String userName,String password,Book book){
this.userId=userId;
this.userName=userName;
this.password=password;
this.book=book;
}
public int describeContents(){
//幾乎所有情況都返回0,僅在當前對象中存在文件描述符時返回1
return 0;
}
public void writeToParcel(Parcel out,int flags){
out.writeInt(userId);
out.writeString(userName);
out.writeString(password);
out.writeParcelable(book,0);
}
public static final Parcelable.Creator<User> CREATOR=new Parcelable.Creator<User>(){
public User createFromParcel(Parcel in){
return new User(in);
}
public User[] newArray(int size){
return new User[size];
}
}
private User(Parcel in){
userId=in.readInt();
userName=in.readString();
password=in.readString(); book=in.readParcelable(Thread.currentThread().getContextClassLoader());
}
}
看起來比Serializable方式複雜太多。我們使用表格把Parcelable方式的相關方法進行說明
方法 | 功能 | 標記位 |
---|---|---|
createFromParcel(Parcel in) | 從序列化後的對象中創建原始對象 | |
newArray(int size) | 創建指定長度的原始對象數組 | |
User(Parcel in) | 從序列化後的對象中創建原始對象 | |
writeToParcel(Parcel out,int flags) | 將當前對象寫入序列化結構中 | PARCALABLE_WRITE_RETURN_VALUE |
describeContents | 返回當前對象的內容描述,幾乎所有情況都返回0,僅在當前對象中存在文件描述符時返回1 | CONTENTS_FILE_DESCRIPTOR |
既然Parcelable和Serializable都可以實現序列化並且可以用於Intent間的數據傳遞,那麼兩者有什麼區別呢?
區別 | Serializable | Parcelable |
---|---|---|
所屬API | JAVA API | Android SDK API |
原理 | 序列化和反序列化過程需要大量的I/O操作 | 序列化和反序列化過程不需要大量的I/O操作 |
開銷 | 開銷大 | 開銷小 |
效率 | 低 | 很高 |
使用場景 | 序列化到本地或者通過網絡傳輸 | 內存序列化 |