什么是序列化
我們在開發過程中,都是面向對象開發的。但是,對于計算機來說,計算機只認識二進制。數據的傳輸和存儲都要通過字節流的方式來進行。所以,如果我們想要在本地持久化存儲對象或是在網絡上或進程間傳輸對象,必須首先將對象轉換成字節流。將對象轉換成字節流的過程,就是所謂的序列化。反序列化就是序列化的反向過程,即將字節流轉換成對象的過程。序列化的核心思想就是對象狀態的保存與重建。
序列化方式
在android中,有如下序列化方式:
- Serializable序列化
- Parcelable序列化
除了上述兩種方式外,還可以通過Json進行序列化(例如fastjson),這里主要對比Serializable和Parcelable這兩種序列化方式。
Serializable
Serializable是Java自帶的序列化方法。在Java中,提供了一個Serializable接口,它是一個空接口,沒有定義任何方法,它僅僅只起到了標記作用。它告訴代碼,只要是實現了Serializable接口的類都是可以被序列化的,而真正的序列化動作由系統來完成的。所以,通過Serializable的方式來實現序列化很簡單,只要想實現序列化的類實現Serializable接口即可。例如,通過繼承Serializable接口來實現一個User對象。
public class SerializableUser implements Serializable {
private String name;
private int age;
public SerializableUser(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
Serializable對象可以通過ObjectOutputStream
持久化,通過ObjectInputStream
讀取到內存。具體代碼如下。
public void saveSerializableUser(SerializableUser user) {
try {
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(getFilesDir().getAbsolutePath() + "/user.txt"));
out.writeObject(user);
out.close();
}catch (Exception e) {
e.printStackTrace();
}
}
public SerializableUser getSerializableUser(){
try {
ObjectInputStream in = new ObjectInputStream(new FileInputStream( getFilesDir().getAbsolutePath() + "/user.txt"));
SerializableUser user = (SerializableUser) in.readObject();
in.close();
return user;
}catch (Exception e) {
e.printStackTrace();
}
return null;
}
例如,在ActvityA中持久化存儲一個User對象,然后啟動ActivityB并在ActivityB中將ActivityA中持久化存儲的對象讀取到內存中。代碼如下。
// ActivityA
public void onClick(View view) {
switch (view.getId()) {
case R.id.btn_start_activityb:
startActivityB();
break;
case R.id.btn_save_user:
saveSerializableUser(new SerializableUser("xmh",18));
break;
default:
break;
}
}
private void startActivityB() {
Intent intent = new Intent(this, MainActivityB.class);
startActivity(intent);
}
//ActivityB
public void onClick(View view) {
switch (view.getId()) {
case R.id.btn_read_user:
SerializableUser user = getSerializableUser();
Log.d(TAG, "name: " + user.getName() + ", age: " + user.getAge());
break;
default:
break;
}
}
在ActivityA中先保存User對象,然后啟動ActivityB,在ActvityB中點擊讀取按鈕,此時可以看到logcat中日志如下:
2022-02-27 20:02:42.495 675-675/com.example.parcelabletest D/MainActivityB: name: xmh, age: 18
通過日志,我們可以看到序列化和反序列化都成功了。
在開發過程中,類的結構并非是固定不變的。例如,當App V1版本發布的時候,User中只包含兩個屬性,即name
和age
,安裝了App V1版本的用戶會在本地持久化存儲User對象,當App升級到V2版本的時候,新的業務需求需要在User類中增加一個userId,用于唯一標識一個用戶,當用戶用App V2版本覆蓋安裝時,會存在一個問題:用戶本地持久化的User對象V1版本的User對象,而當用戶使用V2版本對User對象進行反序列化的時候,使用的是帶userId的User類進行反序列化,也就是說反序列化時,類的結構發生了變化。這時候會發生什么?我們可以在SerializableUser類中新增userId
屬性,并增加對應的get和set方法,然后我們在啟動ActivityB之前不存儲User對象,而是直接啟動(本地已經持久化了一個User對象),然后再ActivityB中反序列化本地存儲的User對象,發現程序發生了異常,異常信息如下所示。
2022-02-27 20:25:37.209 3580-3580/com.example.parcelabletest D/MainActivityB: getSerializableUser: com.example.parcelabletest.SerializableUser; local class incompatible: stream classdesc serialVersionUID = 3615204604863452229, local class serialVersionUID = 5315681738373527338
這里的異常信息提示很明確,就是反序列化的類和之前序列化的類不兼容,因為我們改了類的結構。同時,這里還提到了一個名詞serialVersionUID
,這個serialVersionUID
是什么,剛才的類中明明沒有寫任何關于serialVersionUID
的東西。
serialVersionUID
serialVersionUID是用來輔助序列化和反序列化的,它用來標識反序列化的時候,類的結構是否發生了變化。在序列化時,系統會將serialVersionUID也寫入序列化文件中,然后反序列化時再用當前類中的serialVersionUID和文件中的serialVersionUID對比,如果兩個serialVersionUID相同說明序列化的類的版本和當前的類的版本一樣,可以成功反序列化。否則,說明當前的類相比于序列化時的類發生了某些變化。比如成員變量的數量,類型可能發生了變化,這個時候是不能正常進行反序列化的。
如果在類中沒有手動設置serialVersionUID的值,系統會自動根據當前類的結構計算hash值作為serialVersionUID,如果類的結構發生了變化,那么系統計算出來的serialVersionUID自然就不一樣,這樣反序列化就會失敗。現在,回過來去看之前系統拋出的異常,我們就可以知道原因了。
那么如果我們在代碼中手動設置了serialVersionUID,會發生什么呢?我們在User類中新增一個serialVersionUID屬性,serialVersionUID可以設置為任意值,例如,寫成1,不過這里我們利用Android Studio來生成當前類的serialVersionUID,添加serialVersionUID后,User類如下所示。
public class SerializableUser implements Serializable {
private static final long serialVersionUID = 3615204604863452229L;
private String name;
private int age;
public SerializableUser(String name, int age) {
this.name = name;
this.age = age;
}
…………
}
然后先將新的User對象進行持久化,持久化之后,再按照之前的方式在User類中添加userId屬性后,直接對之前持久化的User對象進行反序列化,此時我們看到打印的日志如下所示。
2022-02-27 20:51:49.161 7412-7412/com.example.parcelabletest D/MainActivityB: name: xmh, age: 18
通過日志可以看到,反序列化成功了,這就是serialVersionUID的作用,當手動設定以后,就可以很大程度上避免反序列化的失敗。程序能夠最大限度地恢復數據。但是在某些情況下,如果類的結構發生了非常規的改變,即使serialVersionUID相同,也無法反序列化,例如,類名發生了變化。所以,在平常開發過程中,最好手動設置serialVersionUID的值,否則只要類的結構稍微發生點改變,都會導致反序列化失敗。
另外有兩點需要注意:
- 靜態成員變量屬于類,不屬于對象,不參與序列化的過程。
- 使用transient關鍵字修飾的成員變量不參與序列化的過程。
以上就是使用Serializable進行序列化時的用法。
Parcelable
Parcelable是Android提供的新的序列化的方式,Parcelable也是一個接口,但是通過Parcelable進行序列化要比通過Serializable進行序列化復雜。使用Parcelable進行序列化的時候,除了實現Parcelable接口外,還需要要實現中的describeContents(),writeToParcel(Parcel dest, int flags),并添加一個靜態成員變量CREATOR,這個變量需要實現Parcelable.Creator接口。
describeContents()
返回內容描述,基本在所有情況下,返回0即可。
writeToParcel(Parcel dest, int flags)
這個方法完成序列化的過程,將當前對象寫入序列化結構中,內部是通過Parcel一系列的write方法來完成序列化的,flag一般情況下都是0。
CREATOR
一個實現了Parcelable.Creator接口的匿名內部類,在Parcelable.Creator接口內有createFromParcel和newArray兩個方法來完成反序列化操作。createFromParcel用于從序列化后的對象中創建原始對象,newArray方法用于創建指定長度的原始對象的數組。
通過Parcelable來實現User序列化的代碼如下。
public class ParcelableUser implements Parcelable {
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public ParcelableUser(String name, int age) {
this.name = name;
this.age = age;
}
private ParcelableUser(Parcel parcel) {
name = parcel.readString();
age = parcel.readInt();
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(name);
dest.writeInt(age);
}
public static Parcelable.Creator<ParcelableUser> CREATOR = new Parcelable.Creator<ParcelableUser>() {
@Override
public ParcelableUser createFromParcel(Parcel source) {
return new ParcelableUser(source);
}
@Override
public ParcelableUser[] newArray(int size) {
return new ParcelableUser[size];
}
};
}
需要注意的是,在Parcelable進行序列化和反序列化的時候,都是按照順序進行的,什么叫做按照順序呢?以上面的User對象為例,在writeToParcel方法中,我們依次寫入了name,age。
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(name);
dest.writeInt(age);
}
在createFromParcel方法中,我們要按照寫入時的順序來讀,如下所示。
@Override
public ParcelableUser createFromParcel(Parcel source) {
return new ParcelableUser(source);
}
private ParcelableUser(Parcel parcel) {
name = parcel.readString();
age = parcel.readInt();
}
如果反序列化的時候讀取的順序和寫入的順序不一致,name反序列化就會出錯。
Serializable和Parcelable區別
Serializable是Java中的序列化接口,其使用簡單方便,但是開銷比較大,序列化和反序列化的過程中會用到反射,還會產生很多臨時變量,引起GC。在Android中,Serializable一般用于持久化存儲數據。
Parcelable是Android中獨有的序列化方式,其使用相當Serializable來說比較麻煩,但是效率比較高,所以在內存中傳遞數據時推薦使用Parcelable,比如Andro中Activity間傳遞數據或者在進程間傳遞數據。但是,因為不同版本Parcelable實現不同,因此不推薦使用Parcelable進行持久化存儲。