一文看懂Android中的序列化

序列化,剛入門的開發者可能從字面上看不出這個到底是啥意思,這比較妨礙我們的理解。所以我們需要銘記其定義和作用。
定義:

  • Java序列化就是指把Java對象轉換爲字節序列的過程
  • Java反序列化就是指把字節序列恢復爲Java對象的過程。

作用:

  • 對象轉換爲有序字節流,以便在網絡上傳輸或者保存在本地文件中。
  • 通過序列化可以在進程間傳遞對象。
    從序列化的作用中我們可以看到字節流有利於網絡傳輸或者存儲文件,或者進行進程間通信傳遞對象。實際上,進程間傳遞對象,也可以理解爲序列化的對象保存在了系統的內存中,然後傳給另一個對象。由於不同的進程,擁有不同的jvm,所以才需要進行進程間通信,然而,序列化後的對象由於其可用存儲爲文件或者存在內存,所以可用與進程間通信。
    圖1 序列化和反序列化過程 圖1 序列化和反序列化過程

Android中總共有兩種序列化方式,一個是Java提供的序列化接口Serializable,一個是Android提供的新的序列化方式Parcelable。

Serializable

首先,Java提供的Serialization接口是個空接口,如果一個對象需要序列化,可以實現Serialization接口,然後聲明一個SerialVersionUID。

package java.io;

public interface Serializable {
}

因爲是空接口,所以非常簡單,看起來什麼都不用做,實際上是系統幫助我們做了序列化的工作,相當於我這個對象標記爲Serialization,系統JVM就知道這個類需要序列化。

public class Person implements Serializable {
    private static final long serialVersionUID = 1234567L;

    private int age;
    private String name;
    private boolean isMale;

然後,序列化和反序列化的代碼爲

//序列化
ObjectOutputStream os = new ObjectOutputStream(new FileOutputStream(new File("person.txt")));
        os.writeObject(person);
        os.close();
//反序列化
 ObjectInputStream in = new ObjectInputStream(new FileInputStream(new File("person.txt")));
        Person person = (Person) in.readObject();
        return person;

此過程可以從上圖1看出來,重建的對象A’,和A的內容完全一致,但是是兩個不同的對象,這和進程間通信一樣的現象,系統會通過渠道根據信息新建一個對象,但是進程間的壁壘還是在的,不是直接訪問另一個進程。
另外serialVersionUID 的作用是,輔助序列化和反序列化過程。可以使用此字段也可以不用,不用時,系統會根據當前對象生成其hash值,但是如果保存的文件或者網絡傳輸時,一端更改了字段值,就不能成功的反序列化,會報錯java.io.InvalidClassException: local class incompatible。而如果指定了這個UID值,比如1L,則兩端的這個UID至少是相同的,即使發生了部分字段更改,系統還是會盡最大努力去恢復數據。但是如果改動過大,比如更改了類名、成員變量類型,則還是會反序列化失敗。

Parcelable

Parcelable爲Android推薦的序列化方法,它是一個接口,和Serialization不同的是,Parcelable不是空接口。這裏創建一個demo,代碼如下:

public class Person implements Parcelable {
    private int age;
    private String name;
    private boolean isMale;
	//從序列化的數據創建原始對象
    protected Person(Parcel in) {
        age = in.readInt();
        name = in.readString();
        isMale = in.readByte() != 0;
    }

    public static final Creator<Person> CREATOR = new Creator<Person>() {
    	//從序列化對象中創建原始對象
        @Override
        public Person createFromParcel(Parcel in) {
            return new Person(in);
        }
		//創建指定長度的原始對象數組
        @Override
        public Person[] newArray(int size) {
            return new Person[size];
        }
    };
	//返回當前對象的內容描述
    @Override
    public int describeContents() {
        return 0;
    }
	//將當前對象寫入數據化結構parcel中,flag是一般爲0|
    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeInt(age);
        dest.writeString(name);
        dest.writeByte((byte) (isMale ? 1 : 0));
    }
}

其中writeToParcel和describeContents是Parcelable的接口函數,是必須要實現。然後還有個內部接口類,用以重建對象。從其註釋我們可以看到,從Parcel中獲取之前writeToParcel的數據,用classloader新建這個Parcelable 實例。
其中Parcel表意是打包的意思,其實就是包裝了我們需要傳輸的數據,然後在Binder中傳輸,用於跨進程傳輸數據。
Parcel提供了一套機制,可以將序列化之後的數據寫入一個共享內存中,其他進程通過Parcel可以從這塊共享內存讀出字節流,並反序列化成對象

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.
         *
         * @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);
    }

由於Parcelable是跨進程通信常用的序列化方式,我們可以從多進程的另一個角度看這個過程。
Parcelable 對象的跨進程通信圖2 Parcelable 對象的跨進程通信
同樣的,重建的對象Person’只是和Person內容相同但是不是同一個對象。

系統中的序列化對象

其實,Parcelable在系統中非常常用,只是由於我們很少分析類的源碼,看到的都是他的另一個樣子。
就比如Intent類,其作用是一個將要執行的動作的抽象的描述,一般來說是作爲參數來使用,由 Intent來協助完成 Android各個組件之間的通訊:

  1. 啓動Activity

通過Context.startActvity();

  1. 啓動Service

通過Context.startService()啓動一個服務,或者通過Context.bindService()和後臺服務交互;

  1. 發送Broadcast

通過廣播方法Context.sendBroadcasts() / /發給Broadcast Receivers

查看其類源碼,可以看出其也是實現了Parcelable的接口,由於剛纔的啓動Activity, service,發送廣播,都可能是跨進程通信,很多數據都是跨進程傳輸的。

public class Intent implements Parcelable, Cloneable {

另外,由於序列化的對象傳輸,其成員函數也需要是序列化的對象,Android開發都知道intent也常包裝Bundle數據。可以看出Bundle也是實現了序列化的接口的。

public final class Bundle extends BaseBundle implements Cloneable, Parcelable

其他的如ArrayList,HashMap等也是實現了序列化接口的,我們可以慢慢去發現。

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
public class HashMap<K,V> extends AbstractMap<K,V>
    implements Map<K,V>, Cloneable, Serializable {

兩種序列化方式優缺點

Serializable是Java提供的序列化接口,使用簡單,但是開銷很大,序列化和反序列化過程需要大量的I/O操作。
Parcelable是Android推薦的序列化方式,效率很高,缺點是使用麻煩。
所以根據二者特點,Serializable一般使用在存儲到設備和網絡傳輸場景,後者雖然也能完成,但是很複雜。Parcelable主要用在內存序列化中,比如進程間通信。

文章到這裏就結束了,相信大家通過源碼和圖表能對序列化有個較深的瞭解, 也對進程間通信有了一定的認識,喜歡的點個贊。下一篇打算寫下Android進程間通信之Binder解析,需要了解的可以關注期待。

參考文獻:
《Android開發藝術探索》

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章