自己開發Android
也有些時間了,Serializable
和Parcelable
遇到過不止一次了。但是每次別人問起具體的內容自己偏偏記得不是很清晰。因為某些原因再次梳理一下,以文章的形式給自己存儲下來。溫故而知新!!
序列化和反序列化幾乎是工程師們每天都要面對的事情,但是要精確掌握這兩個概念并不容易:一方面,它們往往作為框架的一部分出現而湮沒在框架之中;另一方面,它們會以其他更容易理解的概念出現,例如加密、持久化。然而,序列化和反序列化的選型卻是系統設計或重構一個重要的環節,在分布式、大數據量系統設計里面更為顯著。恰當的序列化協議不僅可以提高系統的通用性、強健性、安全性、優化系統性能,而且會讓系統更加易于調試、便于擴展。總而言之搞懂序列化是很重要滴。
序列化
將對象的狀態信息轉換為可以存儲或傳輸的形式的過程。在序列化期間,對象將其當前狀態寫入到臨時或持久性存儲區。
反序列化
就是讀取序列化后保存在存儲區的序列化信息或反序列化對象的狀態,重新創建該對象。
序列化和反序列化通常是并存的,他們的關系就像進行加密和解密操作一樣。前者需要相同的序列化方式,后者需要知道秘鑰。
Android
中將對象序列化的方式有兩種Serializable
和Parcelable
這兩個接口都可以完成。Serializable
是Java
自帶的序列化方法,而Android
原生的序列化為Parcelable
。這并不意味著在Android
中可以拋棄Serializable
,只能說在Android
中Parcelable
方法實現序列化更有優勢。下邊我們可以具體來看看這兩個接口實現。(PS:對象參加序列化的時候其類的結構是不能發生變化的,以下Demo都是在此基礎上演示)
Serializable
我們先看一個實現Serializable
接口的類:
public class User implements Serializable {
private static final long serialVersionUID = -4454266436543306544L;
public int userId;
public String userName;
public User(int userId, String userName) {
this.userId = userId;
this.userName = userName;
}
}
Serializable
是java
提供的一個序列化接口,它是一個空接口,專門為對象提供標準的序列化和反序列化操作,使用Serializable
來實現序列化相當簡單,只需要在類的聲明中指定一個類似下面的標示即可自動實現默認的序列化過程(serialVersionUID
可自動生成也可自己寫,如0L,1L,2L...)。
private static final long serialVersionUID = -4454266436543306544L;
通過Serializable
方式來實現對象的序列化,實現起來非常簡單,幾乎素有工作都被系統自動完成。如果進行對象的序列化和反序列化也非常簡單,只需要采用ObjectOutputStream
和ObjectInputStream
即可輕松實現。下面舉個簡單的列子:
//序列化過程
User user = new User(1, "Tom");
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("cache.txt"));
out.writeObject(user);
out.close();
//反序列化過程
ObjectInputStream in = new ObjectInputStream(new FileInputStream("cache.txt"));
User newuser = (User) in.readObject();
in.close();
上述代碼采用Serializable
方式序列化對象的典型過程,只需要把實現Serializable
接口的User
對象寫到文件中就可以快速恢復了,恢復后的對象newUser
和User
的內容完全一致,但是兩個并不是同一個對象。
serialVersionUID
上面提到,想讓一個對象實現序列化,只需要這個類實現Serializable
接口并聲明一個serialVersionUID
即可,實際上,甚至這個serialVersionUID
也不是必需的,我們不聲明這個serialVersionUID
同樣可以實現序列化,但是這將會對反序列化過程產生影響,具體會產生什么影響呢?
解決這個問題前我們先提一個問題,為什么需要serialVersionUID
呢?
因為靜態成員變量屬于類不屬于對象,不會參與序列化過程,使用
transient
關鍵字標記的成員變量也不參與序列化過程。 (PS:關鍵字transient
,這里簡單說明一下,Java
的serialization
提供了一種持久化對象實例的機制。當持久化對象時,可能有一個特殊的對象數據成員,我們不想用serialization
機制來保存它。為了在一個特定對象的一個域上關閉serialization
,可以在這個域前加上關鍵字transient
。當一個對象被序列化的時候,transient
型變量的值不包括在序列化的表示中,然而非transient
型的變量是被包括進去的)
這個時候又有一個疑問serialVersionUID
是靜態成員變量不參與序列化過程,那么它的存在與否有什么影響呢?
具體過程是這樣的:序列化操作的時候系統會把當前類的
serialVersionUID
寫入到序列化文件中,當反序列化時系統會去檢測文件中的serialVersionUID
,判斷它是否與當前類的serialVersionUID
一致,如果一致就說明序列化類的版本與當前類版本是一樣的,可以反序列化成功,否則失敗。
接下來我們回答聲明serialVersionUID
對進行序列化有啥影響?
如果不手動指定
serialVersionUID
的值,反序列化時當前類有所改變,比如增加或者刪除了某些成員變量,那么系統就會重新計算當前類的hash值并且把它賦值給serialVersionUID
,這個時候當前類的serialVersionUID
就和序列化的數據中的serialVersionUID
不一致,于是反序列化失敗。所以我們手動指定serialVersionUID
的值能很大程度上避免了反序列化失敗。
以上就是自己對Serializable
的認識,下邊來看看Parcelable
相關的知識!!
Parcelable
我們先看一個使用Parcelable進行序列化的例子:
public class Book implements Parcelable {
public int bookId;
public String bookName;
public Book() {
}
public Book(int bookId, String bookName) {
this.bookId = bookId;
this.bookName = bookName;
}
//從序列化后的對象中創建原始對象
protected Book(Parcel in) {
bookId = in.readInt();
bookName = in.readString();
}
public static final Creator<Book> CREATOR = new Creator<Book>() {
//從序列化后的對象中創建原始對象
@Override
public Book createFromParcel(Parcel in) {
return new Book(in);
}
//指定長度的原始對象數組
@Override
public Book[] newArray(int size) {
return new Book[size];
}
};
//返回當前對象的內容描述。如果含有文件描述符,返回1,否則返回0,幾乎所有情況都返回0
@Override
public int describeContents() {
return 0;
}
//將當前對象寫入序列化結構中,其flags標識有兩種(1|0)。
//為1時標識當前對象需要作為返回值返回,不能立即釋放資源,幾乎所有情況下都為0.
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(bookId);
dest.writeString(bookName);
}
@Override
public String toString() {
return "[bookId=" + bookId + ",bookName='" + bookName + "']";
}
}
雖然Serializable
可以將數據持久化在磁盤,但其在內存序列化上開銷比較大(PS:Serializabl
在序列化的時候會產生大量的臨時變量,從而引起頻繁的GC),而內存資源屬于android
系統中的稀有資源(android
系統分配給每個應用的內存開銷都是有限的),為此android
中提供了Parcelable
接口來實現序列化操作,在使用內存的時候,Parcelable
比Serializable
性能高,所以推薦使用Parcelable
。
Parcelable
內部包裝了可序列化的數據,可以在Biander
中自由的傳輸,從代碼中可以看出,在序列化過程中需要實現的功能有序列化,反序列化和內容描述。序列化功能是由writetoParcel
方法來完成,最終是通過Parcel
中的一系列write
方法來完成的。反序列化功能是由CREATOR
方法來完成,其內部標明了如何創建序列化對象和數組,并通過Parcel
的一系列read
方法來完成反序列化過程(PS:write
和read
的順序必須一致~!);內容描述功能是有describeContents
方法來完成,幾乎所有情況下這個方法都應該返回0,僅當當前對象中存在文件描述符時,此方法返回1.
系統已經給我們提供了許多實現了Parcelable
接口類,他們都是可以直接序列化的,比如Intent
,Bundle
,Bitmap
等,同事List和Map也支持序列化,提前是他們里面的每個元素都是可以序列化的。
總結
既然
Parcelable
和Serializable
都能實現序列化并且都可以用于Intent間傳遞數據,那么二者改如果選擇呢?Serializable
是Java
中的序列化接口,其使用起來簡單但是開銷很大,序列化和反序列化過程需要大量的I/O
操作。而Parcelable
是Android
中序列化方法,因為更適合于在Android
平臺上,它的缺點就是使用起來比較麻煩,但是它的效率很高,這是Android
推薦的序列化方法,因為我們要首選Parcelable
。Parcelable
主要用于內存序列化上,通過Parcelable
將對象序列化到存儲設備中或者將對象序列化后通過網絡傳輸也都是可以的,但是這個過程稍顯復雜,因此在這兩種情況下建議使用Serializable
。
文章部分摘自任玉剛的《Android開發藝術探索》。