Android序列化(Serializable/Parcelable)總結

什麼是序列化?爲什麼要序列化?怎麼進行序列化?

序列化定義:將一個類或對象轉換成可存儲、可傳輸狀態的過程。對象序列化後,可以在進程內/進程間、網絡間進行傳輸,也可以做本地持久化存儲。

爲什麼要序列化: 系統底層並不認識對象,數據傳輸是以字節序列形式傳遞,以進程間通信爲例,需要將對象轉化爲字節序列(字節序列中包括該對象的類型,成員信息等),然後在目標進程裏通過反序列化字節序列,將字節序列轉換成對象。

序列化方式:

  • Serializable(Java提供 後面簡稱爲S)
  • Parcelable(Android特有 下面簡稱爲P)

注意事項

  • 參與序列化的成員變量本身也是需要可序列化的,未實現序列化的內部變量不會參與序列化/反序列化
  • 未序列化的變量將會調用自身的無參構造函數重新創建
  • 下面兩種成員變量不會參與到序列化過程中:
    1、static靜態變量屬於類而不屬於對象
    2、transient標記的成員變量

Serializable

S是Java API,是一個通用的序列化機制,可以將對象序列化並保存在本地或內存中。S是一個空接口:

public interface Serializable {}

S用來標識當前類可以被ObjectOutputStream序列化,以及被ObjectInputStream反序列化。

實現原理

兩個相關概念:

ObjectStreamClass: 序列化類的描述符。它包含類的名稱和serialVersionUID。它由Java VM加載,可以使用lookup方法找到或創建。

ObjectStreamField: 類(可序列化)的可序列化字段的描述。ObjectStreamFields數組用於聲明類的可序列化字段。

操作S序列化過程的有ObjectOutputStream/ObjectInputStream,他們都是在Java層實現的。

1、序列化過程(writeObject方法)

  • 通過ObjectStreamClass記錄目標對象的類型、類名等信息,內部有個ObjectStreamFields數組,用來記錄目標對象的內部變量(內部變量可以是基本類型,也可以是自定義類型,但是必須都支持序列化—必須是S不能是P)。

  • 首先通過ObjectStreamClass.lookup()找到或創建ObjectStreamClass,然後調用defaultWriteFields方法,在方法中通過getPrimFieldValues()獲取基本數據類型並賦值到primVals(byte[]類型)中,再通過getObjFieldValues()獲取到自定義對象(通過Unsafe類實現而不是反射)並賦值到objVals(Object[]類型)中,接着遍歷objVals數組,然後遞歸調用writeObject方法重複上述操作。

2、反序列化過程(readObject方法)

  • 通過readClassDescriptor()讀取InputStream裏的數據並初始化ObjectStreamClass類,再根據這個實例通過反射創建目標對象實例。

serialVersionUID

serialVersionUID用來輔助序列化和反序列化的過程的,序列化過程中會把類中的serialVersionUID寫入序列化文件中,在反序列化時,檢測序列化文件中serialVersionUID和當前類中的serialVersionUID是否一致,如果一致,纔可以繼續進行反序列化操作;如果不一致,說明類序列化後發生了一些改變,比如成員變量的類型發生改變,此時是不能反序列化的。

是否需要指定serialVersionUID? 答案是肯定的,如果不指定serialVersionUID,在序列化時系統也會自動幫我們生成一個並寫入序列化文件中,此時如果當前類發生了微小的變化也會導致serialVersionUID改變,從而反序列化失敗;如果指定了serialVersionUID,即使類發生某些修改也不會影響反序列化,但一些類結構發生改變,如類名改變、成員變量的類型發生了改變,此時即使serialVersionUID驗證通過了,反序列化依然會失敗。

舉例

Shop_S.java:

public class Shop_S extends MyBean implements Serializable {
    
    private static final long serialVersionUID = -1399695071515887643L;

    public String mShopName;

    public int mShopId;

    public String mShopPhone;

    public static int STATIC_VALUE = 100;//靜態值

    public transient int TRANSIENT_VALUE = 100;//被transient修飾

    @NonNull
    @Override
    public String toString() {
        return "Serializable: mShopName is " + mShopName
                + ",mShopId is " + mShopId
                + ",mShopPhone is " + mShopPhone
                + ",STATIC_VALUE is " + STATIC_VALUE
                + ",TRANSIENT_VALUE is " + TRANSIENT_VALUE;
    }
}

執行序列化和反序列化過程:

    public static void main(String[] args) throws IOException {
        //------------------Serializable------------------
        Shop_S shop = new Shop_S();
        shop.mShopName = "便利蜂";
        shop.mShopId = 2020;
        shop.mShopPhone = "18888888888";
        shop.TRANSIENT_VALUE = 1000;

        //序列化
        ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("shop.obj"));
        outputStream.writeObject(shop);
        outputStream.close();

        //反序列化
        ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("shop.obj"));
        try {
            Shop_S shop_s = (Shop_S) inputStream.readObject();
            System.out.println(shop_s.toString());
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } finally {
            inputStream.close();
        }
    }

執行結果:

Serializable: mShopName is 便利蜂,mShopId is 2020,mShopPhone is 18888888888,STATIC_VALUE is 100,TRANSIENT_VALUE is 0

結果看到反序列化成功,從序列化結構中又重新生成了對象,這裏注意一點,類中的變量TRANSIENT_VALUE是由transient修飾的,不能被序列化,所以反序列化時得到的是默認值。

Parcelable

P是Android SDK API,通過IBinder作爲消息載體,可以在進程內、進程間(AIDL)高效傳輸數據,其序列化操作完全由底層實現,不同版本的API實現方式可能不同,不宜做本地持久化存儲。

實現原理

