Android 對象序列化之追求完美的 Serial

閃存
Android 存儲優(yōu)化系列專題
  • SharedPreferences 系列

Android 之不要濫用 SharedPreferences
Android 之不要濫用 SharedPreferences(2)— 數(shù)據(jù)丟失

  • ContentProvider 系列(待更)

《Android 存儲選項之 ContentProvider 啟動過程源碼分析》
《Android 存儲選項之 ContentProvider 深入分析》

  • 對象序列化系列

Android 對象序列化之你不知道的 Serializable
Android 對象序列化之 Parcelable 取代 Serializable ?
Android 對象序列化之追求性能完美的 Serial

  • 數(shù)據(jù)序列化系列(待更)

《Android 數(shù)據(jù)序列化之 JSON》
《Android 數(shù)據(jù)序列化之 Protocol Buffer 使用》
《Android 數(shù)據(jù)序列化之 Protocol Buffer 源碼分析》

  • SQLite 存儲系列

Android 存儲選項之 SQLiteDatabase 創(chuàng)建過程源碼分析
Android 存儲選項之 SQLiteDatabase 源碼分析
數(shù)據(jù)庫連接池 SQLiteConnectionPool 源碼分析
SQLiteDatabase 啟用事務(wù)源碼分析
SQLite 數(shù)據(jù)庫 WAL 模式工作原理簡介
SQLite 數(shù)據(jù)庫鎖機(jī)制與事務(wù)簡介
SQLite 數(shù)據(jù)庫優(yōu)化那些事兒


前言

對象序列化系列葡幸,主要內(nèi)容包括:Java 原生提供的 Serializable ,更加適合 Android 平臺輕量且高效的 Parcelable拓巧,以及追求性能完美的 Serial。該系列內(nèi)容主要結(jié)合源碼的角度分析它們各自的優(yōu)缺點以及合適的使用場景。

對象序列化系列赤兴,已經(jīng)分別為大家介紹了實現(xiàn)對象序列化的兩種不同方案:《Android 對象序列化之你不知道的 Serializable》和《Android 對象序列化之 Parcelable 取代 Serializable ?》横漏。

先來回顧下它們各自都有哪些優(yōu)缺點:

  1. Serializable

它是 Java 原生提供的序列化機(jī)制抠艾,在 Android 中也被廣泛使用≌锵兀可以通過它實現(xiàn)對象持久化存儲榜苫,也可以通過 Bundle 傳遞 Serializable 的序列化數(shù)據(jù)。

Serializable 的原理是通過 ObjectInputStream 和 ObjectOutputStream 來實現(xiàn)翎冲,整個序列化過程使用了大量的反射和臨時變量,而且還需要遞歸序列化對象引用的其它對象媳荒】购罚可以說整個過程計算非常復(fù)雜,而且因為存在大量反射和 GC 的影響钳枕,序列化的性能會比較差缴渊。雖然 Serializable 性能那么差,但是它也有一些進(jìn)階的使用技巧鱼炒,如序列化加密衔沼、版本替換等。

  1. Parcelable

由于 Java 的 Serializable 的性能較低昔瞧,Android 需要重新設(shè)計一套更加輕量且高效的對象序列化和反序列化機(jī)制指蚁。Parcelable 正是這個背景下產(chǎn)生的,它的核心作用就是為了解決 Android 中大量跨進(jìn)程通信的性能問題自晰。

另外你可以發(fā)現(xiàn) Parcelable 序列化和 Java 的 Serializable 序列化差別還是比較大的凝化,Parcelable 只會在內(nèi)存中進(jìn)行序列化操作,并不會將數(shù)據(jù)存儲到磁盤里酬荞。

在時間開銷和使用成本的權(quán)衡上搓劫,Parcelable 與 Serializable 的選擇截然不同,Serializable 更注重使用成本混巧,而 Parcalable 機(jī)制選擇的是性能優(yōu)先枪向。說道這里,如果你對它們還不熟悉的話咧党,可以先去參考下秘蛔。

那有沒有更好的方案來解決 Serializalbe 和 Parcleable 的痛點呢?今天我們就來了解一個開源且高性能的序列化方案凿傅。

Serial

作為程序員冤荆,我們肯定會追求完美。那有沒有性能更好的方案并且可以解決這些痛點呢柱查?

事實上辱志,關(guān)于序列化基本每個大公司都會有自己自研的一套方案,這里推薦 Twitter 開源的高性能序列化方案 Serial箱残。

Serial 力求幫助開發(fā)者實現(xiàn)高性能和高可控的序列化過程滔迈。這個序列化框架提供了一個名叫 Serializers 序列化器止吁,開發(fā)者可以通過它來明確地定義對象的序列化過程。

Serial 集成

從 Maven Central 上下載最新的依賴

repositories {
    mavenCentral()
}

dependencies {
    compile 'com.twitter.serial:serial:0.1.6'
}
Serial 方案的主要優(yōu)點如下:
  1. 相比起傳統(tǒng)的反射序列化方案更加高效(沒有使用反射)
  • 性能相比傳統(tǒng)方案提升了 3 倍(序列化的速度提升了 5 倍燎悍,反序列化提升了 2.5 倍)
  • 序列化生成的數(shù)據(jù)量(byte[])大約是之前的 1/5
  1. 開發(fā)者對于序列化過程的控制較強(qiáng)敬惦,可定義哪些 Object,F(xiàn)ield 需要被序列化
  2. 有很強(qiáng)的 debug 能力谈山,可以調(diào)試序列化的過程

