作者簡介:ASCE1885, 《Android 高級進階》作者。
本文由于潛在的商業(yè)目的,未經(jīng)授權(quán)不開放全文轉(zhuǎn)載許可,謝謝!
本文分析的源碼版本已經(jīng) fork 到我的 Github。

數(shù)據(jù)序列化在 Android 應(yīng)用開發(fā)中占據(jù)著舉足輕重的位置,無論是進程間通信,本地數(shù)據(jù)存儲,網(wǎng)絡(luò)數(shù)據(jù)傳輸?shù)鹊?,都離不開序列化的支持。針對不同場景選擇正確的序列化方案,對應(yīng)用的性能有著極大的影響。
廣義上講,序列化是將數(shù)據(jù)結(jié)構(gòu)或者對象轉(zhuǎn)換成可用于存儲或者傳輸?shù)臄?shù)據(jù)格式的過程,在序列化期間,數(shù)據(jù)結(jié)構(gòu)或者對象將其狀態(tài)信息寫入到臨時或者持久性存儲區(qū)中;反序列化是將序列化過程中生成的數(shù)據(jù)還原成數(shù)據(jù)結(jié)構(gòu)或者對象的過程。Android 應(yīng)用開發(fā)有很多種可選的序列化和反序列化方案,我們在《Android 高級進階》一書的《Android 數(shù)據(jù)序列化方案研究》一節(jié)已經(jīng)做過介紹。在正式開始 Serial 相關(guān)內(nèi)容之前,這里我們先來簡單回顧一下幾個跟本文主題密切相關(guān)的序列化方式。
序列化基礎(chǔ)
Serializable
Serializable 是 Java 語言的特性,它是最簡單的也是使用最廣泛的序列化方案之一,只有實現(xiàn)了 Serializable 接口的 Java 對象才可以實現(xiàn)內(nèi)建的序列化,這種類型的序列化是將 Java 對象轉(zhuǎn)換成字節(jié)序列的過程,而反序列化則是將字節(jié)序列恢復(fù)成 Java 對象的過程。
public interface Serializable {
}
Serializable 接口是一種標識接口,也就是無需實現(xiàn)方法,Java 便會對這個對象進行序列化操作。它的缺點是使用反射機制,在序列化的過程中會創(chuàng)建很多臨時對象,容易觸發(fā)垃圾回收,序列化的過程比較慢,對于性能要求很嚴格的場景不建議使用這種方案。
Externalizable
Externalizable 接口繼承自 Serializable 接口,并定義了 writeExternal 和 readExternal 這兩個方法,從而讓開發(fā)者對序列化過程擁有更多的控制權(quán),方便的實現(xiàn)自定義操作,同時可以實現(xiàn)一些使用 Serializable 接口無法實現(xiàn)的功能,例如實現(xiàn) Serializable 接口的對象,其中 static 和 transient 類型的成員變量默認是不會被序列化的,而通過實現(xiàn) Externalizable 接口開發(fā)者可以對 static 和 transient 類型的成員變量進行手動序列化的。
public interface Externalizable extends java.io.Serializable {
void writeExternal(ObjectOutput out) throws IOException;
void readExternal(ObjectInput in) throws IOException, ClassNotFoundException;
}
當(dāng)然,Externalizable 接口的序列化機制跟 Serializable 接口一樣,都是基于反射機制的,性能方面也是比較差的。
Parcelable
前面兩種序列化方式都是 Java 內(nèi)置的接口,可以將對象序列化到磁盤文件,數(shù)據(jù)庫,網(wǎng)絡(luò)等。到了 Android 平臺,為了實現(xiàn) Android 應(yīng)用內(nèi)以及應(yīng)用間(基于 AIDL)高效的數(shù)據(jù)傳輸,Android 設(shè)計了 Parcelable 接口,需要注意的是,Parcelable 只支持內(nèi)存方式的序列化,不能將對象序列化到磁盤等介質(zhì)。
public interface Parcelable {
public static final int PARCELABLE_WRITE_RETURN_VALUE = 0x0001;
public static final int CONTENTS_FILE_DESCRIPTOR = 0x0001;
public int describeContents();
public void writeToParcel(Parcel dest, int flags);
public interface Creator<T> {
public T createFromParcel(Parcel source);
public T[] newArray(int size);
}
public interface ClassLoaderCreator<T> extends Creator<T> {
public T createFromParcel(Parcel source, ClassLoader loader);
}
}
因此,Serial 跟 Serializable(Externalizable)的關(guān)鍵區(qū)別是性能,跟 Parcelable 的關(guān)鍵區(qū)別是能夠序列化到的介質(zhì)。
Serial 的特性
Android 應(yīng)用的流暢度是影響用戶體驗的關(guān)鍵性能指標之一,沒有用戶會喜歡使用起來卡頓的應(yīng)用。具體到 Twitter 的 Android 端應(yīng)用,經(jīng)過性能剖析,他們的開發(fā)者發(fā)現(xiàn),使用標準的 Android Externalizable 接口實現(xiàn)的 Java 對象和數(shù)據(jù)庫之間的序列化和反序列化所花的時間占 UI 線程時間的 15% 左右,這使得 Twitter 時間軸滾動的流暢度受到對象序列化的嚴重影響。現(xiàn)有的對象序列化開源庫對對象的迭代式修改提供的支持有限,任何可能破壞對象序列化的修改都會引入難以定位的 bugs,修復(fù)起來也非常困難,除非清除數(shù)據(jù)庫數(shù)據(jù)。因此,Twitter 研發(fā)并于近期推出了 Serial,一個開源的基于 Java 平臺的輕量級對象序列化框架(同時支持 Android 平臺)。
Serial 解決了傳統(tǒng) Android 序列化函數(shù)庫的以下四大痛點:
- 性能:緩慢的序列化直接影響用戶體驗
- 可調(diào)試性:當(dāng)序列化后的數(shù)據(jù)中存在 bugs 時,調(diào)試信息不足以定位問題的原因
- 向后兼容性:在不完全清除序列化后數(shù)據(jù)的前提下,Android 提供的序列化函數(shù)庫幾乎不支持對序列化對象進行修改,這使得對象屬性的迭代升級變得困難
- 靈活性:序列化函數(shù)庫的使用應(yīng)該對現(xiàn)有代碼和模型類的數(shù)據(jù)結(jié)構(gòu)侵入性小
雖然現(xiàn)有的一些 Java 序列化函數(shù)庫像 Kryo 和 Flatbuffer 嘗試解決上面一些痛點,但這些函數(shù)庫重點在對性能和向后兼容性的支持上,通常忽略了對可調(diào)試性和靈活性的支持,例如為了在項目中使用這些函數(shù)庫,通常需要對現(xiàn)有代碼庫作較大的改動。
使用 Java 的 Externalizable 類實現(xiàn)序列化時,類相關(guān)信息例如類名和包名,會被添加進序列化后生成的二進制數(shù)據(jù)中。這樣,Java 平臺才能根據(jù)這些信息在反序列化時通過反射機制還原這個對象,但同時這是一個非常耗時的操作。為了解決這個問題,Serial 通過規(guī)范開發(fā)者為每個需要序列化的對象實現(xiàn) Serializer 內(nèi)部類,并明確列舉哪些屬性需要序列化和反序列化,從而摒棄了反射機制和動態(tài)查找的使用。
使用 Serial 對一個大對象進行序列化和反序列化測試,和 Externalizable 方式相比,初步的性能指標如下所示:
- 序列化速度提升 5 倍,反序列化速度提升 2.5 倍
- 序列化后生成的數(shù)據(jù)字節(jié)大小約等于原來的 1/5
Serial 的基本用法
Serial 的使用很簡單,首先我們?yōu)槊總€需要序列化的類定義一個 Serializer,它通常是以靜態(tài)內(nèi)部類的形式存在,并通過定義名為 SERIALIZER 的靜態(tài)變量來訪問它。Serializer 要求開發(fā)者顯式的對序列化類中的屬性進行讀寫,如果是原始數(shù)據(jù)類型,那么直接讀寫即可,如果是對象類型則遞歸調(diào)用該對象中定義的 Serializer 來讀寫。Serializer 在讀寫對象類型和字符串類型時能自動處理 null 對象。下面的例子中,我們在 ExampleObject 類中定義了一個繼承自 ObjectSerializer 的 Serializer 內(nèi)部類,并重寫 serializeObject 和 deserializeObject 方法分別實現(xiàn)自定義的序列化和反序列化。
public static class ExampleObject {
// 序列化時會使用到這個靜態(tài)變量
public static final ObjectSerializer<ExampleObject> SERIALIZER = new ExampleObjectSerializer();
public final int num;
public final SubObject obj;
public ExampleObject(int num, @NotNull SubObject obj) {
this.num = num;
this.obj = obj;
}
...
private static final ExampleObjectSerializer extends ObjectSerializer<ExampleObject> {
// 自定義序列化操作
@Override
protected void serializeObject(@NotNull SerializerOutput output,
@NotNull ExampleObject object) throws IOException {
output.writeInt(object.num)
.writeObject(object.obj, SubObject.SERIALIZER);
}
// 自定義反序列化操作
@Override
@NotNull
protected ExampleObject deserializeObject(@NotNull SerializerInput input,
int versionNumber) throws IOException, ClassNotFoundException {
final int num = input.readInt();
final SubObject obj = input.readObject(SubObject.SERIALIZER);
return new ExampleObject(num, obj);
}
}
}
上面這個例子 ExampleObject 是通過構(gòu)造方法初始化的,但有的時候,我們會使用 Builder 模式來實例化一個類(詳情可以參見《Android 高級進階》一書的《Builder 模式詳解》一節(jié));又或者在 Serial 中要實現(xiàn)對象的迭代式修改,這時候我們的 Serializer 類就不能繼承 ObjectSerializer,而應(yīng)該繼承 BuilderSerializer 類,并重寫 createBuilder,serializeObject 和 deserializeToBuilder 這三個方法。
public static class ExampleObject {
...
public ExampleObject(@NotNull Builder builder) {
this.num = builder.mNum;
this.obj = builder.mObj;
}
...
public static Builder extends ModelBuilder<ExampleObject> {
...
}
private static final ExampleObjectSerializer extends BuilderSerializer<ExampleObject, Builder> {
@Override
@NotNull
protected Builder createBuilder() {
return new Builder();
}
@Override
protected void serializeObject(@NotNull SerializerOutput output,
@NotNull ExampleObject object) throws IOException {
output.writeInt(object.num)
.writeObject(object.obj, SubObject.SERIALIZER);
}
@Override
protected void deserializeToBuilder(@NotNull SerializerInput input,
@NotNull Builder builder, int versionNumber) throws IOException, ClassNotFoundException {
builder.setNum(input.readInt())
.setObj(input.readObject(SubObject.SERIALIZER));
}
}
}