05 | Android 高級(jí)進(jìn)階(源碼剖析篇) Twitter 的高性能序列化框架 Serial(一)

作者簡(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 接口,并定義了 writeExternalreadExternal 這兩個(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ù)庫像 KryoFlatbuffer 嘗試解決上面一些痛點(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));
        }
    }
}
還有 42% 的精彩內(nèi)容
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
支付 ¥5.20 繼續(xù)閱讀
  • 序言:七十年代末绣夺,一起剝皮案震驚了整個(gè)濱河市吏奸,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌陶耍,老刑警劉巖奋蔚,帶你破解...
    沈念sama閱讀 218,755評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡泊碑,警方通過查閱死者的電腦和手機(jī)坤按,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來馒过,“玉大人臭脓,你說我怎么就攤上這事「购觯” “怎么了来累?”我有些...
    開封第一講書人閱讀 165,138評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)窘奏。 經(jīng)常有香客問我嘹锁,道長(zhǎng),這世上最難降的妖魔是什么着裹? 我笑而不...
    開封第一講書人閱讀 58,791評(píng)論 1 295
  • 正文 為了忘掉前任领猾,我火速辦了婚禮,結(jié)果婚禮上骇扇,老公的妹妹穿的比我還像新娘摔竿。我一直安慰自己,他們只是感情好少孝,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,794評(píng)論 6 392
  • 文/花漫 我一把揭開白布继低。 她就那樣靜靜地躺著,像睡著了一般韭山。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上冷溃,一...
    開封第一講書人閱讀 51,631評(píng)論 1 305
  • 那天钱磅,我揣著相機(jī)與錄音,去河邊找鬼似枕。 笑死盖淡,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的凿歼。 我是一名探鬼主播褪迟,決...
    沈念sama閱讀 40,362評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼答憔!你這毒婦竟也來了味赃?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,264評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤虐拓,失蹤者是張志新(化名)和其女友劉穎心俗,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,724評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡城榛,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評(píng)論 3 336
  • 正文 我和宋清朗相戀三年揪利,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片狠持。...
    茶點(diǎn)故事閱讀 40,040評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡疟位,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出喘垂,到底是詐尸還是另有隱情甜刻,我是刑警寧澤,帶...
    沈念sama閱讀 35,742評(píng)論 5 346
  • 正文 年R本政府宣布王污,位于F島的核電站罢吃,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏昭齐。R本人自食惡果不足惜尿招,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,364評(píng)論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望阱驾。 院中可真熱鬧就谜,春花似錦、人聲如沸里覆。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽喧枷。三九已至虹统,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間隧甚,已是汗流浹背车荔。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評(píng)論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留戚扳,地道東北人忧便。 一個(gè)月前我還...
    沈念sama閱讀 48,247評(píng)論 3 371
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像帽借,于是被迫代替她去往敵國和親珠增。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,979評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容

  • JAVA序列化機(jī)制的深入研究 對(duì)象序列化的最主要的用處就是在傳遞,和保存對(duì)象(object)的時(shí)候,保證對(duì)象的完整...
    時(shí)待吾閱讀 10,868評(píng)論 0 24
  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,163評(píng)論 25 707
  • 第一章-喚醒內(nèi)在的自己: 喚醒砍艾,這是行動(dòng)教練需要掌握的一個(gè)重要技能蒂教。也是開展教練活動(dòng)的基礎(chǔ),有句話說道“最難喚醒的...
    宋洋SongYang閱讀 194評(píng)論 0 0
  • 1感恩: 感恩自己今天如約打卡脆荷,例行約定悴品。 感恩今天依然是好天氣,看到冬日暖陽,心情也很舒暢苔严。 感恩公公今天就提前...
    遇見念念相續(xù)閱讀 117評(píng)論 0 0
  • 孩子星期二好好的去上學(xué)岖妄,回來一身包,一直抓寂祥。第一時(shí)間我問孩子怎么回事荐虐,老師知道嗎?孩子說知道丸凭,有跟老師說福扬,老...
    施麗萍閱讀 299評(píng)論 0 0