那它是否真的是高性能呢俄删?我們可以將它和前面的兩套方案做一個對比測試。

... 序列化時間/ms 反序列化時間/ms 文件大小/Byte
Serializable 162 79 427932
Parcelable 61 30 755564
Serial 57 18 357528
Serial 使用

Serializer 是本庫中的關(guān)鍵類奏路,這個類提供了序列化和反序列化的能力畴椰,序列化的定義和流程都是通過它來實現(xiàn)的。

目前庫中默認(rèn)提供的序列化實現(xiàn)類是 ByteBufferSerial鸽粉,它的產(chǎn)物是 byte[]斜脂。使用者也可以自行更換實現(xiàn)類,不用拘泥于 byte[]触机。

定義 Serializer
  • 于之前的實現(xiàn) Serializable 接口不同帚戳,這里需要給每個被序列化的對象單獨定義一個 Serializer。
  • Serializers 中需要給每個 field 明確定義 write 和 read 操作儡首,對于有繼承關(guān)系的序列化類片任,需要被遞歸的進(jìn)行定義
  • Serializers 已經(jīng)為使用者處理了空對象問題,就像 read/writeString 一樣椒舵,記住不要使用原始的方法
  • Serializers 是無狀態(tài)的蚂踊,所以我們可以將其寫為 Object 的內(nèi)部類,并通過 SERIALIZER 作為名稱來訪問它笔宿。

對于大多數(shù)類犁钟,你可以建立一個繼承自 ObjectSerializer 的子類,然后實現(xiàn) serializeObject 方法和 deserializeObject 方法:

public static class ExampleObject {

    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 class ExampleObjectSerializer extends ObjectSerializer<ExampleObject> {
        @Override
        protected void serializeObject(@NotNull SerializationContext context, @NotNull SerializerOutput output,
                @NotNull ExampleObject object) throws IOException {
            output
                .writeInt(object.num) // first field
                .writeObject(object.obj, SubObject.SERIALIZER); // second field
        }

        @Override
        @NotNull
        protected ExampleObject deserializeObject(@NotNull SerializationContext context, @NotNull SerializerInput input,
                int versionNumber) throws IOException, ClassNotFoundException {
            final int num = input.readInt(); // first field
            final SubObject obj = input.readObject(SubObject.SERIALIZER); // second field
            return new ExampleObject(num, obj);
        }
    }
}

將 ExampleObject 對象序列化為 byte[]

final Serial serial = new ByteBufferSerial();
final byte[] serializedData = serial.toByteArray(object, ExampleObject.SERIALIZER)

將對象從 byte[] 反序列化為 Object

final ExampleObject object = serial.fromByteArray(serializedData, ExampleObject.SERIALIZER)

在 ExampleObject 中內(nèi)部類和 Parcelable.Creator 極為相似泼橘,都是按照順序?qū)ψ兞窟M(jìn)行讀寫操作涝动。接下來我們就從源碼的角度分析下 Serial 實現(xiàn)序列化的原理。在分析的過程中我們希望能夠更多的與 Android 系統(tǒng)提供的 Parcelable 做對比炬灭,看下 Serial 實現(xiàn)更加高效序列化機(jī)制都做了哪些優(yōu)化醋粟?

源碼分析

Serial 序列化過程分析

從 ByteBufferSerial 的 toByteArray 方法開始:

serial.toByteArray(object, ExampleObject.SERIALIZER)

將 Objecdt 轉(zhuǎn)換成 byte[] ,ByteBufferSerial 的 toByteArray 方法:

@Override
@NotNull
public <T> byte[] toByteArray(@Nullable T value, @NotNull Serializer<T> serializer)
        throws IOException {
    if (value == null) {
        //空字節(jié)數(shù)組
        return InternalSerialUtils.EMPTY_BYTE_ARRAY;
    }
    final Pools.SynchronizedPool<byte[]> currentPool = mBufferPool;
    //獲取復(fù)用的byte[]
    final byte[] tempBuffer = currentPool != null ? currentPool.acquire() : null;
    //默認(rèn)mBufferPool為null
    if (tempBuffer != null) {
        try {
            synchronized (tempBuffer) {
                return toByteArray(value, serializer, tempBuffer);
            }
        } finally {
            //回收該byte[]
            currentPool.release(tempBuffer);
        }
    }
    //默認(rèn)mBufferPool為null重归,此時走這里
    return toByteArray(value, serializer, null);
}

ByteBufferSerial 內(nèi)部可以指定一個 byte[] 的復(fù)用池 Pools.SynchronizedPool米愿。有助于減少頻繁內(nèi)存空間申請可能帶來的問題。

繼續(xù)向下分析鼻吮,實際調(diào)用了 toByteArray 的重載方法:

