十二、Android性能優(yōu)化之?dāng)?shù)據(jù)傳輸效率優(yōu)化

數(shù)據(jù)傳輸效率優(yōu)化

一、數(shù)據(jù)的序列化和反序列化

服務(wù)器對(duì)象Object------數(shù)據(jù)流---->客戶端Object對(duì)象

傳統(tǒng)序列化:
Serializable/Parcelable
效率低
像新聞端用戶瀏覽時(shí)會(huì)下載大量圖片和文字
采用傳統(tǒng)數(shù)據(jù)傳輸會(huì)造成內(nèi)存的浪費(fèi)和CPU計(jì)算時(shí)間的占用

數(shù)據(jù)的序列化是程序代碼里面必不可少的組成部分,當(dāng)我們討論到數(shù)據(jù)序列化的性能的時(shí)候,需要了解有哪些候選的方案,他們各自的優(yōu)缺點(diǎn)是什么。首先什么是序列化?用下面的圖來(lái)解釋一下:

序列化

數(shù)據(jù)序列化的行為可能發(fā)生在數(shù)據(jù)傳遞過(guò)程中的任何階段,例如網(wǎng)絡(luò)傳輸,不同進(jìn)程間數(shù)據(jù)傳遞,不同類(lèi)之間的參數(shù)傳遞,把數(shù)據(jù)存儲(chǔ)到磁盤(pán)上等等。通常情況下,我們會(huì)把那些需要序列化的類(lèi)實(shí)現(xiàn)Serializable接口(如下圖所示),但是這種傳統(tǒng)的做法效率不高,實(shí)施的過(guò)程會(huì)消耗更多的內(nèi)存。

傳統(tǒng)做法消耗太多的內(nèi)存

但是我們?nèi)绻褂肎SON庫(kù)來(lái)處理這個(gè)序列化的問(wèn)題,不僅僅執(zhí)行速度更快,內(nèi)存的使用效率也更高。Android的XML布局文件會(huì)在編譯的階段被轉(zhuǎn)換成更加復(fù)雜的格式,具備更加高效的執(zhí)行性能與更高的內(nèi)存使用效率。

Google Gson序列化

下面介紹三個(gè)數(shù)據(jù)序列化的候選方案:

  • Protocal Buffers:強(qiáng)大,靈活,但是對(duì)內(nèi)存的消耗會(huì)比較大,并不是移動(dòng)終端上的最佳選擇。
  • Nano-Proto-Buffers:基于Protocal,為移動(dòng)終端做了特殊的優(yōu)化,代碼執(zhí)行效率更高,內(nèi)存使用效率更佳。
  • FlatBuffers:這個(gè)開(kāi)源庫(kù)最開(kāi)始是由Google研發(fā)的,專(zhuān)注于提供更優(yōu)秀的性能。
    上面這些方案在性能方面的數(shù)據(jù)對(duì)比如下圖所示:


可見(jiàn),F(xiàn)latBuffers 幾乎從空間和時(shí)間復(fù)雜度上完勝其他技術(shù)。
FlatBuffers 是一個(gè)開(kāi)源的跨平臺(tái)數(shù)據(jù)序列化庫(kù),可以應(yīng)用到幾乎任何語(yǔ)言(C++, C#, Go, Java, JavaScript, PHP, Python),最開(kāi)始是 Google 為游戲或者其他對(duì)性能要求很高的應(yīng)用開(kāi)發(fā)的。
項(xiàng)目地址在GitHub 上。官方的文檔地址。

FlatBuffer 的優(yōu)點(diǎn)

FlatBuffer 相對(duì)于其他序列化技術(shù),例如 XML,JSON,Protocol Buffers 等,有哪些優(yōu)勢(shì)呢?官方文檔的說(shuō)法如下:

1.直接讀取序列化數(shù)據(jù),而不需要解析(Parsing)或者解包(Unpacking):FlatBuffer 把數(shù)據(jù)層級(jí)結(jié)構(gòu)保存在一個(gè)扁平化的二進(jìn)制緩存(一維數(shù)組)中,同時(shí)能夠保持直接獲取里面的結(jié)構(gòu)化數(shù)據(jù),而不需要解析,并且還能保證數(shù)據(jù)結(jié)構(gòu)變化的前后向兼容。

2.高效的內(nèi)存使用和速度:FlatBuffer 使用過(guò)程中,不需要額外的內(nèi)存,幾乎接近原始數(shù)據(jù)在內(nèi)存中的大小。

3.靈活:數(shù)據(jù)能夠前后向兼容,并且能夠靈活控制你的數(shù)據(jù)結(jié)構(gòu)。

4.很少的代碼侵入性:使用少量的自動(dòng)生成的代碼即可實(shí)現(xiàn)。

5.強(qiáng)數(shù)據(jù)類(lèi)性,易于使用,跨平臺(tái),幾乎語(yǔ)言無(wú)關(guān)。

官方提供了一個(gè)性能對(duì)比表如下:

性能對(duì)比

在做 Android 開(kāi)發(fā)的時(shí)候,JSON 是最常用的數(shù)據(jù)序列化技術(shù)。我們知道,JSON 的可讀性很強(qiáng),但是序列化和反序列化性能卻是最差的。解析的時(shí)候,JSON 解析器首先,需要在內(nèi)存中初始化一個(gè)對(duì)應(yīng)的數(shù)據(jù)結(jié)構(gòu),這個(gè)事件經(jīng)常會(huì)消耗 100ms ~ 200ms2;解析過(guò)程中,要產(chǎn)生大量的臨時(shí)變量,造成 Java 虛擬機(jī)的 GC 和內(nèi)存抖動(dòng),解析 20KB 的數(shù)據(jù),大概會(huì)消耗 100KB 的臨時(shí)內(nèi)存2。FlatBuffers 就解決了這些問(wèn)題。

FlatBuffer 使用

下載文件編譯工具1.下載源碼$ git clone https://github.com/google/flatbuffers2.下載編譯工具https://cmake.org/download/ 下載對(duì)應(yīng)的cmake工具
參考網(wǎng)上:可以使用 cmake編譯成flatc工具。但是暫時(shí)沒(méi)有搞懂怎么編譯?
最后,在這個(gè)地方可以下載windows的 exe可以運(yùn)用文件。https://github.com/google/flatbuffers/releases比較坑的最新的1.7.1沒(méi)有exe沒(méi)有exe下載。也沒(méi)有看后面的,以為需要自己編譯。最后一不留 神看到1.7.0有下載。哎。。。。。。。。

編寫(xiě)描述使用 FlatBuffers 的 IDL 定義好數(shù)據(jù)結(jié)構(gòu) Schema,編寫(xiě) Schema 的詳細(xì)文檔在這里 http://google.github.io/flatbuffers/flatbuffers_guide_writing_schema.html。參考一個(gè)例子

namespace com.haocai.app.flatbuffer;
table Items {
    ItemId : long;
    timestemp : int;
    basic:[Basic];
}

table Basic{
    id:int;
    name:string;
    email:int;
    code:long;
    isVip:bool;
    count:int;
    carList:[Car];
}

table Car{
    id:long;
    number:long;
    describle:string;
}

root_type Items;

使用工具 生產(chǎn)相應(yīng)數(shù)據(jù)類(lèi)


Paste_Image.png

2.根據(jù)json生成和fbs 生成flatbuffer 二進(jìn)制文件.bin
例如repos_json.json (有數(shù)據(jù)),對(duì)應(yīng)的數(shù)據(jù)結(jié)構(gòu)repos_schema.fbs ,生成repos_json.bin(二進(jìn)制flatbuffer格式的)
$ ./flatc -j -b repos_schema.fbs repos_json.json

工程中使用
編寫(xiě)對(duì)應(yīng)描述文件,翻譯成對(duì)應(yīng)語(yǔ)言的類(lèi)。把對(duì)應(yīng)的類(lèi), 放到你的工程中。

調(diào)用
MainActivity.Java

public class MainActivity extends AppCompatActivity {

    private static final String TAG ="main" ;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }

    public void serialize(View v) {
//----------------序列化-------------
        FlatBufferBuilder builder = new FlatBufferBuilder();

        int id1 = builder.createString("蘭博基尼");
        //準(zhǔn)備Car對(duì)象
        int car1 = Car.createCar(builder,10001L,88888L,id1);
        int id2 = builder.createString("奧迪A8");
        //準(zhǔn)備Car對(duì)象
        int car2 = Car.createCar(builder,10001L,88888L,id2);
        int id3 = builder.createString("奧迪A9");
        //準(zhǔn)備Car對(duì)象
        int car3 = Car.createCar(builder,10001L,88888L,id3);

        int[] cars = new int[3];
        cars[0] = car1;
        cars[1] = car2;
        cars[2] = car3;

        //創(chuàng)建Basic對(duì)象里面的Car集合

        int carList =  Basic.createCarListVector(builder,cars);

        int name =  builder.createString("kpioneer");
        int email = builder.createString("kpioneer@qq.com");
        int basic = Basic.createBasic(builder,10,name,email,100L,true,100,carList);
        int basicOffset = Items.createBasicVector(builder,new int[]{basic});
        /**
         * table Items {
         ItemId : long;
         timestemp : int;
         basic:[Basic];
         }
         */

        Items.startItems(builder);
        Items.addItemId(builder,1000L);
        Items.addTimestemp(builder,2016);
        Items.addBasic(builder,basicOffset);

        int rootItems = Items.endItems(builder);
        Items.finishItemsBuffer(builder,rootItems);

        //============保存數(shù)據(jù)到文件=================
        File sdcard = Environment.getExternalStorageDirectory();
        //保存的路徑
        File file = new File(sdcard,"Items.txt");
        if(file.exists()){
            file.delete();
        }
        ByteBuffer data = builder.dataBuffer();
        FileOutputStream out = null;
        FileChannel channel = null;
        try {
            out = new FileOutputStream(file);
            channel = out.getChannel();
            while(data.hasRemaining()){
                channel.write(data);
            }

        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            try {
                if(out!=null){
                    out.close();
                }
                if(channel!=null){
                    channel.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        //===================反序列化=============================
        FileInputStream fis = null;
        FileChannel readChannel = null;
        try {
            fis = new FileInputStream(file);
            ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
            readChannel = fis.getChannel();
            int readBytes = 0;
            while ((readBytes=readChannel.read(byteBuffer))!=-1){
                System.out.println("讀取數(shù)據(jù)個(gè)數(shù):"+readBytes);
            }
            //把指針回到最初的狀態(tài),準(zhǔn)備從byteBuffer當(dāng)中讀取數(shù)據(jù)
            byteBuffer.flip();
            //解析出二進(jìn)制為Items對(duì)象。
            Items items = Items.getRootAsItems(byteBuffer);
            //讀取數(shù)據(jù)測(cè)試看看是否跟保存的一致
            Log.i(TAG,"items.id:"+items.ItemId());
            Log.i(TAG,"items.timestemp:"+items.timestemp());

            Basic basic2 = items.basic(0);
            Log.i(TAG,"basic2.name:"+basic2.name());
            Log.i(TAG,"basic2.email:"+basic2.email());

            //carList
            int length = basic2.carListLength();
            for (int i=0;i<length; i++){
                Car car = basic2.carList(i);
                Log.i(TAG,"car.number:"+car.number());
                Log.i(TAG,"car.describle:"+car.describle());
            }
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            try {
                if(readChannel!=null){
                    readChannel.close();
                }
                if(fis!=null){
                    fis.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}


結(jié)果輸出:
09-27 16:25:38.567 15008-15008/com.haocai.app.flatbuffer I/System.out: 讀取數(shù)據(jù)個(gè)數(shù):304
09-27 16:25:38.567 15008-15008/com.haocai.app.flatbuffer I/main: items.id:1000
09-27 16:25:38.567 15008-15008/com.haocai.app.flatbuffer I/main: items.timestemp:2016
09-27 16:25:38.567 15008-15008/com.haocai.app.flatbuffer I/main: basic2.name:kpioneer
09-27 16:25:38.567 15008-15008/com.haocai.app.flatbuffer I/main: basic2.email:196
09-27 16:25:38.567 15008-15008/com.haocai.app.flatbuffer I/main: car.number:88888
09-27 16:25:38.567 15008-15008/com.haocai.app.flatbuffer I/main: car.describle:蘭博基尼
09-27 16:25:38.567 15008-15008/com.haocai.app.flatbuffer I/main: car.number:88888
09-27 16:25:38.567 15008-15008/com.haocai.app.flatbuffer I/main: car.describle:奧迪A8
09-27 16:25:38.567 15008-15008/com.haocai.app.flatbuffer I/main: car.number:88888
09-27 16:25:38.567 15008-15008/com.haocai.app.flatbuffer I/main: car.describle:奧迪A9

基本原理



在上面的布局中,你需要注意:

每個(gè)對(duì)象都被分為兩個(gè)部分:元數(shù)據(jù)的部分(或vtable)在軸心點(diǎn)的左邊,真實(shí)的數(shù)據(jù)部分在右邊。
每個(gè)字段對(duì)應(yīng)于vtable中的一個(gè)槽,其中存儲(chǔ)了那個(gè)字段的真實(shí)數(shù)據(jù)的偏移量。比如,John的table的第一個(gè)槽的值為1,表明了John的名字被存放在了距離Jonh的軸心點(diǎn)向右偏移一個(gè)字節(jié)的地方。

對(duì)于對(duì)象字段,vtable中的偏移量指向子對(duì)象的軸心點(diǎn)。比如,John的vtable中的第三個(gè)槽指向了Mary的軸心點(diǎn)。

要表示字段沒(méi)有值,我們可以在一個(gè)vtable槽中使用一個(gè)0偏移量。

特別感謝:

動(dòng)腦學(xué)院Ricky

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Spring Cloud為開(kāi)發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見(jiàn)模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,568評(píng)論 19 139
  • Spark SQL, DataFrames and Datasets Guide Overview SQL Dat...
    Joyyx閱讀 8,487評(píng)論 0 16
  • Spark SQL, DataFrames and Datasets Guide Overview SQL Dat...
    草里有只羊閱讀 18,555評(píng)論 0 85
  • 發(fā)現(xiàn) 關(guān)注 消息 iOS 第三方庫(kù)、插件、知名博客總結(jié) 作者大灰狼的小綿羊哥哥關(guān)注 2017.06.26 09:4...
    肇東周閱讀 15,359評(píng)論 4 61
  • 我常想,一個(gè)人成長(zhǎng)的最重要方面應(yīng)該是靈魂的不斷完整和內(nèi)心生活的充實(shí)和沉淀,而文字就是記錄下生命成長(zhǎng)的重要方式之一了...
    有馬_3a55閱讀 114評(píng)論 0 1

友情鏈接更多精彩內(nèi)容