Java基礎(chǔ)-ProtoBuf解析

Android知識總結(jié)

一、Protobuf簡介

protocolbuffer(以下簡稱protobuf)是google 的是一種輕便高效的結(jié)構(gòu)化數(shù)據(jù)存儲格式,作用形同于xml和json。它獨(dú)立于語言,獨(dú)立于平臺。google 提供了多種語言的實(shí)現(xiàn):java、c#、c++、go 和 python,每一種實(shí)現(xiàn)都包含了相應(yīng)語言的編譯器以及庫文件。由于它是一種二進(jìn)制的格式,比使用 xml 進(jìn)行數(shù)據(jù)交換快許多??梢园阉糜诜植际綉?yīng)用之間的數(shù)據(jù)通信或者異構(gòu)環(huán)境下的數(shù)據(jù)交換。作為一種效率和兼容性都很優(yōu)秀的二進(jìn)制數(shù)據(jù)傳輸格式,可以用于諸如網(wǎng)絡(luò)傳輸、配置文件、數(shù)據(jù)存儲等諸多領(lǐng)域。

Protobuf是一種平臺無關(guān)、語言無關(guān)、可擴(kuò)展且輕便高效的序列化數(shù)據(jù)結(jié)構(gòu)的協(xié)議,可以用于網(wǎng)絡(luò)通信數(shù)據(jù)存儲。 可簡單類比于 XML ,其具有以下特點(diǎn):

  • 1、語言無關(guān)、平臺無關(guān)。即 ProtoBuf 支持 Java、C++、Python 等多種語言,支持多個平臺
  • 2、高效。即比 XML 更?。? ~ 10倍)、更快(20 ~ 100倍)、更為簡單
  • 3、擴(kuò)展性、兼容性好。你可以更新數(shù)據(jù)結(jié)構(gòu),而不影響和破壞原有的舊程

1、優(yōu)點(diǎn)

使用代碼生成器生成的代碼來讀寫這個數(shù)據(jù)結(jié)構(gòu)。你甚至可以在無需重新部署程序的情況下更新數(shù)據(jù)結(jié)構(gòu)。只需使用 Protobuf 對數(shù)據(jù)結(jié)構(gòu)進(jìn)行一次描述,即可利用各種不同語言或從各種不同數(shù)據(jù)流中對你的結(jié)構(gòu)化數(shù)據(jù)輕松讀寫。

它有一個非常棒的特性,即“向后”兼容性好,人們不必破壞已部署的、依靠“老”數(shù)據(jù)格式的程序就可以對數(shù)據(jù)結(jié)構(gòu)進(jìn)行升級。這樣您的程序就可以不必?fù)?dān)心因?yàn)橄⒔Y(jié)構(gòu)的改變而造成的大規(guī)模的代碼重構(gòu)或者遷移的問題。因?yàn)樘砑有碌南⒅械?field 并不會引起已經(jīng)發(fā)布的程序的任何改變。

2、缺點(diǎn)

Protbuf 與 XML 相比也有不足之處。它功能簡單,無法用來表示復(fù)雜的概念。

XML 已經(jīng)成為多種行業(yè)標(biāo)準(zhǔn)的編寫工具,Protobuf 只是 Google 公司內(nèi)部使用的工具,在通用性上還差很多。

由于文本并不適合用來描述數(shù)據(jù)結(jié)構(gòu),所以 Protobuf 也不適合用來對基于文本的標(biāo)記文檔(如 HTML)建模。另外,由于 XML 具有某種程度上的自解釋性,它可以被人直接讀取編輯,在這一點(diǎn)上 Protobuf 不行,它以二進(jìn)制的方式存儲,除非你有 .proto 定義,否則你沒法直接讀出 Protobuf 的任何內(nèi)容。

二、使用protobuf步驟

android中使用protobuf,過程是這樣的:

  • 1、定義proto文件;
  • 2、使用該文件生成對應(yīng)的java類;
  • 3、利用該java類實(shí)現(xiàn)數(shù)據(jù)傳輸;

從以上過程中就可以看出,我們并不是直接使用proto文件,而是對應(yīng)的java類,如何根據(jù)proto文件生成java類呢?官方推薦的是命令行的方式生成,但是Android Studio生成方式更加簡單,這里直接介紹as生成方式(同樣適用服務(wù)端開發(fā)工具intellij idea)

第一步

在AS的Plugins中插入protobuf插件,如圖


第二步

在根Project/build.gradle中加入protobuf插件

buildscript {
    repositories {
        google()
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:3.5.0'
        classpath 'com.google.protobuf:protobuf-gradle-plugin:0.8.8'
    }
}
第三步