@NotNull
public <T> byte[] toByteArray(@Nullable T value, @NotNull Serializer<T> serializer,
        @Nullable byte[] tempBuffer) throws IOException {
    if (value == null) {
        //空字節(jié)數(shù)組
        return InternalSerialUtils.EMPTY_BYTE_ARRAY;
    }
    //真正開始完成序列化的是ByteBufferSerializerOutput
    final ByteBufferSerializerOutput serializerOutput = new ByteBufferSerializerOutput(tempBuffer);
    try {
        //將ByteBufferSerializerOutput作為參數(shù),
        //此時回到ExampleObjectSerializer的serializeObject方法
        serializer.serialize(mContext, serializerOutput, value);
    } catch (IOException e) {
        throw e;
    }
    //返回序列化后的字節(jié)數(shù)組
    //ByteBufferSerializerOutput內(nèi)部是通過ByteBuffer完成序列化
    return serializerOutput.getSerializedData();
}

注意參數(shù) serializer 就是在上面例子 ExampleObject 中定義的 ExampleObjectSerializer 實例育苟,然后創(chuàng)建 ByteBufferSerializerOutput 實例作為 serialize 方法的參數(shù):

try {
    //將ByteBufferSerializerOutput作為參數(shù),
    //此時回到ExampleObjectSerializer的serializeObject方法
    serializer.serialize(mContext, serializerOutput, value);
} catch (IOException e) {
    throw e;
}

ExampleObjectSerializer 繼承自 ObjectSerializer,每個需要序列化的對象都需要為其自定義一個 ObjectSerializer椎木,這也是 Serial 提供的序列化規(guī)則违柏,實際與 Android 系統(tǒng)提供的 Parcelable.Creator 機(jī)制類似博烂。

所以這里先調(diào)用了 ObjectSerialize 的 serialize 方法:

@Override
public final void serialize(@NotNull SerializationContext context,
        @NotNull SerializerOutput output, @Nullable T object) throws IOException {
    //object不為null
    if (!SerializationUtils.writeNullIndicator(output, object)) {
        //序列化版本管理
        if (context.isDebug()) {
            output.writeObjectStart(mVersionNumber, getClass().getSimpleName());
        } else {
            output.writeObjectStart(mVersionNumber);
        }
        //noinspection BlacklistedMethod
        //這里調(diào)用重寫了該方法ExampleObjectSerializer中
        serializeObject(context, output, object);
        output.writeObjectEnd();
    }
}

//serializeObject必須由子類實現(xiàn)
protected abstract void serializeObject(@NotNull SerializationContext context,
        @NotNull SerializerOutput output, @NotNull T object) throws IOException;

Serial 默認(rèn)為我們提供了序列化版本管理,這有區(qū)別與 Android 的 Parcelable漱竖,我們可以通過該版本號實現(xiàn)序列化的版本兼容禽篱,后面會為大家介紹該部分內(nèi)容。

serializeObject 方法開始真正自定義序列化過程馍惹,這個過程其實和 Parcelable 中的 Parcelable.Creator 極為相似躺率,都是按照順序?qū)ψ兞窟M(jìn)行讀寫,為了方便理解万矾,可以和 Parcelable.Creator 做下類比:

    public static final Parcelable.Creator<Person> CREATOR = new Creator<Person>() {

    @Override
    public Person createFromParcel(Parcel source) {
        Person person = new Person();
        person.mName = source.readString();
        person.mSex = source.readString();
        person.mAge = source.readInt();
        return person;
    }

    //供反序列化本類數(shù)組時調(diào)用的方法
    @Override
    public Person[] newArray(int size) {
        return new Person[size];
    }
};

通過上面的分析我們知道 SerializerOutput 的實際類型是 ByteBufferSerializerOutput肥照,這也是我們要重點分析的序列化實現(xiàn)過程:

ByteBufferSerializerOutput 中提供了一系列的 write 操作,我們先通過 output.writeInt 方法看下:

@Override
@NotNull
public ByteBufferSerializerOutput writeInt(int val) {
    //SerializerDefs.TYPE_INT byte類型勤众,值為2
    writeIntHeader(SerializerDefs.TYPE_INT, val);
    return this;
}

從方法的返回其實可以看出 ByteBufferSerializerOutput 設(shè)計為構(gòu)建者模式,可以更方便的操作鲤脏。

繼續(xù)向下看 writeIntHeader 方法:

/**
 * int 類型劃分成4種方式存儲
 * 0 默認(rèn)不寫入value们颜,只寫入類型 defualt類型,默認(rèn)值為0
 * 1~255猎醇,僅用一個字節(jié)表示窥突,類型 byte
 * 256 ~ 65535 僅用2個字節(jié)表示,類型short
 * 大于65535 再用4個字節(jié)表示硫嘶,類型int
 */
