Protocol Buffer For Android

什么是protocal buffer?

protocal buffer 以下簡稱protobuf是google 的一種數據交換的格式,它獨立于語言,獨立于平臺。(作用類似json、xml等,但是更安全,更快)
簡要說明一下流程:

文章分兩個部分 我先講講protobuf的語法規則與介紹,再講講如何使用(已經使用在項目中,如不想了解介紹可以直接跳到使用部分,一般.proto文件服務端會提供好)

介紹

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


Paste_Image.png

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

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

定義一個含有枚舉字段
Protocol Buffer消息。

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

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

這里將給出以上消息定義的關鍵性說明(僅包括上一小節中沒有描述的)。

  • enum是枚舉類型定義的關鍵字,等同于C++/Java中的enum。
  1. UserStatus為枚舉的名字。
  2. 和C++/Java中的枚舉不同的是,枚舉值之間的分隔符是分號,而不是逗號。
  3. OFFLINE/ONLINE為枚舉值。
  4. 0和1表示枚舉值所對應的實際整型值,和C/C++一樣,可以為枚舉值指定任意整型值,而無需總是從0開始定義。如
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消息。
我們可以在同一個.proto文件中定義多個message,這樣便可以很容易的實現嵌套消息的定義。如:

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;** 
}

這里將給出以上消息定義的關鍵性說明(僅包括上兩小節中沒有描述的)。

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

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

  • 在每個消息中必須至少留有一個required類型的字段。
  1. 每個消息中可以包含0個或多個optional類型的字段。
  2. repeated表示的字段可以包含0個或多個數據。需要說明的是,這一點有別于C++/Java中的數組,因為后兩者中的數組必須包含至少一個元素。
  3. 如果打算在原有消息協議中添加新的字段,同時還要保證老版本的程序能夠正常讀取或寫入,那么對于新添加的字段必須是optional或repeated。道理非常簡單,老版本程序無法讀取或寫入新增的required限定符的字段。

** Protocol Buffer消息升級原則。**

在實際的開發中會存在這樣一種應用場景,既消息格式因為某些需求的變化而不得不進行必要的升級,但是有些使用原有消息格式的應用程序暫時又不能被立刻升級,這便要求我們在升級消息格式時要遵守一定的規則,從而可以保證基于新老消息格式的新老程序同時運行。規則如下:

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

Packages

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

Options。

Protocol Buffer允許我們在.proto文件中定義一些常用的選項,這樣可以指示Protocol Buffer編譯器幫助我們生成更為匹配的目標語言代碼。Protocol Buffer內置的選項被分為以下三個級別:

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

類型對照表

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上或者下面谷歌的官網鏈接下載根據平臺下載protoc的可運行文件,然后把 .proto文件放到同一個目錄下(或者從上面鏈接下載)
2 cd 到該目錄下用terminal 運行以下代碼:
protoc --java_out ./ ./.proto
protoc:表示利用剛才下載的可運行程序打包
--java_out :是輸出指令 第一個從參數是生成的java目錄,第二個參數是指定編 譯的.proto文件 ./
.proto表示當前目錄下所有.proto文件
3把java文件拷貝到項目上
4 在項目的build.gradle的dependencies節點下加入代碼:
compile 'com.google.protobuf:protobuf-java:3.0.0'
這行代碼是引用android 使用上面生成的java代碼需要的jar包

該方法生成的java文件方法會比較多,文件也比較大,會占用許多空間,所以當運用到實際項目的時候會采用輕量版

  • 生成java文件(lite版)

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

注意有區別哦
還有你會發現雖然方法數少了,size也少了,可是這個java文件依舊占了很大的size。不要急,打開后你會發現其實有很大一部分的代碼都是注釋,所以當文件打包的時候或者混淆的時候實際會變得很小。舉個實例:我的項目中生成的java文件將近700k,混淆完最后打包只占用了40k的大小。

  • 使用java代碼

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

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

好了到這里已經介紹完了,當我使用pb時,都是服務器給定的.proto文件,所以不會動態的去生成java文件,一次生成一直使用,如果你的項目有需求動態更新.proto文件的時候并且生成新的java代碼 請參考這個帖子:
http://www.tuicool.com/articles/ruIFvif

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

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,431評論 6 544
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,637評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,555評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,900評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,629評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,976評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,976評論 3 448
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,139評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,686評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,411評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,641評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,129評論 5 364
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,820評論 3 350
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,233評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,567評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,362評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,604評論 2 380

推薦閱讀更多精彩內容