Android 進階——持久化存儲序列化方案Serializable和IPC及內存序列化方案Parcelable詳解與應用

引言

相信序列化實戰,所有開發者都經歷過的,常見的JSON、XML、Protobuf、Serializable和Parcelable等等這些本質上都屬於序列化,那麼爲什麼必須要進行序列化才能進行數據通信呢?

一、文件的本質

衆所周知對於我們計算機來說,一切文件的本質都是比特序列(byte序列),文件就是由一個個0或1組成的位(比特byte,每一個邏輯0或者1便是一個位)組合而成,其中8位(byte)爲一組即一個字節(Byte)。操作系統在處理解析不同類型的文件,根據各自不同的協議,把比特序列解析爲具體的格式,比如說解析到是文本文件時按照UTF-8進行編碼(這個UTF-8就是協議,就是操作系統理解的上下文)等等,所以文件=byte序列+上下文(協議)

對於Linux來說,一切都是文件,文件也是一個高層的抽象,設備是一種特殊的文件。

二、序列化和反序列化概述

List和Map也可以序列化,前提是它們存儲每一個元素都是可序列化的。

1、序列化和反序列化的定義

在這裏插入圖片描述

Java序列化就是指把Java對象轉換爲字節序列(二進制流序列)的過程;而Java反序列化就是指把字節序列恢復爲Java對象的過程。

2、序列化和反序列化的意義

序列化時可以確保在傳遞和保存對象時,保證對象的完整性和可傳遞性,對象轉換爲有序字節序列,以便在網絡上傳輸或者持久化保存在本地文件中;而反序列化的主要是在程序中根據字節序列中保存的對象狀態及描述信息,通過反序列化得到相應的對象。簡而言之,通過序列化和反序列化,我們可以對對象進行持久化保存和便捷傳輸並復原。

三、Serializable

1、Serializable 概述

Serializable 序列化接口是Java 提供的原生序列化方案,他是一個空接口,爲對象提供標準的序列化和反序列化操作。

public interface Serializable {
}

使用 Serializable 來實現序列化操作十分簡單,只需要在定義類時實現Serializable 接口並在類中聲明一個long 類型靜態常量serialVersionUID,其中serialVersionUID值可以是任意值,建議根據當前類自動生成其hash值。因爲serialVersionUID 是系統同於確保反序列化安全的一種機制,原則上只有反序列化和序列化時的serialVersionUID一直纔可以進行序列化。序列化時系統會保存當前的serialVersionUID值,在反序列化時就會首先進行serialVersionUID驗證。驗證通過則進行後續操作。如果不指定這個靜態常量,反序列化時當前類的結構有所改變(比如增加或者刪除了某些成員),那麼系統會重新計算當前類的hash值並賦值給serialVersionUID,導致當前類的serialVersionUID與序列化時的不一樣進而產生反序列化失敗引起的異常。

如果類的結構發生了本質的改變,比如說重構了類名、修改了現有成員的類型和名稱,即使serialVersionUID 驗證通過了,反序列化也會失敗。

默認的序列化過程中,靜態變量(屬於類不屬於對象)和被transiend 關鍵字標記的成員變量均不參與序列化過程。

2、JDK中序列化和反序列化的方法

JDK中提供了兩個類:java.io.ObjectInputStream對象輸入流和java.io.ObjectOutputStream 對象輸出流:

方法 說明
ObjectInputStream Object readObject() 從輸入流中讀取字節序列,然後將字節序列反序列化爲一個對象並返回。
ObjectOutputStream void writeObject(Object obj) 將將傳入的obj對象進行序列化,把得到的字節序列寫入到目標輸出流中進行輸出。

3、序列化和反序列化的具體策略

利用Serializable 實現序列化和反序列化有多種形式,不同的方式java.io.ObjectInputStream對象輸入流和java.io.ObjectOutputStream 採取的策略不同:

3.1、僅僅實現了Serializable接口

  • ObjectOutputStream對對象的非transient的實例變量進行序列化。
  • ObjcetInputStream對對象的非transient的實例變量進行反序列化。

3.2、實現了Serializable接口,還實現了readObject(ObjectInputStream in)和writeObject(ObjectOutputSteam out)方法

  • ObjectOutputStream調用Student對象的writeObject(ObjectOutputStream out)的方法進行序列化。
  • ObjectInputStream會調用Student對象的readObject(ObjectInputStream in)的方法進行反序列化。

JDK中的ArrayDeque< E > 就是這類。