private void writeIntHeader(byte type, int val) {
    if (val == 0) {
        //ByteBufferSerializerDefs.SUBTYPE_DEFAULT)) 為byte類型值為1
        //makeHeader得到的值是:01000001
        writeHeader(ByteBufferSerializerDefs.makeHeader(type, ByteBufferSerializerDefs.SUBTYPE_DEFAULT));
    } else if ((val & 0xFFFFFF00) == 0) {
        //不超過一個字節(jié)的取值范圍 1~255
        //0x00000001~0x000000FF & 0XFFFFFF00 = 0 此時的值<=255(一個字節(jié)空間足以)
        //<= 255 
        writeHeader(ByteBufferSerializerDefs.makeHeader(type, ByteBufferSerializerDefs.SUBTYPE_BYTE));
        //一個字節(jié)空間阻问,確保剩余空間
        ensureCapacity(ByteBufferSerializerDefs.SIZE_BYTE);
        mByteBuffer.put((byte) val);
    } else if ((val & 0xFFFF0000) == 0) {
        //不超過2個字節(jié)的取值范圍256~65535
        //2個字節(jié)的最大取值是65535
        writeHeader(ByteBufferSerializerDefs.makeHeader(type, ByteBufferSerializerDefs.SUBTYPE_SHORT));
        //2個字節(jié),確保剩余空間
        ensureCapacity(ByteBufferSerializerDefs.SIZE_SHORT);
        //short 2個字節(jié)空間
        mByteBuffer.putShort((short) val);
    } else {
        //否則四個字節(jié)空間 2^32 - 1
        writeHeader(ByteBufferSerializerDefs.makeHeader(type, ByteBufferSerializerDefs.SUBTYPE_INT));
        //4個字節(jié)沦疾,確保剩余空間充足
        ensureCapacity(ByteBufferSerializerDefs.SIZE_INT);
        mByteBuffer.putInt(val);
    }
}

在 writeIntHeader 方法中称近,寫入 int 值按照取值范圍被劃分成 4 種類型:

  • 寫入值為 0
  • 寫入值在 1 ~ 255 之間(1個字節(jié))
  • 寫入值在 256 ~ 65535 之間(2個字節(jié))
  • 寫入值在 65536 ~ 2^32 -1(4個字節(jié))

可以看出 Serial 根據(jù)具體寫入值范圍來優(yōu)化字節(jié)存儲空間。

先來看下 writeHeader 方法:

private void writeHeader(byte headerType) {
    //確保剩余空間充足
    //擴(kuò)容機(jī)制是原容量*2
    ensureCapacity(ByteBufferSerializerDefs.SIZE_BYTE);
    //表示當(dāng)前寫入值的類型哮塞,default/byte/short/int 注意default將不再寫入value,直接默認(rèn)值0
    mByteBuffer.put(headerType);
}

writeHeader 方法是為了確定當(dāng)前寫入值的類型刨秆,用于在反序列化時根據(jù)數(shù)據(jù)類型讀取合適的字節(jié)空間。

ensureCapacity 方法是保證當(dāng)前剩余空間大于要寫入字節(jié)數(shù)忆畅,默認(rèn)擴(kuò)容該機(jī)制是原容量 * 2衡未。

/**
 * 這里單位是字節(jié),如10字節(jié)
 */
private void ensureCapacity(int sizeNeeded) {
    //剩余空間小于需要的空間家凯,單位字節(jié)
    if (mByteBuffer.remaining() < sizeNeeded) {
        //當(dāng)前位置
        final int position = mByteBuffer.position();
        final byte[] bufferContents = mByteBuffer.array();
        //擴(kuò)容原容量的2倍
        final byte[] newBufferContents = new byte[2 * mByteBuffer.capacity()];
        //將原來的字節(jié)數(shù)中拷貝擴(kuò)容后的數(shù)組中
        System.arraycopy(bufferContents, 0, newBufferContents, 0, position);
        final ByteBuffer newBuffer = ByteBuffer.wrap(newBufferContents);
        newBuffer.position(position);
        mByteBuffer = newBuffer;
        //擔(dān)心仍然不夠
        ensureCapacity(sizeNeeded);
    }
}

重新回到 writeIntHeader 方法

  1. 寫入值范圍 val == 0

當(dāng)寫入值 val == 0 時缓醋,我們看到 serial 只是寫入了一個字節(jié)的 Header,用以表示當(dāng)前數(shù)據(jù)的類型绊诲。并沒有將 val 的值在寫入送粱。

  1. 寫入值范圍:(val & 0xFFFFFF00) == 0

此時表示變量 val 的取值在 1 ~ 255(0 不會走到這里) 之間,僅用一個字節(jié)便可以表示驯镊。

  1. 寫入值范圍:(val & 0xFFFF0000) == 0

此時表示 val 的取值在 256 ~ 65535 之間葫督,用兩個字節(jié)空間表示足以竭鞍。

  1. 否則就是 4 個字節(jié)空間

不知道分析到這里,大家是否還記得 Parcelable 的序列化規(guī)則:在 Parcelable 中用于表示數(shù)據(jù)類型采用的是 int橄镜,相比起 Serial 要多占用 3 個字節(jié)空間偎快,并且 Serial 根據(jù)寫入值的范圍進(jìn)一步節(jié)約字節(jié)空間,這個優(yōu)化其實非常有效洽胶,因為在實際的項目中用到的絕大多數(shù)整數(shù)值都相對較小(絕大多數(shù) 2 個字節(jié)空間足以)晒夹。

而這樣的優(yōu)化在 Serial 中到處可見:

/**
 * long 被劃分成2種
 */
