Protocol Buffer For Android

什么是protocal buffer?

protocal buffer 以下簡(jiǎn)稱protobuf是google 的一種數(shù)據(jù)交換的格式,它獨(dú)立于語(yǔ)言,獨(dú)立于平臺(tái)。(作用類似json、xml等,但是更安全,更快)
簡(jiǎn)要說(shuō)明一下流程:

文章分兩個(gè)部分 我先講講protobuf的語(yǔ)法規(guī)則與介紹,再講講如何使用(已經(jīng)使用在項(xiàng)目中,如不想了解介紹可以直接跳到使用部分,一般.proto文件服務(wù)端會(huì)提供好)

介紹

如果你用 android studio 在plugin中安裝了 protobuf android插件。那么android studio 將可以識(shí)別.proto文件(.proto文件就是一種描述文件)
效果如下圖所示:


Paste_Image.png

syntax :是指定編譯的格式、我們可以使用 “proto2” “proto3” 具體區(qū)別我也沒(méi)有深究 默認(rèn)使用“proto2”
packge 指定生成的java文件所在包
option java_package 指定生成的java文件所在的完整包名

  • message是消息定義的關(guān)鍵字,等同于C++中的struct/class,或是Java中的class。
  • Request為消息的名字,等同于結(jié)構(gòu)體名或類名。
  • required前綴表示該字段為必要字段,既在序列化和反序列化之前該字段必須已經(jīng)被賦值。與此同時(shí),在Protocol Buffer中還存在另外兩個(gè)類似的關(guān)鍵字,optionalrepeated,帶有這兩種限定符的消息字段則沒(méi)有required字段這樣的限制。相比于optionalrepeated主要用于表示數(shù)組字段。具體的使用方式在后面的用例中均會(huì)一一列出。
  • int64string分別表示長(zhǎng)整型和字符串型的消息字段,在Protocol Buffer中存在一張類型對(duì)照表,既Protocol Buffer中的數(shù)據(jù)類型與其他編程語(yǔ)言(C++/Java)中所用類型的對(duì)照。該對(duì)照表中還將給出在不同的數(shù)據(jù)場(chǎng)景下,哪種類型更為高效。該對(duì)照表將在后面給出。
  • tokensign分別表示消息字段名,等同于Java中的域變量名,或是C++中的成員變量名。
  • 標(biāo)簽數(shù)字12則表示不同的字段在序列化后的二進(jìn)制數(shù)據(jù)中的布局位置。在該例中,sign字段編碼后的數(shù)據(jù)一定位于token之后。需要注意的是該值在同一message中不能重復(fù)。另外,對(duì)于Protocol Buffer而言,標(biāo)簽值為1到15的字段在編碼時(shí)可以得到優(yōu)化,既標(biāo)簽值和類型信息僅占有一個(gè)byte,標(biāo)簽范圍是16到2047的將占有兩個(gè)bytes,而Protocol Buffer可以支持的字段數(shù)量則為2的29次方減一。有鑒于此,我們?cè)谠O(shè)計(jì)消息結(jié)構(gòu)時(shí),可以盡可能考慮讓repeated類型的字段標(biāo)簽位于1到15之間,這樣便可以有效的節(jié)省編碼后的字節(jié)數(shù)量。

定義一個(gè)含有枚舉字段
Protocol Buffer消息。

//在定義Protocol Buffer的消息時(shí),可以使用和C++/Java代碼同樣的方式添加注釋。

enum UserStatus {
  OFFLINE = 0;  //表示處于離線狀態(tài)的用戶     
  ONLINE = 1;   //表示處于在線狀態(tài)的用戶    
 }
message UserInfo {         
  required int64 acctID = 1;
  required string name = 2; 
  required UserStatus status = 3;
}

這里將給出以上消息定義的關(guān)鍵性說(shuō)明(僅包括上一小節(jié)中沒(méi)有描述的)。

  • enum是枚舉類型定義的關(guān)鍵字,等同于C++/Java中的enum。
  1. UserStatus為枚舉的名字。
  2. 和C++/Java中的枚舉不同的是,枚舉值之間的分隔符是分號(hào),而不是逗號(hào)。
  3. OFFLINE/ONLINE為枚舉值。
  4. 0和1表示枚舉值所對(duì)應(yīng)的實(shí)際整型值,和C/C++一樣,可以為枚舉值指定任意整型值,而無(wú)需總是從0開(kāi)始定義。如
enum OperationCode {
    LOGON_REQ_CODE = 101;
    LOGOUT_REQ_CODE = 102;
    RETRIEVE_BUDDIES_REQ_CODE = 103;
    LOGON_RESP_CODE = 1001;
    LOGOUT_RESP_CODE = 1002;
    RETRIEVE_BUDDIES_RESP_CODE = 1003;
}