3.3、實現了Externalnalizable接口且實現readExternal(ObjectInput in)和writeExternal(ObjectOutput out)方法

  • ObjectOutputStream調用對象的writeExternal(ObjectOutput out))的方法進行序列化。
  • ObjectInputStream會調用對象的readExternal(ObjectInput in)的方法進行反序列化。

4、實現序列化和反序列化

//序列化
Bean bean=new Bean(100,"cmo");
ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("bean.oot"));
oos.writeObject(bean);
oos.close();

//反序列化
FileInputStream fis = new FileInputStream("bean.oot");
ObjectInputStream ois = new ObjectInputStream(fis);
Bean bean2 = (Bean2) ois.readObject();

四、Parcelable

android.os.Parcelable 是Android 針對自身系統某些場景特意定製的高效的序列化方案,在Android中只要實現了android.os.Parcelable接口就可以通過Intent和Binder 進行數據通信。Parcelable接口的實現類是通過Parcel寫入和恢復數據的且必須要有一個非空的靜態變量 CREATOR

AIDL中可以傳遞Set集合類型的數據嗎?建議帶着這個疑問好好看下文。爲什麼?

1、Parcel

1.1、Parcel 概述

Container for a message (data and object references) that can be sent through an IBinder

Parcel提供了一套native機制,將序列化之後的數據先寫入到一個共享內存中,其他進程通過Binder機制就可以從這塊共享內存中讀出字節流,並反序列化成對象。Parcel類可以看成是一個存放序列化數據的容器並能通過Binder傳遞(Binder機制就利用了Parcel類來進行客戶端與服務端數據交互)。Android在Java層和C++層都實現了Parcel,Java 層的Parcel 僅僅是相當於代理角色通過JNI 代理調用android_os_Parcel.cpp對應的方法。(Parcel在C/C++中其實是一塊連續的內存,會自動根據需要自動擴展大小)。Parcel可以包含原始數據類型(通過各種對應的方法writeInt(),writeFloat()等寫入),也可以包含Parcelable對象。重要的是還包含了一個活動的IBinder對象的引用(可以讓對端直接接收到指向這個引用的代理IBinder對象),Parcel 更多詳情由於篇幅問題不便展開。

Parcel不是一般目的的序列化機制。這個類被設計用於高性能的IPC傳輸。因此不適合把Parcel寫入永久化存儲中,因爲Parcel中的數據類型的實現的改變會導致舊版的數據不可讀。

1.2、Parcel 重要的方法

1.2.1、直接操作原始基本類型的方法

writeByte(byte), readByte(), writeDouble(double), readDouble(), writeFloat(float), readFloat(), writeInt(int), readInt(), writeLong(long), readLong(), writeString(String), readString()。

1.2.2、讀寫原始基本類型組成的數組的方法

在向數組寫數據時先寫入數組的長度再寫入數據,而讀數組的方法可以將數據讀到已存在的數組中,也可以創建並返回一個新數組,形如:

  • writeBooleanArray(boolean[]), readBooleanArray(boolean[]), createBooleanArray()
  • writeByteArray(byte[]), writeByteArray(byte[], int, int), readByteArray(byte[]), createByteArray()
  • writeCharArray(char[]), readCharArray(char[]), createCharArray()
  • writeDoubleArray(double[]), readDoubleArray(double[]), createDoubleArray()
  • writeFloatArray(float[]), readFloatArray(float[]), createFloatArray()
  • writeIntArray(int[]), readIntArray(int[]), createIntArray()
  • writeLongArray(long[]), readLongArray(long[]), createLongArray()
  • writeStringArray(String[]), readStringArray(String[]), createStringArray().
  • writeSparseBooleanArray(SparseBooleanArray), readSparseBooleanArray().

1.2.3、用於讀寫標準的Java容器類的方法

writeArray(Object[]), readArray(ClassLoader), writeList(List), readList(List, ClassLoader), readArrayList(ClassLoader), writeMap(Map), readMap(Map, ClassLoader), writeSparseArray(SparseArray), readSparseArray(ClassLoader)。

2、android.os.Parcelable概述

Parcelable是通過Parcel實現了read和write的方法,從而實現序列化和反序列化。簡而言之,相較於普通的getter和setter方法,Parcelable的類似操作是通過Parcel 對象對應的方法完成的。Parcelable爲對象從Parcel中讀寫自己提供了極其高效的協議,並提供了兩種類別的方法:

2.1、把類的信息和數據都寫入Parcel,以使將來能使用合適的類裝載器重新構造類的實例的方法

形如writeParcelable(Parcelable, int) 和 readParcelable(ClassLoader) 或 writeParcelableArray(T[], int) and readParcelableArray(ClassLoader) 等方法