private void writeLongHeader(byte type, long val) {
    if ((val & 0xFFFFFFFF00000000L) == 0) {
        //實際這里又根據(jù)值劃分為4種,結(jié)合上面分析的writeIntHeader
        writeIntHeader(type, (int) val);
    } else {
        //否則8個字節(jié)
        writeHeader(ByteBufferSerializerDefs.makeHeader(type, ByteBufferSerializerDefs.SUBTYPE_LONG));
        ensureCapacity(ByteBufferSerializerDefs.SIZE_LONG);
        mByteBuffer.putLong(val);
    }
}

分析到這里其實我們也驗證了前面講到 Serial 主要優(yōu)點之一的:序列化生成的數(shù)據(jù)量(byte[])大約是之前的 1/5姊氓。

Serial 在整個序列化過程中的確與 Parcelable 極其類似丐怯,都沒有像 Java 原生提供的 Serializable 那樣使用大量的反射和臨時變量(Serializable 機(jī)制選擇的是使用成本優(yōu)先),Serial 和 Parcelable 在性能和使用成本的權(quán)衡上翔横,都選擇了性能優(yōu)先读跷,這樣對于開發(fā)人員在使用上就復(fù)雜了很多。 Serial 在序列化的過程中對字節(jié)的控制更加高效禾唁。相比 Parcelable 更節(jié)省空間效览。

以上便是 Serial 整個序列化過程,接下來我們就分析下 Serial 的反序列化過程荡短。

Serial 反序列化過程分析

還是結(jié)合上面給出的示例:

final ExampleObject object = serial.fromByteArray(serializedData, ExampleObject.SERIALIZER)

調(diào)用 ByteBufferSerial 的 fromByteArray 方法:

@Override
@Nullable
@Contract("null, _ -> null")
public <T> T fromByteArray(@Nullable byte[] bytes, @NotNull Serializer<T> serializer)
        throws IOException,
        ClassNotFoundException {
    if (bytes == null || bytes.length == 0) {
        //直接返回null
        return null;
    }
    //序列化是ByteBufferSerializerOutput
    //反序列化是ByteBufferSerializerInput
    final SerializerInput serializerInput = new ByteBufferSerializerInput(bytes);
    try {
        //將ByteBufferSerializerInput作為參數(shù)調(diào)用ObjectSerializer的deserialize方法
        //最終調(diào)用到自定義ExampleObjectSerializer的deserializeObject方法
        return serializer.deserialize(mContext, serializerInput);
    } catch (IOException | ClassNotFoundException | IllegalStateException e) {
        throw new SerializationException("Invalid serialized data:\n" +
                SerializationUtils.dumpSerializedData(bytes, serializerInput.getPosition(), mContext.isDebug()), e);
    }
}

序列化的操作者是 ByteBufferSerializerOutput丐枉,對應(yīng)反序列化的操作者 ByteBufferSerializerInput。將其作為參數(shù)調(diào)用 ObjectSerializer 的 deserialize 方法掘托。

@Nullable
@Override
public T deserialize(@NotNull SerializationContext context, @NotNull SerializerInput input)
        throws IOException, ClassNotFoundException {
    if (SerializationUtils.readNullIndicator(input)) {
        //如果byte[]為null瘦锹,或無數(shù)據(jù)直接返回null
        return null;
    }
    //獲取序列化版本號
    final int deserializedVersionNumber = input.readObjectStart();
    if (deserializedVersionNumber > mVersionNumber) {
        //如果序列化版本號大于當(dāng)前版本號則拋出異常
        throw new SerializationException("Version number found (" + deserializedVersionNumber + ") is " +
                "greater than the maximum supported value (" + mVersionNumber + ")");
    }
    //回到自定義的ExampleObjectSerializer的deserializeObject方法
    final T deserializedObject = deserializeObject(context, input, deserializedVersionNumber);
    input.readObjectEnd();
    return deserializedObject;
}

最后實際回到自定義 ExampleObjectSerializer 的 deserializeObject 方法中:

@Override
    @NotNull
    protected ExampleObject deserializeObject(@NotNull SerializationContext context, @NotNull SerializerInput input,
            int versionNumber) throws IOException, ClassNotFoundException {
        final int num = input.readInt(); // first field
        final SubObject obj = input.readObject(SubObject.SERIALIZER); // second field
        return new ExampleObject(num, obj);
    }

其實這個過程與序列化過程基本一致。只不過這里是從 byte[] 還原到 Object 過程闪盔。

分析到這里弯院,大家是否還記得 Parcelable 的反序列過程:
雖然相比 Java 的 Serializable 減少使用反射提升整體序列化/反序列化性能。但是 Parcelable 在反序列化的過程中仍然使用到了少許反射:首次創(chuàng)建該 Parcelable 時泪掀,首先根據(jù) name 通過 Classloader 加載返回 Class 對象抽兆,然后反射獲取其內(nèi)部的 CREATOR 對象 ,然后在通過該 Creator 實例完成反序列化過程族淮,當(dāng)然后續(xù)系統(tǒng)會緩存該 Parcelable 的 Creator 實例辫红。