定義含有嵌套消息字段的Protocol Buffer消息。
我們可以在同一個(gè).proto文件中定義多個(gè)message,這樣便可以很容易的實(shí)現(xiàn)嵌套消息的定義。如:

enum UserStatus {
    OFFLINE = 0;  
    ONLINE = 1;
}
message UserInfo {
    required int64 acctID = 1;
    required string name = 2;**   
    required UserStatus status = 3;**   
   }
message LogonRespMessage {**     
    required LoginResult logonResult = 1;**       
    required UserInfo userInfo = 2;** 
}

這里將給出以上消息定義的關(guān)鍵性說(shuō)明(僅包括上兩小節(jié)中沒(méi)有描述的)。

  • LogonRespMessage消息的定義中包含另外一個(gè)消息類型作為其字段,如UserInfo userInfo。
  1. 上例中的UserInfo和LogonRespMessage被定義在同一個(gè).proto文件中,那么我們是否可以包含在其他.proto文件中定義的message呢?Protocol Buffer提供了另外一個(gè)關(guān)鍵字import,這樣我們便可以將很多通用的message定義在同一個(gè).proto文件中,而其他消息定義文件可以通過(guò)import的方式將該文件中定義的消息包含進(jìn)來(lái),如:
    import "myproject/CommonMessages.proto"**

限定符(required/optional/repeated)的基本規(guī)則

  • 在每個(gè)消息中必須至少留有一個(gè)required類型的字段。
  1. 每個(gè)消息中可以包含0個(gè)或多個(gè)optional類型的字段。
  2. repeated表示的字段可以包含0個(gè)或多個(gè)數(shù)據(jù)。需要說(shuō)明的是,這一點(diǎn)有別于C++/Java中的數(shù)組,因?yàn)楹髢烧咧械臄?shù)組必須包含至少一個(gè)元素。
  3. 如果打算在原有消息協(xié)議中添加新的字段,同時(shí)還要保證老版本的程序能夠正常讀取或?qū)懭耄敲磳?duì)于新添加的字段必須是optional或repeated。道理非常簡(jiǎn)單,老版本程序無(wú)法讀取或?qū)懭胄略龅膔equired限定符的字段。

** Protocol Buffer消息升級(jí)原則。**

在實(shí)際的開(kāi)發(fā)中會(huì)存在這樣一種應(yīng)用場(chǎng)景,既消息格式因?yàn)槟承┬枨蟮淖兓坏貌贿M(jìn)行必要的升級(jí),但是有些使用原有消息格式的應(yīng)用程序暫時(shí)又不能被立刻升級(jí),這便要求我們?cè)谏?jí)消息格式時(shí)要遵守一定的規(guī)則,從而可以保證基于新老消息格式的新老程序同時(shí)運(yùn)行。規(guī)則如下:

  • 不要修改已經(jīng)存在字段的標(biāo)簽號(hào)。
  1. 任何新添加的字段必須是optional和repeated限定符,否則無(wú)法保證新老程序在互相傳遞消息時(shí)的消息兼容性。
  2. 在原有的消息中,不能移除已經(jīng)存在的required字段,optional和repeated類型的字段可以被移除,但是他們之前使用的標(biāo)簽號(hào)必須被保留,不能被新的字段重用。
  3. int32、uint32、int64、uint64和bool等類型之間是兼容的,sint32和sint64是兼容的,string和bytes是兼容的,fixed32和sfixed32,以及fixed64和sfixed64之間是兼容的,這意味著如果想修改原有字段的類型時(shí),為了保證兼容性,只能將其修改為與其原有類型兼容的類型,否則就將打破新老消息格式的兼容性。
  4. optional和repeated限定符也是相互兼容的。

Packages