2.2、讀取時必須能知道數據屬於哪個類並傳入正確的Parcelable.Creator來創建對象而不是直接構造新對象的方法(推薦)

riteTypedArray(T[], int), writeTypedList(List), readTypedArray(T[], Parcelable.Creator) and readTypedList(List, Parcelable.Creator)這些方法不會寫入類的信息因此更高效。

直接調用Parcelable.writeToParcel()和Parcelable.Creator.createFromParcel()方法讀寫單個Parcelable對象最高效。

public interface Parcelable {
    /** @hide */
    @IntDef(flag = true, prefix = { "PARCELABLE_" }, value = {
            PARCELABLE_WRITE_RETURN_VALUE,
            PARCELABLE_ELIDE_DUPLICATES,
    })
    @Retention(RetentionPolicy.SOURCE)
    public @interface WriteFlags {}

    /**
     * writeToParcel方法的標記位,表示當前對象需要作爲返回值返回,不能立即釋放
     */
    public static final int PARCELABLE_WRITE_RETURN_VALUE = 0x0001;

    /**
	 * 標識父對象會管理內部狀態中重複的數據
     * @hide
     */
    public static final int PARCELABLE_ELIDE_DUPLICATES = 0x0002;

    /*
     * 用於 describeContents() 方法的位掩碼,每一位都代表着一種對象類型
	 */
	 public static final int CONTENTS_FILE_DESCRIPTOR = 0x0001;

    /** @hide */
    @IntDef(flag = true, prefix = { "CONTENTS_" }, value = {
            CONTENTS_FILE_DESCRIPTOR,
    })
    @Retention(RetentionPolicy.SOURCE)
    public @interface ContentsFlags {}

    /**
	 * 描述當前 Parcelable 實例的對象類型,比如說,如果對象中有文件描述符,這個方法就會返回上面的 * CONTENTS_FILE_DESCRIPTOR,其他情況會返回一個位掩碼
     */
    public @ContentsFlags int describeContents();
    
    /**
     * Flatten this object in to a Parcel.
     * 將對象轉換成一個 Parcel 對象 參數中 dest 表示要寫入的 Parcel 對象
     * @param dest The Parcel in which the object should be written.
     * @param flags Additional flags about how the object should be written. 表示這個對象將如何寫入
     * May be 0 or {@link #PARCELABLE_WRITE_RETURN_VALUE}.
     */
    public void writeToParcel(Parcel dest, @WriteFlags int flags);

    /**
     * Interface that must be implemented and provided as a public CREATOR
     * field that generates instances of your Parcelable class from a Parcel.
	 * 實現類必須有一個 Creator 屬性,用於反序列化,將 Parcel 對象轉換爲 Parcelable 
     */
    public interface Creator<T> {
        /**
         * Create a new instance of the Parcelable class, instantiating it
         * from the given Parcel whose data had previously been written by
         * {@link Parcelable#writeToParcel Parcelable.writeToParcel()}.
         * 
         * @param source The Parcel to read the object's data from.
         * @return Returns a new instance of the Parcelable class.
         */
        public T createFromParcel(Parcel source);
        
        /**
         * Create a new array of the Parcelable class.
         * 
         * @param size Size of the array.
         * @return Returns an array of the Parcelable class, with every entry
         * initialized to null.
         */
        public T[] newArray(int size);
    }

    /**
     * Specialization of {@link Creator} that allows you to receive the
     * ClassLoader the object is being created in.
	 * 對象創建時提供的一個創建器
     */
    public interface ClassLoaderCreator<T> extends Creator<T> {
        /**
         * Create a new instance of the Parcelable class, instantiating it
         * from the given Parcel whose data had previously been written by
         * {@link Parcelable#writeToParcel Parcelable.writeToParcel()} and
         * using the given ClassLoader.
         * 使用類加載器和之前序列化成的 Parcel 對象反序列化一個對象
         * @param source The Parcel to read the object's data from.
         * @param loader The ClassLoader that this object is being created in.
         * @return Returns a new instance of the Parcelable class.
         */
        public T createFromParcel(Parcel source, ClassLoader loader);
    }
}
方法 說明
T createFromParcel(Parcel source) 從序列化後的Parcel對象中反序列化創建原始對象
writeToParcel(Parcel out,int flags) 將當前對象寫入Parcel中,flag取值爲0或者1
@ContentsFlags int describeContents() 返回當前對象的內容描述,若含有文件描述符,返回1;否則返回0

實現了 Parcelable 接口的類在序列化和反序列化時會被轉換爲 Parcel 類型的數據 。

3、Parcelable 序列化和反序列化的實現

