Android序列化方式總結(jié)

什么是序列化

我們?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è)屬性,即nameage,安裝了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ǔ)。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容