作者簡(jiǎn)介:ASCE1885浩村, 《Android 高級(jí)進(jìn)階》作者。
本文由于潛在的商業(yè)目的占哟,未經(jīng)授權(quán)不開放全文轉(zhuǎn)載許可心墅,謝謝酿矢!
本文分析的源碼版本已經(jīng) fork 到我的 Github。
數(shù)據(jù)序列化在 Android 應(yīng)用開發(fā)中占據(jù)著舉足輕重的位置怎燥,無論是進(jìn)程間通信瘫筐,本地?cái)?shù)據(jù)存儲(chǔ),網(wǎng)絡(luò)數(shù)據(jù)傳輸?shù)鹊阮硪Γ茧x不開序列化的支持策肝。針對(duì)不同場(chǎng)景選擇正確的序列化方案,對(duì)應(yīng)用的性能有著極大的影響隐绵。
廣義上講之众,序列化是將數(shù)據(jù)結(jié)構(gòu)或者對(duì)象轉(zhuǎn)換成可用于存儲(chǔ)或者傳輸?shù)臄?shù)據(jù)格式的過程,在序列化期間依许,數(shù)據(jù)結(jié)構(gòu)或者對(duì)象將其狀態(tài)信息寫入到臨時(shí)或者持久性存儲(chǔ)區(qū)中棺禾;反序列化是將序列化過程中生成的數(shù)據(jù)還原成數(shù)據(jù)結(jié)構(gòu)或者對(duì)象的過程。Android 應(yīng)用開發(fā)有很多種可選的序列化和反序列化方案峭跳,我們?cè)凇禔ndroid 高級(jí)進(jìn)階》一書的《Android 數(shù)據(jù)序列化方案研究》一節(jié)已經(jīng)做過介紹膘婶。在正式開始 Serial 相關(guān)內(nèi)容之前,這里我們先來簡(jiǎn)單回顧一下幾個(gè)跟本文主題密切相關(guān)的序列化方式蛀醉。
序列化基礎(chǔ)
Serializable
Serializable 是 Java 語言的特性悬襟,它是最簡(jiǎn)單的也是使用最廣泛的序列化方案之一,只有實(shí)現(xiàn)了 Serializable 接口的 Java 對(duì)象才可以實(shí)現(xiàn)內(nèi)建的序列化拯刁,這種類型的序列化是將 Java 對(duì)象轉(zhuǎn)換成字節(jié)序列的過程脊岳,而反序列化則是將字節(jié)序列恢復(fù)成 Java 對(duì)象的過程。
public interface Serializable {
}
Serializable 接口是一種標(biāo)識(shí)接口垛玻,也就是無需實(shí)現(xiàn)方法逸绎,Java 便會(huì)對(duì)這個(gè)對(duì)象進(jìn)行序列化操作。它的缺點(diǎn)是使用反射機(jī)制夭谤,在序列化的過程中會(huì)創(chuàng)建很多臨時(shí)對(duì)象棺牧,容易觸發(fā)垃圾回收,序列化的過程比較慢朗儒,對(duì)于性能要求很嚴(yán)格的場(chǎng)景不建議使用這種方案颊乘。
Externalizable
Externalizable 接口繼承自 Serializable 接口,并定義了 writeExternal
和 readExternal
這兩個(gè)方法醉锄,從而讓開發(fā)者對(duì)序列化過程擁有更多的控制權(quán)乏悄,方便的實(shí)現(xiàn)自定義操作,同時(shí)可以實(shí)現(xiàn)一些使用 Serializable 接口無法實(shí)現(xiàn)的功能恳不,例如實(shí)現(xiàn) Serializable 接口的對(duì)象檩小,其中 static 和 transient 類型的成員變量默認(rèn)是不會(huì)被序列化的,而通過實(shí)現(xiàn) Externalizable 接口開發(fā)者可以對(duì) static 和 transient 類型的成員變量進(jìn)行手動(dòng)序列化的烟勋。
public interface Externalizable extends java.io.Serializable {
void writeExternal(ObjectOutput out) throws IOException;
void readExternal(ObjectInput in) throws IOException, ClassNotFoundException;
}
當(dāng)然规求,Externalizable 接口的序列化機(jī)制跟 Serializable 接口一樣筐付,都是基于反射機(jī)制的,性能方面也是比較差的。
Parcelable
前面兩種序列化方式都是 Java 內(nèi)置的接口,可以將對(duì)象序列化到磁盤文件构订,數(shù)據(jù)庫,網(wǎng)絡(luò)等较解。到了 Android 平臺(tái),為了實(shí)現(xiàn) Android 應(yīng)用內(nèi)以及應(yīng)用間(基于 AIDL)高效的數(shù)據(jù)傳輸赴邻,Android 設(shè)計(jì)了 Parcelable 接口印衔,需要注意的是,Parcelable 只支持內(nèi)存方式的序列化姥敛,不能將對(duì)象序列化到磁盤等介質(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)用的流暢度是影響用戶體驗(yàn)的關(guān)鍵性能指標(biāo)之一金顿,沒有用戶會(huì)喜歡使用起來卡頓的應(yīng)用臊泌。具體到 Twitter 的 Android 端應(yīng)用,經(jīng)過性能剖析揍拆,他們的開發(fā)者發(fā)現(xiàn)渠概,使用標(biāo)準(zhǔn)的 Android Externalizable 接口實(shí)現(xiàn)的 Java 對(duì)象和數(shù)據(jù)庫之間的序列化和反序列化所花的時(shí)間占 UI 線程時(shí)間的 15% 左右,這使得 Twitter 時(shí)間軸滾動(dòng)的流暢度受到對(duì)象序列化的嚴(yán)重影響∩┧現(xiàn)有的對(duì)象序列化開源庫對(duì)對(duì)象的迭代式修改提供的支持有限播揪,任何可能破壞對(duì)象序列化的修改都會(huì)引入難以定位的 bugs,修復(fù)起來也非常困難筒狠,除非清除數(shù)據(jù)庫數(shù)據(jù)猪狈。因此,Twitter 研發(fā)并于近期推出了 Serial辩恼,一個(gè)開源的基于 Java 平臺(tái)的輕量級(jí)對(duì)象序列化框架(同時(shí)支持 Android 平臺(tái))雇庙。
Serial 解決了傳統(tǒng) Android 序列化函數(shù)庫的以下四大痛點(diǎn):
- 性能:緩慢的序列化直接影響用戶體驗(yàn)
- 可調(diào)試性:當(dāng)序列化后的數(shù)據(jù)中存在 bugs 時(shí),調(diào)試信息不足以定位問題的原因
- 向后兼容性:在不完全清除序列化后數(shù)據(jù)的前提下灶伊,Android 提供的序列化函數(shù)庫幾乎不支持對(duì)序列化對(duì)象進(jìn)行修改疆前,這使得對(duì)象屬性的迭代升級(jí)變得困難
- 靈活性:序列化函數(shù)庫的使用應(yīng)該對(duì)現(xiàn)有代碼和模型類的數(shù)據(jù)結(jié)構(gòu)侵入性小
雖然現(xiàn)有的一些 Java 序列化函數(shù)庫像 Kryo 和 Flatbuffer 嘗試解決上面一些痛點(diǎn),但這些函數(shù)庫重點(diǎn)在對(duì)性能和向后兼容性的支持上聘萨,通常忽略了對(duì)可調(diào)試性和靈活性的支持竹椒,例如為了在項(xiàng)目中使用這些函數(shù)庫,通常需要對(duì)現(xiàn)有代碼庫作較大的改動(dòng)米辐。
使用 Java 的 Externalizable 類實(shí)現(xiàn)序列化時(shí)胸完,類相關(guān)信息例如類名和包名书释,會(huì)被添加進(jìn)序列化后生成的二進(jìn)制數(shù)據(jù)中。這樣舶吗,Java 平臺(tái)才能根據(jù)這些信息在反序列化時(shí)通過反射機(jī)制還原這個(gè)對(duì)象征冷,但同時(shí)這是一個(gè)非常耗時(shí)的操作。為了解決這個(gè)問題誓琼,Serial 通過規(guī)范開發(fā)者為每個(gè)需要序列化的對(duì)象實(shí)現(xiàn) Serializer
內(nèi)部類检激,并明確列舉哪些屬性需要序列化和反序列化,從而摒棄了反射機(jī)制和動(dòng)態(tài)查找的使用腹侣。
使用 Serial 對(duì)一個(gè)大對(duì)象進(jìn)行序列化和反序列化測(cè)試叔收,和 Externalizable 方式相比,初步的性能指標(biāo)如下所示:
- 序列化速度提升 5 倍傲隶,反序列化速度提升 2.5 倍
- 序列化后生成的數(shù)據(jù)字節(jié)大小約等于原來的 1/5
Serial 的基本用法
Serial 的使用很簡(jiǎn)單饺律,首先我們?yōu)槊總€(gè)需要序列化的類定義一個(gè) Serializer,它通常是以靜態(tài)內(nèi)部類的形式存在跺株,并通過定義名為 SERIALIZER 的靜態(tài)變量來訪問它复濒。Serializer 要求開發(fā)者顯式的對(duì)序列化類中的屬性進(jìn)行讀寫,如果是原始數(shù)據(jù)類型乒省,那么直接讀寫即可巧颈,如果是對(duì)象類型則遞歸調(diào)用該對(duì)象中定義的 Serializer 來讀寫。Serializer 在讀寫對(duì)象類型和字符串類型時(shí)能自動(dòng)處理 null 對(duì)象袖扛。下面的例子中砸泛,我們?cè)?ExampleObject 類中定義了一個(gè)繼承自 ObjectSerializer 的 Serializer 內(nèi)部類,并重寫 serializeObject 和 deserializeObject 方法分別實(shí)現(xiàn)自定義的序列化和反序列化蛆封。
public static class ExampleObject {
// 序列化時(shí)會(huì)使用到這個(gè)靜態(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);
}
}
}
上面這個(gè)例子 ExampleObject 是通過構(gòu)造方法初始化的唇礁,但有的時(shí)候,我們會(huì)使用 Builder 模式來實(shí)例化一個(gè)類(詳情可以參見《Android 高級(jí)進(jìn)階》一書的《Builder 模式詳解》一節(jié))惨篱;又或者在 Serial 中要實(shí)現(xiàn)對(duì)象的迭代式修改盏筐,這時(shí)候我們的 Serializer 類就不能繼承 ObjectSerializer,而應(yīng)該繼承 BuilderSerializer 類砸讳,并重寫 createBuilder机断,serializeObject 和 deserializeToBuilder 這三個(gè)方法。
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));
}
}
}