Serial 則徹底沒有使用任何反射,其內(nèi)部通過使用者手動傳入對應(yīng)的 Serializer 序列化/反序列化器祝辣。正如 Serial 介紹其主要優(yōu)點之一的:性能相比起傳統(tǒng)反射序列化方案更加高效(沒有使用反射)贴妻,性能比傳統(tǒng)方案提升了 3 倍(序列化速度提升了 5 倍,反序列化提升了 2.5 倍)蝙斜。

重新回到 deserializeObject 方法內(nèi) input.readInt 方法:

@Override
public int readInt() throws IOException {
    //SerializerDefs.TYPE_INT表示int類型
    return readIntHeader(SerializerDefs.TYPE_INT);
}

private int readIntHeader(byte expectedType) throws IOException {
    //讀取數(shù)據(jù)類型信息
    final byte subtype = readHeader(expectedType);
    return readIntValue(subtype);
}

readHeader 方法用于確定當(dāng)前數(shù)據(jù)的類型名惩,還記得序列化時的 writeHeader 方法嗎,此時通過寫入數(shù)據(jù)類型確定要讀取的字節(jié)數(shù)孕荠,重點看下 readIntValue 方法:

/**
 * 這里就是根據(jù)寫入時數(shù)據(jù)類型讀取相應(yīng)的字節(jié)數(shù)
 */
private int readIntValue(byte subtype) throws IOException {
    try {
        if (subtype == ByteBufferSerializerDefs.SUBTYPE_DEFAULT) {
            //default默認(rèn)就是0娩鹉,在writeInt時攻谁,也是只寫了Header,并且沒有寫value
            return 0;
        } else if (subtype == ByteBufferSerializerDefs.SUBTYPE_BYTE) {
            //byte類型讀取一個字節(jié)
            //& 0xFF 的作用是:byte轉(zhuǎn)換成int弯予,要進(jìn)行補(bǔ)位(byte 8位戚宦,int 32位),補(bǔ)位高24為都為1
            //此時數(shù)值已經(jīng)不一致了锈嫩,通過& 0xFF運(yùn)算保證高24位為0受楼,低8位保持原樣
            return mByteBuffer.get() & 0xFF;
        } else if (subtype == ByteBufferSerializerDefs.SUBTYPE_SHORT) {
            //short類型讀取兩個字節(jié)
            //& 0xFFFF的作用參照上面0xFF,保證高16位為0,低16位保持原樣
            return mByteBuffer.getShort() & 0xFFFF;
        } else {
            //否則4個字節(jié)
            return mByteBuffer.getInt();
        }
    } catch (BufferUnderflowException ignore) {
        throw new EOFException();
    }
}

readIntValue 方法完全對應(yīng)序列化時的 writeIntValue 劃分進(jìn)行字節(jié)數(shù)讀取呼寸。

這里需要說的是:& 0xFF 的作用艳汽,當(dāng) byte 強(qiáng)轉(zhuǎn)為 int 時,由于 byte 占用 8 位对雪,而 int 占用 32 位河狐,此時需要補(bǔ)充高位的 24 位為 1,這樣補(bǔ)碼后的二進(jìn)制位就不一致了瑟捣,此時通過 & 0xFF 保證高 24 位為 0甚牲,低 8 位保持原樣,這就就保證了二進(jìn)制數(shù)據(jù)的一致性蝶柿。

小結(jié)

至此關(guān)于 Serial 的序列化/反序列化主線工作流程就分析完了,感興趣的朋友可以進(jìn)一步深入源碼分析非驮。

從 Serial 的實現(xiàn)原理上看交汤,它與 Android 的 Parcelable 及其相似,不過相對 Parcelable 在寫入數(shù)據(jù)做了進(jìn)一步的空間優(yōu)化劫笙,而且整個過程沒有使用一點反射芙扎。

其他

較強(qiáng)的 Debug 能力

Serial 具有較強(qiáng)的 Debug 能力,可以調(diào)試序列化的過程 SerializationUtils

  • dumpSerialzedData 會根據(jù)序列化后的 byte[] 數(shù)據(jù)產(chǎn)生 String 類型的 Log填大。
public static String dumpSerializedData(@NotNull byte[] bytes) {
    return dumpSerializedData(bytes, -1, true);
}

