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