在app/build.gradle中加入如下配置,頂部加上應(yīng)用插件

apply plugin: 'com.google.protobuf'
apply plugin: 'com.android.application'

android{}中加入

sourceSets {
        main {
            java {
                srcDir 'src/main/java'
            }
            proto {
                srcDir 'src/main/proto' //指定.proto文件路徑
                include '**/*.proto'  //find it
            }
        }
    }

android{}同級加入:

//編譯并生成
protobuf {
    protoc { // 也可以配置本地編譯器路徑
        artifact = 'com.google.protobuf:protoc:3.4.0'
    }
    plugins {
        javalite {
            // The codegen for lite comes as a separate artifact
            artifact = 'com.google.protobuf:protobuf-java:3.4.0'
        }
    }
    //這里配置生成目錄,編譯后會在build的目錄下生成對應(yīng)的java文件和C文件
    generateProtoTasks {
        all().each { task ->
            task.builtins {
                // In most cases you don't need the full Java output
                // if you use the lite output.
                remove java
            }
            task.builtins {
                java {} //java文件
                cpp {} //C文件
            }
        }
    }
}

dependencies中加入protobuf相關(guān)依賴

implementation 'com.google.protobuf:protobuf-java:3.4.0'
implementation 'com.google.protobuf:protoc:3.4.0'
  • 混淆配置:
-keep class * extends com.google.protobuf.GeneratedMessageLite { *; }
第四步

在app\src\main目錄中新建proto文件夾,并新建對應(yīng)的proto文件,這里以LoginRequest.proto為例


LoginRequest.proto文件內(nèi)容為:

syntax = "proto3";  //聲明 proto 協(xié)議版本 ( proto2 和 proto3 在定義看數(shù)據(jù)結(jié)構(gòu)時有些差別)
package com.example.protobuf;  //定義了 Protobuf 自動生成類的包名(即 java 類所在的包名)
option java_package = "com.example.protobuf";//java 類所在的包名 == package com.example.protobuf;
option java_outer_classname = "LoginRequestProto"; //定義了 Protobuf 自動生成類的類名

message LoginRequest {
  string name = 1;
  int32 id = 2;
  string email = 3;
  string phone = 4;
}

//定義了類中的字段(這里只有 account 和 password 兩個字段)
message Login {
  uint64 ID = 1;
  string name = 2;
  string password = 3;
  oneof pet {
    Dog dog = 4;
    Cat cat = 5;
  }
}

message Dog {
  string name = 1;
  bool sex = 2;
}

message Cat {
  string name = 1;
  //屬性可以與Dog不同
}
第五步

Build/Clean Project跑完即可,此時會在\app\build\generated\source\proto中生成對應(yīng)的java文件和C++文件,拷出來備用。


三、Window 系統(tǒng)下使用protobuf

protoc --version    #查看protoc的版本

代碼轉(zhuǎn)換顯例(把目錄切換到 E:\user\protoc-3.15.8-win64\bin, protoc的bin目錄下)

protoc.exe --java_out=E:\java Immortaldb.proto

輸出文件夾是E:\java
輸入是Immortaldb.proto

四、簡單使用

public class MainActivity extends AppCompatActivity {
    private TextView mTextView;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mTextView = findViewById(R.id.name_tv);
        //序列化
        LoginRequestProto.LoginRequest loginRequest = LoginRequestProto.LoginRequest
                .newBuilder()
                .setName("XaoLi")
                .setId(122)
                .setEmail("123@QQ.com")
                .setPhone("123456")
                .build();
        byte[] bytes = loginRequest.toByteArray();

