什么是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文件就是一種描述文件)
效果如下圖所示:

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)鍵字,optional和repeated,帶有這兩種限定符的消息字段則沒(méi)有required字段這樣的限制。相比于optional,repeated主要用于表示數(shù)組字段。具體的使用方式在后面的用例中均會(huì)一一列出。 -
int64和string分別表示長(zhǎng)整型和字符串型的消息字段,在Protocol Buffer中存在一張類型對(duì)照表,既Protocol Buffer中的數(shù)據(jù)類型與其他編程語(yǔ)言(C++/Java)中所用類型的對(duì)照。該對(duì)照表中還將給出在不同的數(shù)據(jù)場(chǎng)景下,哪種類型更為高效。該對(duì)照表將在后面給出。 -
token和sign分別表示消息字段名,等同于Java中的域變量名,或是C++中的成員變量名。 - 標(biāo)簽數(shù)字1和2則表示不同的字段在序列化后的二進(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。
-
UserStatus為枚舉的名字。 - 和C++/Java中的枚舉不同的是,枚舉值之間的分隔符是分號(hào),而不是逗號(hào)。
-
OFFLINE/ONLINE為枚舉值。 - 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。
- 上例中的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類型的字段。
- 每個(gè)消息中可以包含0個(gè)或多個(gè)optional類型的字段。
- repeated表示的字段可以包含0個(gè)或多個(gè)數(shù)據(jù)。需要說(shuō)明的是,這一點(diǎn)有別于C++/Java中的數(shù)組,因?yàn)楹髢烧咧械臄?shù)組必須包含至少一個(gè)元素。
- 如果打算在原有消息協(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)。
- 任何新添加的字段必須是optional和repeated限定符,否則無(wú)法保證新老程序在互相傳遞消息時(shí)的消息兼容性。
- 在原有的消息中,不能移除已經(jīng)存在的required字段,optional和repeated類型的字段可以被移除,但是他們之前使用的標(biāo)簽號(hào)必須被保留,不能被新的字段重用。
- int32、uint32、int64、uint64和bool等類型之間是兼容的,sint32和sint64是兼容的,string和bytes是兼容的,fixed32和sfixed32,以及fixed64和sfixed64之間是兼容的,這意味著如果想修改原有字段的類型時(shí),為了保證兼容性,只能將其修改為與其原有類型兼容的類型,否則就將打破新老消息格式的兼容性。
- 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í)別:
- 文件級(jí)別,這樣的選項(xiàng)將影響當(dāng)前文件中定義的所有消息和枚舉。
- 消息級(jí)別,這樣的選項(xiàng)僅影響某個(gè)消息及其包含的所有字段。
- 字段級(jí)別,這樣的選項(xiàng)僅僅響應(yīng)與其相關(guān)的字段。 下面將給出一些常用的Protocol Buffer選項(xiàng)。
- 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ú)影響。
- 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.*。
- 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。
- [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。
- [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屬性

好了到這里已經(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