實現Parcelable 接口時默認必須實現三部分:

  • 參數爲Parcel 類型的構造方法(是交由CREATOR去調用的)
  • writeToParcel方法
  • 定義CREATOR成員變量,用於反序列化
/**
 * @author : Crazy.Mo
 */
public class User implements Parcelable {
    private String name;
    private long id;
    private String card;
   
    public User(String name, long id, String card) {
        this.name = name;
        this.id = id;
        this.card = card;
    }
   
    protected User(Parcel in) {
        //順序要和write時一致
        this.name=in.readString();
        this.id=in.readLong();
        this.card=in.readString();
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeString(name);
        dest.writeLong(id);
        dest.writeString(card);
    }

    @Override
    public int describeContents() {
        return 0;
    }

    public static final Creator<User> CREATOR = new Creator<User>() {
        @Override
        public User createFromParcel(Parcel in) {
        	//從序列化對象中,獲取原始的對象
            return new User(in);
        }

        @Override
        public User[] newArray(int size) {
	        //創建指定長度的原始對象數組
            return new User[size];
        }
    };
}

3.1、實現參數爲Parcel 類型的構造方法

這個構造方法主要是交由CREATOR 調用的,本質上就是通過傳入的Parcel 對象,根據成員變量的類型,調用各自對應的readXxx方法完成成員變量的賦值,特別地若成員變量中包含其他Parcelable的對象,那麼在反序列化時需要當前線程的上下文類加載器(否則會報ClassNotFound 異常),因此在處理此類問題時候,在這個構造方法裏還必須傳入當前線程上下文類加載器到readParcelable方法中。

//獲取當前線程上下文類加載器的幾種方式:
getClass().getClassLoader();
Thread.currentThread().getContextClassLoader();
User.class.getClassLoader();

3.2、重寫writeToParcel方法

一系列的序列化操作都是經由writeToParcel方法進而調用Parcel native層的一系列對應的writeXxx方法實現的,所以這裏的實現很簡單基本上就是通過傳入的Parcel對象調用對應的writeXxx方法,特別地write的順序應與read時的順序一致,即與反序列化時read的順序一致

    private String name;
    private long id;
    private String card;
    private User(Parcel in) {
        //順序要和write時一致
        this.name=in.readString();
        this.id=in.readLong();
        this.card=in.readString();
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeString(name);
        dest.writeLong(id);
        dest.writeString(card);
    }

3.3、聲明定義用於反序列化的CREATOR成員

Android Studio 已經很智能了,基本上不用做額外的處理,這是反序列化時需要使用到的。其內部表明瞭如何創建序列化數組和對象,本質上就是調用Parcel 的readXxx方法完成的。

五、Parcelable 小結

  • 僅在使用內存序列化的時Parcelable比Serializable的性能高,Binder和Intent傳值。
  • 從一定程度來說,Parcelable的序列化和反序列化都是由用戶自己去實現的,不需要去進行邊界計算,而Serializable 是由系統(JVM)去完成序列化和反序列化的邏輯的,需要一些額外的計算(比如邊界判定等)。
  • Parcelable不宜應用於持久化存儲的需求上,持久化存儲還是選擇 Serializable
  • Parcelable不宜應用於網絡通信的需求上,因爲Parcelable 不是通用的序列化機制。
  • Serializable在序列化的時候會產生大量的臨時變量,從而引起頻繁的GC(內存回收)。
/**
 * @author : Crazy.Mo
 */
public class MyParcelable implements Parcelable {
    private User user;
    //記得初始化
    private List<String> list=new ArrayList<>(16);
    private List<User> usrs=new ArrayList<>(16);


    protected MyParcelable(Parcel in) {
        this.user=in.readParcelable(User.class.getClassLoader());
        in.readStringList(list);
        //in.readList(usrs,User.class.getClassLoader()); //對應writeList
        //對應writeTypedList
        in.readTypedList(usrs,User.CREATOR);
        //對應writeTypedList
        //usrs=in.createTypedArrayList(User.CREATOR);
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeParcelable(user,0);
        dest.writeStringList(list);
        //dest.writeList(usrs); 把類的信息和數據都寫入Parcel,以使將來能使用合適的類裝載器重新構造類的實例
        dest.writeTypedList(usrs);
    }

    @Override
    public int describeContents() {
        return 0;
    }

    public static final Creator<MyParcelable> CREATOR = new Creator<MyParcelable>() {
        @Override
        public MyParcelable createFromParcel(Parcel in) {
            return new MyParcelable(in);
        }

        @Override
        public MyParcelable[] newArray(int size) {
            return new MyParcelable[size];
        }
    };
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章