        //反序列化
        try {
            LoginRequestProto.LoginRequest login = LoginRequestProto.LoginRequest
                    .parseFrom(bytes);
            mTextView.setText(login.getName());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
  • 在proto3枚舉值第一個必須是0,其他的隨意
  • 在proto2,每個屬性前必須加required,optional,repeated
  • 該數(shù)字只要不重復(fù),可以定義為任何數(shù)字,不需要總是從1或者0開始
  • 這個數(shù)字表示在序列化數(shù)組里面的順序

注意:定義的proto文件中的編號對應(yīng)字段名前后可以不行同,但是編號對應(yīng)的字段類型的相同。

五、語法

syntax = "proto3";
message LoginRequest {
  string query = 1;
  int32 page_number = 2;
  int32 result_per_page = 3;
}
  • 文件的第一行指定了你正在使用proto3語法:如果你沒有指定這個,編譯器會使用proto2。這個指定語法行必須是文件的非空非注釋的第一個行。
  • SearchRequest消息格式有3個字段,在消息中承載的數(shù)據(jù)分別對應(yīng)于每一個字段。其中每個字段都有一個名字和一種類型。

關(guān)鍵字

  • syntax:聲明版本。例如上面syntax="proto3",如果沒有聲明,則默認(rèn)是proto2。
  • package:聲明包名.
  • import:導(dǎo)入包。類似于java,例如上面導(dǎo)入了timestamp.proto包。
  • java_package:指定生成的類應(yīng)該放在什么Java包名下。如果你沒有顯式地指定這個值,則它簡單地匹配由package 聲明給出的Java包名,但這些名字通常都不是合適的Java包名 (由于它們通常不以一個域名打頭)。
  • java_outer_classname:定義應(yīng)該包含這個文件中所有類的類名。如果你沒有顯式地給定java_outer_classname ,則將通過把文件名轉(zhuǎn)換為首字母大寫來生成。例如上面例子編譯生成的文件名和類名是AddressBookProtos。
  • message:類似于java中的class關(guān)鍵字。
  • repeated:用于修飾屬性,表示對應(yīng)的屬性是個array。

更多的關(guān)鍵字可以參考官方文檔,這里不做介紹。

1)、標(biāo)量數(shù)值類型

Protobuf3語言指南

2)、枚舉

當(dāng)需要定義一個消息類型的時候,可能想為一個字段指定某“預(yù)定義值序列”中的一個值。例如,假設(shè)要為每一個SearchRequest消息添加一個 corpus字段,而corpus的值可能是UNIVERSAL,WEB,IMAGES,LOCAL,NEWS,PRODUCTS或VIDEO中的一個。 其實(shí)可以很容易地實(shí)現(xiàn)這一點(diǎn):通過向消息定義中添加一個枚舉(enum)并且為每個可能的值定義一個常量就可以了。

在下面的例子中,在消息格式中添加了一個叫做Corpus的枚舉類型——它含有所有可能的值 ——以及一個類型為Corpus的字段

message SearchRequest {
  string query = 1;
  int32 page_number = 2;
  int32 result_per_page = 3;
  enum Corpus {
    UNIVERSAL = 0;
    WEB = 1;
    IMAGES = 2;
    LOCAL = 3;
    NEWS = 4;
    PRODUCTS = 5;
    VIDEO = 6;
  }
  Corpus corpus = 4;
}

如你所見,Corpus枚舉的第一個常量映射為0:每個枚舉類型必須將其第一個類型映射為0,這是因?yàn)椋?/p>

  • 必須有有一個0值,我們可以用這個0值作為默認(rèn)值。
  • 這個零值必須為第一個元素,為了兼容proto2語義,枚舉類的第一個值總是默認(rèn)值。
3)、其他消息類型
message SearchResponse {
  repeated Result results = 1;
}

message Result {
  string url = 1;
  string title = 2;
  repeated string snippets = 3;
}

所指定的消息字段修飾符必須是如下之一:

  • singular:一個格式良好的消息應(yīng)該有0個或者1個這種字段(但是不能超過1個)。
  • repeated:在一個格式良好的消息中,這種字段可以重復(fù)任意多次(包括0次)。重復(fù)的值的順序會被保留。
    在proto3中,repeated的標(biāo)量域默認(rèn)情況蝦使用packed。
4)、嵌套類型

你可以在其他消息類型中定義、使用消息類型,在下面的例子中,Result消息就定義在SearchResponse消息內(nèi),如:

message SearchResponse {
  message Result {
    string url = 1;
    string title = 2;
    repeated string snippets = 3;
  }
  repeated Result results = 1;
}

如果你想在它的父消息類型的外部重用這個消息類型,你需要以Parent.Type的形式使用它,如:

message SomeOtherMessage {
  SearchResponse.Result result = 1;
}
5)、字段編號

在message定義中每個字段都有一個唯一的編號,這些編號被用來在二進(jìn)制消息體中識別你定義的這些字段,一旦你的message類型被用到后就不應(yīng)該在修改這些編號了。注意在將message編碼成二進(jìn)制消息體時字段編號1-15將會占用1個字節(jié),16-2047將占用兩個字節(jié)。所以在一些頻繁使用用的message中,你應(yīng)該總是先使用前面1-15字段編號。

你可以指定的最小編號是1,最大是2E29 - 1(536,870,911)。其中19000到19999是給protocol buffers實(shí)現(xiàn)保留的字段標(biāo)號,定義message時不能使用。同樣的你也不能重復(fù)使用任何當(dāng)前message定義里已經(jīng)使用過和預(yù)留的字段編號。

參考

Protobuf3語言指南
Language Guide (proto3)

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

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

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