什么是序列化
我們在開發(fā)過程中,都是面向?qū)ο箝_發(fā)的。但是,對于計算機來說,計算機只認識二進制。數(shù)據(jù)的傳輸和存儲都要通過字節(jié)流的方式來進行。所以,如果我們想要在本地持久化存儲對象或是在網(wǎng)絡(luò)上或進程間傳輸對象,必須首先將對象轉(zhuǎn)換成字節(jié)流。將對象轉(zhuǎn)換成字節(jié)流的過程,就是所謂的序列化。反序列化就是序列化的反向過程,即將字節(jié)流轉(zhuǎn)換成對象的過程。序列化的核心思想就是對象狀態(tài)的保存與重建。
序列化方式
在android中,有如下序列化方式:
- Serializable序列化
- Parcelable序列化
除了上述兩種方式外,還可以通過Json進行序列化(例如fastjson),這里主要對比Serializable和Parcelable這兩種序列化方式。
Serializable
Serializable是Java自帶的序列化方法。在Java中,提供了一個Serializable接口,它是一個空接口,沒有定義任何方法,它僅僅只起到了標記作用。它告訴代碼,只要是實現(xiàn)了Serializable接口的類都是可以被序列化的,而真正的序列化動作由系統(tǒng)來完成的。所以,通過Serializable的方式來實現(xiàn)序列化很簡單,只要想實現(xiàn)序列化的類實現(xiàn)Serializable接口即可。例如,通過繼承Serializable接口來實現(xiàn)一個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讀取到內(nèi)存。具體代碼如下。
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中持久化存儲的對象讀取到內(nèi)存中。代碼如下。
// 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
通過日志,我們可以看到序列化和反序列化都成功了。
在開發(fā)過程中,類的結(jié)構(gòu)并非是固定不變的。例如,當App V1版本發(fā)布的時候,User中只包含兩個屬性,即name和age,安裝了App V1版本的用戶會在本地持久化存儲User對象,當App升級到V2版本的時候,新的業(yè)務(wù)需求需要在User類中增加一個userId,用于唯一標識一個用戶,當用戶用App V2版本覆蓋安裝時,會存在一個問題:用戶本地持久化的User對象V1版本的User對象,而當用戶使用V2版本對User對象進行反序列化的時候,使用的是帶userId的User類進行反序列化,也就是說反序列化時,類的結(jié)構(gòu)發(fā)生了變化。這時候會發(fā)生什么?我們可以在SerializableUser類中新增userId屬性,并增加對應(yīng)的get和set方法,然后我們在啟動ActivityB之前不存儲User對象,而是直接啟動(本地已經(jīng)持久化了一個User對象),然后再ActivityB中反序列化本地存儲的User對象,發(fā)現(xiàn)程序發(fā)生了異常,異常信息如下所示。
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
這里的異常信息提示很明確,就是反序列化的類和之前序列化的類不兼容,因為我們改了類的結(jié)構(gòu)。同時,這里還提到了一個名詞serialVersionUID,這個serialVersionUID是什么,剛才的類中明明沒有寫任何關(guān)于serialVersionUID的東西。
serialVersionUID
serialVersionUID是用來輔助序列化和反序列化的,它用來標識反序列化的時候,類的結(jié)構(gòu)是否發(fā)生了變化。在序列化時,系統(tǒng)會將serialVersionUID也寫入序列化文件中,然后反序列化時再用當前類中的serialVersionUID和文件中的serialVersionUID對比,如果兩個serialVersionUID相同說明序列化的類的版本和當前的類的版本一樣,可以成功反序列化。否則,說明當前的類相比于序列化時的類發(fā)生了某些變化。比如成員變量的數(shù)量,類型可能發(fā)生了變化,這個時候是不能正常進行反序列化的。
如果在類中沒有手動設(shè)置serialVersionUID的值,系統(tǒng)會自動根據(jù)當前類的結(jié)構(gòu)計算hash值作為serialVersionUID,如果類的結(jié)構(gòu)發(fā)生了變化,那么系統(tǒng)計算出來的serialVersionUID自然就不一樣,這樣反序列化就會失敗。現(xiàn)在,回過來去看之前系統(tǒng)拋出的異常,我們就可以知道原因了。
那么如果我們在代碼中手動設(shè)置了serialVersionUID,會發(fā)生什么呢?我們在User類中新增一個serialVersionUID屬性,serialVersionUID可以設(shè)置為任意值,例如,寫成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的作用,當手動設(shè)定以后,就可以很大程度上避免反序列化的失敗。程序能夠最大限度地恢復(fù)數(shù)據(jù)。但是在某些情況下,如果類的結(jié)構(gòu)發(fā)生了非常規(guī)的改變,即使serialVersionUID相同,也無法反序列化,例如,類名發(fā)生了變化。所以,在平常開發(fā)過程中,最好手動設(shè)置serialVersionUID的值,否則只要類的結(jié)構(gòu)稍微發(fā)生點改變,都會導(dǎo)致反序列化失敗。
另外有兩點需要注意:
- 靜態(tài)成員變量屬于類,不屬于對象,不參與序列化的過程。
- 使用transient關(guān)鍵字修飾的成員變量不參與序列化的過程。
以上就是使用Serializable進行序列化時的用法。
Parcelable
Parcelable是Android提供的新的序列化的方式,Parcelable也是一個接口,但是通過Parcelable進行序列化要比通過Serializable進行序列化復(fù)雜。使用Parcelable進行序列化的時候,除了實現(xiàn)Parcelable接口外,還需要要實現(xiàn)中的describeContents(),writeToParcel(Parcel dest, int flags),并添加一個靜態(tài)成員變量CREATOR,這個變量需要實現(xiàn)Parcelable.Creator接口。
describeContents()
返回內(nèi)容描述,基本在所有情況下,返回0即可。
writeToParcel(Parcel dest, int flags)
這個方法完成序列化的過程,將當前對象寫入序列化結(jié)構(gòu)中,內(nèi)部是通過Parcel一系列的write方法來完成序列化的,flag一般情況下都是0。
CREATOR
一個實現(xiàn)了Parcelable.Creator接口的匿名內(nèi)部類,在Parcelable.Creator接口內(nèi)有createFromParcel和newArray兩個方法來完成反序列化操作。createFromParcel用于從序列化后的對象中創(chuàng)建原始對象,newArray方法用于創(chuàng)建指定長度的原始對象的數(shù)組。
通過Parcelable來實現(xiàn)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區(qū)別
Serializable是Java中的序列化接口,其使用簡單方便,但是開銷比較大,序列化和反序列化的過程中會用到反射,還會產(chǎn)生很多臨時變量,引起GC。在Android中,Serializable一般用于持久化存儲數(shù)據(jù)。
Parcelable是Android中獨有的序列化方式,其使用相當Serializable來說比較麻煩,但是效率比較高,所以在內(nèi)存中傳遞數(shù)據(jù)時推薦使用Parcelable,比如Andro中Activity間傳遞數(shù)據(jù)或者在進程間傳遞數(shù)據(jù)。但是,因為不同版本Parcelable實現(xiàn)不同,因此不推薦使用Parcelable進行持久化存儲。