@NotNull
public static String dumpSerializedData(@NotNull SerializerInput input, int position, boolean includeValues) {
    final StringBuilder builder = new StringBuilder().append('{').append(InternalSerialUtils.lineSeparator());
    try {
        int objectNesting = 0;
        String indentation = "    ";
        boolean addPositionMarker = position >= 0;
        byte type;
        while ((type = input.peekType()) != SerializerDefs.TYPE_EOF) {
            if (type == SerializerDefs.TYPE_END_OBJECT) {
                --objectNesting;
                if (objectNesting < 0) {
                    throw new SerializationException("Object end with no matching object start.");
                }
                indentation = InternalSerialUtils.repeat("    ", objectNesting + 1);
                input.readObjectEnd();
                builder.append(indentation).append('}');
            } else {
                builder.append(indentation);
                switch (type) {
                    case SerializerDefs.TYPE_BYTE: {
                        final byte b = input.readByte();
                        if (includeValues) {
                            builder.append("Byte: ").append(b);
                        } else {
                            builder.append("Byte");
                        }
                        break;
                    }
                    case SerializerDefs.TYPE_INT: {
                        final int i = input.readInt();
                        if (includeValues) {
                            builder.append("Integer: ").append(i);
                        } else {
                            builder.append("Integer");
                        }
                        break;
                    }
                    
                    // ... 省略
            }
}

Serial 最終會將序列化后的 byte[] 數(shù)據(jù)封裝進(jìn) StringBuilder 中進(jìn)行輸出戒洼。

  • validataSerializedData 確保了序列化后的對象有有效的結(jié)構(gòu)(比如每個對象都有開頭和結(jié)尾)

部分源碼:

private static void readStream(@NotNull SerializerInput input, boolean singleObject) throws IOException {
    int objectNesting = 0;
    byte type;
    while ((type = input.peekType()) != SerializerDefs.TYPE_EOF) {
        switch (type) {
            case SerializerDefs.TYPE_START_OBJECT:
            case SerializerDefs.TYPE_START_OBJECT_DEBUG: {
                input.readObjectStart();
                //TYPE_START_OBJECT 與 TYPE_END_OBJECT 成對出現(xiàn)
                ++objectNesting;
                break;
            }
            case SerializerDefs.TYPE_END_OBJECT: {
                --objectNesting;
                input.readObjectEnd();
                if (singleObject && objectNesting == 0) {
                    return;
                }
                if (objectNesting < 0) {
                    throw new SerializationException("Object end with no matching object start.");
                }
                break;
            }
            default: {
                throw new SerializationException("Unknown type: " + SerializerDefs.getTypeName(type) + '.');
            }
        }
    }
    if (objectNesting > 0) {
        throw new SerializationException("Object start with no matching object end.");
    }
}

Serial 的異常信息中會包含很多序列化失敗的原因,比如期望的類型和實際類型不匹配這種常見錯誤允华。

Serial 版本控制

如果我們在新版本 App 中添加或刪除了之前已經(jīng)被序列化的對象的 Field圈浇,那么在反序列化老版本數(shù)據(jù)的時候可能會碰到一些問題。

Serial 為我們提供了幾種方案來處理這種情況:

  1. OptionalFieldException

還是結(jié)合上面的例子靴寂,在 ExampleObject 添加了一個新的字段磷蜀,這時反序列化老版本 ExampleObject 就會出問題。Serializer 默認(rèn)會依次讀取所有的 Field百炬,此時拋出 OptionalFieldException 異常褐隆。

如果使用的是普通的 Serializer,我們可以通過 try-catch 來處理這個問題剖踊。

  • 比如想要給 ExampleObject 的最后增加一個叫 name 字段(原先的 ExampleObject 僅有 num 和 SubObject 這兩個字段)

此時我們必須向下面一樣修改deserializeObject 方法:

@Override
@NotNull
protected ExampleObject deserializeObject(@NotNull SerializationContext context, @NotNull SerializerInput input, int versionNumber) throws IOException, ClassNotFoundException {
    final int num = input.readInt();
    final SubObject obj = input.readObject(SubObject.SERIALIZER);
    final String name;
    try {
        name = input.readString();
    } catch (OptionalFieldException e) {
        name = DEFAULT_NAME; // 老版本中沒有這個字段庶弃,給它一個默認(rèn)值
    }
    return new ExampleObject(num, obj, name);
}

對于 BuilderSerializer衫贬,只需要在 deserializeToBuilder 的最后添加 .setName(input.readString()) 即可。

  1. 版本號

還可以通過給 Serializer 添加一個版本號歇攻,這樣在反序列化的過程中就可以通過這個版本號進(jìn)行復(fù)雜的處理了固惯。添加版本號十分簡單,只需要在 SERIALIZER 的構(gòu)造函數(shù)中傳入數(shù)字即可掉伏。

我們修改下上面的代碼缝呕,通過版本號來處理字段新老版本的問題:

final Serializer<ExampleObject> SERIALIZER = new ExampleObjectSerializer(1);

...

@Override
@NotNull
protected ExampleObject deserializeObject(@NotNull SerializationContext context, @NotNull SerializerInput input, int versionNumber) throws IOException, ClassNotFoundException {
    final int num = input.readInt();
    final SubObject obj = input.readObject(SubObject.SERIALIZER);
    final String name;
    if (versionNumber < 1) {
        name = DEFAULT_NAME;
    } else {
        name = input.readString();
    }
    return new ExampleObject(num, obj, name);
}

如果你刪除了序列化對象中部的某個 Field,比如 ExampleObject 中間的 SubObject斧散。你可能需要用 SerializationUtils.skipObject() 來終止整個反序列化過程供常。如果已經(jīng)把 SubObject 完全移除了,那么可以不用保留 SubObject 中的 Serializer 對象鸡捐。

比如栈暇,你可能在新版本中刪除了 SubObject,而老版本的數(shù)據(jù)中含有這個對象箍镜,你可以進(jìn)行下面的處理:

@Override
@NotNull
protected ExampleObject deserializeObject(@NotNull SerializationContext context, @NotNull SerializerInput input, int versionNumber)
        throws IOException, ClassNotFoundException {
    final int num = input.readInt();
    if (versionNumber < 1) {
        SerializationUtils.skipObject()
        name = DEFAULT_NAME;
    } else {
        name = input.readString();
    }
    return new ExampleObject(num, name);
}

另一種方法是調(diào)用 input.peekType源祈,這個方法可以讓你在讀取 Object 對象前進(jìn)行下一個參數(shù)的類型檢查,它提供了一個除判斷版本號之外的解決新老數(shù)據(jù)問題的方案色迂。當(dāng)你不愿意升級版本號或是不愿意擦除數(shù)據(jù)的時候香缺,這個方法會十分有用。

需要注意的是:該方法僅僅適用于兩個對象類型不同的情況歇僧。因為這里 Object 是 SubObject图张,name 類型是 String,所以可以進(jìn)行如下處理:

@Override
@NotNull
protected ExampleObject deserializeObject(@NotNull SerializationContext context, @NotNull SerializerInput input, int versionNumber) throws IOException, ClassNotFoundException {
    final int num = input.readInt();
    if (input.peekType() == SerializerDefs.TYPE_START_OBJECT) {
        SerializationUtils.skipObject();
        name = DEFAULT_NAME;
    } else {
        name = input.readString();
    }
    return new ExampleObject(num, name);
}

總結(jié)

不知大家有沒有注意到诈悍,Serial 從實現(xiàn)原理上看就像是把 Parcelable 和 Serializable 的優(yōu)點集合在一起的方案祸轮。

  • 由于沒有使用反射,相比起傳統(tǒng)的反射序列話方案更加高效侥钳,具體你可以參考上面的測試數(shù)據(jù)适袜。
  • 開發(fā)者對于序列化過程的控制較強(qiáng),可定義哪些 Object舷夺、Field 需要被序列化苦酱。
  • 有很強(qiáng)的 Debug 能力,可以調(diào)試序列化的過程
  • 有很強(qiáng)的版本管理能力给猾,可以通過版本號和 OptionalFieldException 做兼容躏啰。

總體看 Serial 這套對象序列化方案還不錯,但是對象的序列化要記錄的信息還是比較多耙册,在操作比較頻繁時候给僵,對應(yīng)用的影響還是不少的,這個時候我們可以選擇使用數(shù)據(jù)的序列化。


以上便是個人在學(xué)習(xí) Serial 時的心得和體會帝际,文中分析如有不妥或更好的分析結(jié)果蔓同,還請大家指出!

文章如果對你有幫助蹲诀,就請留個贊吧斑粱!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市脯爪,隨后出現(xiàn)的幾起案子则北,更是在濱河造成了極大的恐慌,老刑警劉巖痕慢,帶你破解...
    沈念sama閱讀 206,214評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件尚揣,死亡現(xiàn)場離奇詭異,居然都是意外死亡掖举,警方通過查閱死者的電腦和手機(jī)快骗,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來塔次,“玉大人方篮,你說我怎么就攤上這事±海” “怎么了藕溅?”我有些...
    開封第一講書人閱讀 152,543評論 0 341
  • 文/不壞的土叔 我叫張陵,是天一觀的道長继榆。 經(jīng)常有香客問我巾表,道長,這世上最難降的妖魔是什么裕照? 我笑而不...
    開封第一講書人閱讀 55,221評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮调塌,結(jié)果婚禮上晋南,老公的妹妹穿的比我還像新娘。我一直安慰自己羔砾,他們只是感情好负间,可當(dāng)我...
    茶點故事閱讀 64,224評論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著姜凄,像睡著了一般政溃。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上态秧,一...
    開封第一講書人閱讀 49,007評論 1 284
  • 那天董虱,我揣著相機(jī)與錄音,去河邊找鬼。 笑死愤诱,一個胖子當(dāng)著我的面吹牛云头,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播淫半,決...
    沈念sama閱讀 38,313評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼溃槐,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了科吭?” 一聲冷哼從身側(cè)響起昏滴,我...
    開封第一講書人閱讀 36,956評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎对人,沒想到半個月后谣殊,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,441評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡规伐,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,925評論 2 323
  • 正文 我和宋清朗相戀三年蟹倾,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片猖闪。...
    茶點故事閱讀 38,018評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡鲜棠,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出培慌,到底是詐尸還是另有隱情豁陆,我是刑警寧澤,帶...
    沈念sama閱讀 33,685評論 4 322
  • 正文 年R本政府宣布吵护,位于F島的核電站盒音,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏馅而。R本人自食惡果不足惜祥诽,卻給世界環(huán)境...
    茶點故事閱讀 39,234評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望瓮恭。 院中可真熱鬧雄坪,春花似錦、人聲如沸屯蹦。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽登澜。三九已至阔挠,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間脑蠕,已是汗流浹背购撼。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評論 1 261
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人份招。 一個月前我還...
    沈念sama閱讀 45,467評論 2 352
  • 正文 我出身青樓切揭,卻偏偏與公主長得像,于是被迫代替她去往敵國和親锁摔。 傳聞我的和親對象是個殘疾皇子廓旬,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,762評論 2 345