P的序列化操作在Native層實現,通過write內存寫入及read讀內存數據重新生成對象。P將對象進行分解,且分解後每一部分都是支持可傳遞的數據類型。

舉例

1、實現P接口,複寫describeContents方法和writeToParcel方法
2、實例化靜態內部對象CREATOR,實現接口Parcelable.Creator 。

Shop_P.java:

public class Shop_P implements Parcelable {

    public String mShopName;
    public int mShopId;
    public String mShopAddress;
    
    /**
     * 從序列化結構中創建原始對象
     *
     * @param in
     */
    protected Shop_P(Parcel in) {
        mShopName = in.readString();
        mShopId = in.readInt();
        mShopAddress = in.readString();
    }

    //反序列化
    public static final Creator<Shop_P> CREATOR = new Creator<Shop_P>() {
        //根據字節序列重新生成對象
        @Override
        public Shop_P createFromParcel(Parcel in) {
            return new Shop_P(in);
        }
        //供反序列化本類數組時調用
        @Override
        public Shop_P[] newArray(int size) {
            return new Shop_P[size];
        }
    };
    //內容接口描述,當前對象中存在文件描述符時返回1,其他都返回0即可
    @Override
    public int describeContents() {
        return 0;
    }

    /**
     * 將當前對象寫入序列化結構中
     *
     * @param dest
     * @param flags
     */
    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeString(mShopName);
        dest.writeInt(mShopId);
        dest.writeString(mShopAddress);
    }
}

注意:createFromParcel()和writeToParcel()方法中對應變量讀寫的順序必須是一致的,否則序列化會失敗並拋異常。

Parcel處理工具:

public class PUtil {

    //marshall Parcel將自身保存的數據以byte數組形式返回
    public static byte[] marshall(Parcelable parcelable) {

        Parcel parcel = Parcel.obtain();
        parcel.setDataPosition(0);
        parcelable.writeToParcel(parcel, 0);
        byte[] bytes = parcel.marshall();
        parcel.recycle();
        return bytes;
    }

    //從byte數組中獲取數據,存入自身的Parcel中
    public static Parcel unmarshall(byte[] bytes) {
        Parcel parcel = Parcel.obtain();
        parcel.unmarshall(bytes, 0, bytes.length);
        parcel.setDataPosition(0);
        return parcel;
    }

    private static final String SP_NAME = "sp_parcel";
    private static final String PARCEL_KEY = "parcel_key";

    //將bytes經過base64轉換成字符串並存儲到sp種種
    public static void save(Context context, byte[] bytes) {
        SharedPreferences preferences = context.getSharedPreferences(SP_NAME, Context.MODE_PRIVATE);
        SharedPreferences.Editor editor = preferences.edit();
        String saveStr = Base64.encodeToString(bytes, 0);
        editor.putString(PARCEL_KEY, saveStr);
        editor.apply();
    }

    //從sp中取出字符串並轉換成bytes 最終轉換成Parcel
    public static Parcel getParcel(Context context) {
        SharedPreferences preferences = context.getSharedPreferences(SP_NAME, Context.MODE_PRIVATE);
        byte[] bytes = Base64.decode(preferences
                .getString(PARCEL_KEY, ""), Base64.DEFAULT);

        return unmarshall(bytes);
    }
}
 Shop_P shopP = new Shop_P();
 shopP.mShopName = "便利蜂";
 shopP.mShopId = 2020;
 shopP.mShopPhone = "18888888888";
 shopP.TRANSIENT_VALUE = 1000;

 byte[] bytes = PUtil.marshall(shopP);//Parcel->bytes[]
 PUtil.save(context, bytes);//保存bytes[]
 Parcel parcel = PUtil.getParcel(context);//bytes[]->Parcel
 Shop_P shop_p = Shop_P.CREATOR.createFromParcel(parcel);
 Log.e("TTT", shop_p.toString());

執行結果:

Parcelable: mShopName is 便利蜂,mShopId is 2020,mShopPhone is 18888888888,STATIC_VALUE is 100,TRANSIENT_VALUE is 0

Parcelable、Serializable比較

效率對比

S序列化和反序列化會經過大量的I/O操作,產生大量的臨時變量引起GC;P是基於內存實現的封裝和解封(marshalled& unmarshalled),效率比S快很多。

容錯率對比

字段改變對反序列化的影響

改變字段 Serializable Parcelable
增加字段 ✔️ 追加到末尾:✔️ 其他:❌
刪除字段 ✔️
修改字段類型

總結

對比 Serializable Parcelable
所屬API Java API Android SDK API
特點 序列化和反序列化會經過大量的I/O操作,產生大量的臨時變量引起GC,且反序列化時需要反射 基於內存拷貝實現的封裝和解封(marshalled& unmarshalled),序列化基於Native層實現,不同版本的API實現可能不同
開銷 相對高 相對低
效率 相對低 相對高
適用場景 序列化到本地、網絡傳輸 內存序列化

參考

【1】https://www.jianshu.com/p/1b362e374354
【2】https://www.wanandroid.com/wenda/show/9002
【3】https://wangchunlei000.github.io/bigthunder.github.io/2019/07/19/Android%E4%B8%AD%E4%B8%A4%E7%A7%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%96%B9%E5%BC%8F%E7%9A%84%E6%AF%94%E8%BE%83Serializable%E5%92%8CParcelable/
【4】http://darryrzhong.xyz/2019/09/15/Android%E4%B9%8B%E5%BA%8F%E5%88%97%E5%8C%96%E8%AF%A6%E8%A7%A3/
【5】https://mp.weixin.qq.com/s/Qj-Bed2G6ekQ4DmogUffWw

發佈了53 篇原創文章 · 獲贊 98 · 訪問量 10萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章