我們可以在.proto文件中定義包名,如: package ourproject.lyphone; 該包名在生成對(duì)應(yīng)的C++文件時(shí),將被替換為名字空間名稱,既namespace ourproject { namespace lyphone。而在生成的Java代碼文件中將

Options。

Protocol Buffer允許我們?cè)?proto文件中定義一些常用的選項(xiàng),這樣可以指示Protocol Buffer編譯器幫助我們生成更為匹配的目標(biāo)語(yǔ)言代碼。Protocol Buffer內(nèi)置的選項(xiàng)被分為以下三個(gè)級(jí)別:

  1. 文件級(jí)別,這樣的選項(xiàng)將影響當(dāng)前文件中定義的所有消息和枚舉。
  2. 消息級(jí)別,這樣的選項(xiàng)僅影響某個(gè)消息及其包含的所有字段。
  3. 字段級(jí)別,這樣的選項(xiàng)僅僅響應(yīng)與其相關(guān)的字段。 下面將給出一些常用的Protocol Buffer選項(xiàng)。
  4. option java_package = "com.companyname.projectname"; java_package是文件級(jí)別的選項(xiàng),通過(guò)指定該選項(xiàng)可以讓生成Java代碼的包名為該選項(xiàng)值,如上例中的Java代碼包名為com.companyname.projectname。與此同時(shí),生成的Java文件也將會(huì)自動(dòng)存放到指定輸出目錄下的com/companyname/projectname子目錄中。如果沒(méi)有指定該選項(xiàng),Java的包名則為package關(guān)鍵字指定的名稱。該選項(xiàng)對(duì)于生成C++代碼毫無(wú)影響。
  5. option java_outer_classname = "LYPhoneMessage"; java_outer_classname是文件級(jí)別的選項(xiàng),主要功能是顯示的指定生成Java代碼的外部類名稱。如果沒(méi)有指定該選項(xiàng),Java代碼的外部類名稱為當(dāng)前文件的文件名部分,同時(shí)還要將文件名轉(zhuǎn)換為駝峰格式,如:my_project.proto,那么該文件的默認(rèn)外部類名稱將為MyProject。該選項(xiàng)對(duì)于生成C++代碼毫無(wú)影響。 注:主要是因?yàn)镴ava中要求同一個(gè).java文件中只能包含一個(gè)Java外部類或外部接口,而C++則不存在此限制。因此在.proto文件中定義的消息均為指定外部類的內(nèi)部類,這樣才能將這些消息生成到同一個(gè)Java文件中。在實(shí)際的使用中,為了避免總是輸入該外部類限定符,可以將該外部類靜態(tài)引入到當(dāng)前Java文件中,如:import static com.company.project.LYPhoneMessage.*。
  6. option optimize_for = LITE_RUNTIME; optimize_for是文件級(jí)別的選項(xiàng),Protocol Buffer定義三種優(yōu)化級(jí)別SPEED/CODE_SIZE/LITE_RUNTIME。缺省情況下是SPEED。 SPEED: 表示生成的代碼運(yùn)行效率高,但是由此生成的代碼編譯后會(huì)占用更多的空間。 CODE_SIZE: 和SPEED恰恰相反,代碼運(yùn)行效率較低,但是由此生成的代碼編譯后會(huì)占用更少的空間,通常用于資源有限的平臺(tái),如Mobile。 LITE_RUNTIME: 生成的代碼執(zhí)行效率高,同時(shí)生成代碼編譯后的所占用的空間也是非常少。這是以犧牲Protocol Buffer提供的反射功能為代價(jià)的。因此我們?cè)贑++中鏈接Protocol Buffer庫(kù)時(shí)僅需鏈接libprotobuf-lite,而非libprotobuf。在Java中僅需包含protobuf-java-2.4.1-lite.jar,而非protobuf-java-2.4.1.jar。 注:對(duì)于LITE_MESSAGE選項(xiàng)而言,其生成的代碼均將繼承自MessageLite,而非Message。
  7. [pack = true]: 因?yàn)闅v史原因,對(duì)于數(shù)值型的repeated字段,如int32、int64等,在編碼時(shí)并沒(méi)有得到很好的優(yōu)化,然而在新近版本的Protocol Buffer中,可通過(guò)添加[pack=true]的字段選項(xiàng),以通知Protocol Buffer在為該類型的消息對(duì)象編碼時(shí)更加高效。如: repeated int32 samples = 4 [packed=true]。 注:該選項(xiàng)僅適用于2.3.0以上的Protocol Buffer。
  8. [default = default_value]: optional類型的字段,如果在序列化時(shí)沒(méi)有被設(shè)置,或者是老版本的消息中根本不存在該字段,那么在反序列化該類型的消息是,optional的字段將被賦予類型相關(guān)的缺省值,如bool被設(shè)置為false,int32被設(shè)置為0。Protocol Buffer也支持自定義的缺省值,如: optional int32 result_per_page = 3 [default = 10]。

類型對(duì)照表

proto Type Notes C++ Type Java Type
double double double
float float float
int32 Uses variable-length encoding. Inefficient for encoding negative numbers – if your field is likely to have negative values, use sint32 instead. int32 int
int64 Uses variable-length encoding. Inefficient for encoding negative numbers – if your field is likely to have negative values, use sint64 instead. int64 long
uint32 Uses variable-length encoding. uint32 int
uint64 Uses variable-length encoding. uint64 long
sint32 Uses variable-length encoding. Signed int value. These more efficiently encode negative numbers than regular int32s. int32 int
sint64 Uses variable-length encoding. Signed int value. These more efficiently encode negative numbers than regular int64s. int64 long
fixed32 Always four bytes. More efficient than uint32 if values are often greater than 228. uint32 int
fixed64 Always eight bytes. More efficient than uint64 if values are often greater than 256. uint64 long
sfixed32 Always four bytes. int32 int
sfixed64 Always eight bytes. int64 long
bool bool boolean
string A string must always contain UTF-8 encoded or 7-bit ASCII text. string String
bytes May contain any arbitrary sequence of bytes. string ByteString

使用

為了方便大家使用,我將下面用到的文件都放到了csdn上面供大家下載(mac和windows的)
http://download.csdn.net/detail/qq_22605283/9722829

  • 生成java文件(普通版)

1.在git上或者下面谷歌的官網(wǎng)鏈接下載根據(jù)平臺(tái)下載protoc的可運(yùn)行文件,然后把 .proto文件放到同一個(gè)目錄下(或者從上面鏈接下載)
2 cd 到該目錄下用terminal 運(yùn)行以下代碼:
protoc --java_out ./ ./.proto
protoc:表示利用剛才下載的可運(yùn)行程序打包
--java_out :是輸出指令 第一個(gè)從參數(shù)是生成的java目錄,第二個(gè)參數(shù)是指定編 譯的.proto文件 ./
.proto表示當(dāng)前目錄下所有.proto文件
3把java文件拷貝到項(xiàng)目上
4 在項(xiàng)目的build.gradle的dependencies節(jié)點(diǎn)下加入代碼:
compile 'com.google.protobuf:protobuf-java:3.0.0'
這行代碼是引用android 使用上面生成的java代碼需要的jar包

該方法生成的java文件方法會(huì)比較多,文件也比較大,會(huì)占用許多空間,所以當(dāng)運(yùn)用到實(shí)際項(xiàng)目的時(shí)候會(huì)采用輕量版

  • 生成java文件(lite版)

1.在git上或者下面谷歌的官網(wǎng)鏈接下載根據(jù)平臺(tái)下載protoc的可運(yùn)行文件,然后把 .proto文件放到同一個(gè)目錄下(或者從上面鏈接下載)
2.在git上面下載proto-gen-javalite可運(yùn)行文件放在上面的目錄下
2 cd 到該目錄下用terminal 運(yùn)行以下代碼:
protoc --javalite_out ./ ./*.proto
3把java文件拷貝到項(xiàng)目上
4 在項(xiàng)目的build.gradle的dependencies節(jié)點(diǎn)下加入代碼:
compile 'com.google.protobuf:protobuf-lite:3.0.1'

注意有區(qū)別哦
還有你會(huì)發(fā)現(xiàn)雖然方法數(shù)少了,size也少了,可是這個(gè)java文件依舊占了很大的size。不要急,打開(kāi)后你會(huì)發(fā)現(xiàn)其實(shí)有很大一部分的代碼都是注釋,所以當(dāng)文件打包的時(shí)候或者混淆的時(shí)候?qū)嶋H會(huì)變得很小。舉個(gè)實(shí)例:我的項(xiàng)目中生成的java文件將近700k,混淆完最后打包只占用了40k的大小。

  • 使用java代碼

例如 我生成了一個(gè)Pb.java文件 ,如果原本的.proto帶有一個(gè)message Request,那么這個(gè)Pb.java文件中就會(huì)有一個(gè)Request的內(nèi)部類。 看例子代碼:

    String appIdUTF8 = "";
    try {//注意格式轉(zhuǎn)碼
        appIdUTF8 = URLEncoder.encode("你好啊", "UTF-8");        
    } catch (UnsupportedEncodingException e) {
        Log.e(TAG, "err: " + e.getMessage());
    }
    //實(shí)例化一個(gè)PB對(duì)象
    Pb.Request request = Pb.Request.newBuilder().setId(appIdUTF8).set***.builder
    byte[] data = rq.toByteArray();//將PB對(duì)象轉(zhuǎn)換成二進(jìn)制流
    String stream =new String(data);//講PB對(duì)象轉(zhuǎn)換成String
...//解析 從網(wǎng)絡(luò)上或者數(shù)據(jù) 比如我們已經(jīng)連接上一個(gè)HttpURLConnection conn
    //可以被解析的參數(shù)有很多種,可以查看下面的圖
    Pb.Request  rq =Pb.Request.parseFrom(conn.getInputStream())
    rq.getSid();//獲得Pb對(duì)象的sid屬性
轉(zhuǎn)換格式

好了到這里已經(jīng)介紹完了,當(dāng)我使用pb時(shí),都是服務(wù)器給定的.proto文件,所以不會(huì)動(dòng)態(tài)的去生成java文件,一次生成一直使用,如果你的項(xiàng)目有需求動(dòng)態(tài)更新.proto文件的時(shí)候并且生成新的java代碼 請(qǐng)參考這個(gè)帖子:
http://www.tuicool.com/articles/ruIFvif

資料參考
https://developers.google.com/protocol-buffers/
http://www.cnblogs.com/stephen-liu74/archive/2013/01/02/2841485.html

最后編輯于
?著作權(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)容

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