前言
- 習慣用
Json、XML數據存儲格式的你們,相信大多都沒聽過Protocol Buffer Protocol Buffer其實 是Google出品的一種輕量 & 高效的結構化數據存儲格式,性能比Json、XML真的強!太!多!由於
Google出品,我相信Protocol Buffer已經具備足夠的吸引力- 今天,我將獻上一份
Protocol Buffer的介紹 & 使用攻略,希望你們會喜歡。
目錄
1. 定義
一種 結構化數據 的數據存儲格式(類似於 XML、Json )
Protocol Buffer目前有兩個版本:proto2和proto3- 因為
proto3還是beta 版,所以本次講解是proto2
2. 作用
通過將 結構化的數據 進行 串行化(序列化),從而實現 數據存儲 / RPC 數據交換的功能
- 序列化: 將 數據結構或對象 轉換成 二進制串 的過程
- 反序列化:將在序列化過程中所生成的二進制串 轉換成 數據結構或者對象 的過程
3. 特點
- 對比於 常見的
XML、Json數據存儲格式,Protocol Buffer有如下特點:
4. 應用場景
傳輸數據量大 & 網絡環境不穩定 的數據存儲、RPC 數據交換 的需求場景
如 即時IM (QQ、微信)的需求場景
總結
在 傳輸數據量較大的需求場景下,Protocol Buffer比XML、Json 更小、更快、使用 & 維護更簡單!
5. 使用流程
使用 Protocol Buffer 的流程如下:
5.1 環境配置
-
要使用
Protocol Buffer,需要先在電腦上安裝Protocol Buffer -
整個 安裝過程 只需要按照以下步驟進行即可:
整個安裝過程請 自備梯子 以保證 網絡暢通
步驟1:下載 Protocol Buffer 安裝包
- 下載方式1:官網下載(需要翻牆)
- 下載方式2:貼心的我 已經給你們准備好了,請移步百度網盤,密碼:paju
此處選擇 較穩定的版本
protobuf-2.6.1.tar.gz進行演示
下載成功后,對文件進行解壓,如下圖:
步驟2:安裝 HOMEBREW(已安裝的可以跳過)
// 打開 終端 輸入以下指令 /usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
步驟3:安裝 Protocol Buffer
打開 您的終端 依次輸入 下列指令 即可:
brew install autoconf automake libtool curl
// Step1:安裝 Protocol Buffer 依賴
// 注:Protocol Buffer 依賴於 autoconf、automake、libtool、curl
cd Desktop/protobuf-2.6.1
// Step2:進入 Protocol Buffer安裝包 解壓后的文件夾(我的解壓文件放在桌面)
./autogen.sh // Step3:運行 autogen.sh 腳本 ./configure // Step4:運行 configure.sh 腳本 make // Step5:編譯未編譯的依賴包 make check // Step6:檢查依賴包是否完整 make install // Step7:開始安裝Protocol Buffer
步驟4:檢查 Protocol Buffer 是否安裝成功
// 在 終端 下輸入 protoc - - version
出現 libprotoc 2.6.1 提示即表示 安裝成功,如下圖
特別注意:
protoc=Protocol Buffer的編譯器- 作用:將
.proto文件編譯成對應平台的 頭文件和源代碼文件 - 在下面會詳細介紹
至此, Protocol Buffer已經安裝完成。下面將講解如何具體使用Protocol Buffer
5.2 構建 Protocol Buffer 消息對象模型
5.2.1 構建步驟
下面將通過一個實例(Android(Java) 平台為例)詳細介紹每個步驟。
5.2.2 詳細介紹
- 實例說明:構建一個
Person類的數據結構,包含成員變量name、id、email等等
// Java類 public class Person { private String name; private Int id; private String email; ... }
- 平台使用:以
Android(Java)平台為例來進行演示
步驟1:通過 Protocol Buffer 語法 描述 需要存儲的數據結構
- 新建一個文件,命名規則為:文件名 = 類名,后綴為
.proto此處叫
Demo.proto
- 根據上述數據結構的需求,在
Demo.proto里 通過Protocol Buffer語法寫入對應.proto對象模型的代碼,如下:
package protocobuff_Demo; // 關注1:包名 option java_package = "com.carson.proto"; option java_outer_classname = "Demo"; // 關注2:option選項 // 關注3:消息模型 // 下面詳細說明 // 生成 Person 消息對象(包含多個字段,下面詳細說明) message Person { required string name = 1; required int32 id = 2; optional string email = 3; enum PhoneType { MOBILE = 0; HOME = 1; WORK = 2; } message PhoneNumber { required string number = 1; optional PhoneType type = 2 [default = HOME]; } repeated PhoneNumber phone = 4; } message AddressBook { repeated Person person = 1; }
- 下面將結合 上述例子 對
Protocol Buffer語法 進行詳細介紹
關注1:包名
package protocobuff_Demo; // 關注1:包名
- 作用:防止不同
.proto項目間命名 發生沖突 Protocol buffer包的解析過程如下:Protocol buffer的類型名稱解析與C++一致:從 最內部 開始查找,依次 向外 進行每個包會被看作是其父類包的內部類
Protocol buffer編譯器會解析.proto文件中定義的所有類型名- 生成器會根據 不同語言 生成 對應語言 的代碼文件
a. 即對 不同語言 使用了 不同的規則 進行處理
b.Protoco Buffer提供C++、Java、Python三種語言的 API
關注2:Option選項
option java_package = "com.carson.proto"; option java_outer_classname = "Demo"; // 關注2:option選項
-
作用:影響 特定環境下 的處理方式
但不改變整個文件聲明的含義
-
常用Option選項如下:
option java_package = "com.carson.proto"; // 定義:Java包名 // 作用:指定生成的類應該放在什么Java包名下 // 注:如不顯式指定,默認包名為:按照應用名稱倒序方式進行排序 option java_outer_classname = "Demo"; // 定義:類名 // 作用:生成對應.java 文件的類名(不能跟下面message的類名相同) // 注:如不顯式指定,則默認為把.proto文件名轉換為首字母大寫來生成 // 如.proto文件名="my_proto.proto",默認情況下,將使用 "MyProto" 做為類名 option optimize_for = ***; // 作用:影響 C++ & java 代碼的生成 // ***參數如下: // 1. SPEED (默認)::protocol buffer編譯器將通過在消息類型上執行序列化、語法分析及其他通用的操作。(最優方式) // 2. CODE_SIZE::編譯器將會產生最少量的類,通過共享或基於反射的代碼來實現序列化、語法分析及各種其它操作。 // 特點:采用該方式產生的代碼將比SPEED要少很多, 但是效率較低; // 使用場景:常用在 包含大量.proto文件 但 不追求效率 的應用中。 //3. LITE_RUNTIME::編譯器依賴於運行時 核心類庫 來生成代碼(即采用libprotobuf-lite 替代libprotobuf)。 // 特點:這種核心類庫要比全類庫小得多(忽略了 一些描述符及反射 );編譯器采用該模式產生的方法實現與SPEED模式不相上下,產生的類通過實現 MessageLite接口,但它僅僅是Messager接口的一個子集。 // 應用場景:移動手機平台應用 option cc_generic_services = false; option java_generic_services = false; option py_generic_services = false; // 作用:定義在C++、java、python中,protocol buffer編譯器是否應該 基於服務定義 產生 抽象服務代碼(2.3.0版本前該值默認 = true) // 自2.3.0版本以來,官方認為通過提供 代碼生成器插件 來對 RPC實現 更可取,而不是依賴於“抽象”服務 optional repeated int32 samples = 4 [packed=true]; // 如果該選項在一個整型基本類型上被設置為真,則采用更緊湊的編碼方式(不會對數值造成損失) // 在2.3.0版本前,解析器將會忽略 非期望的包裝值。因此,它不可能在 不破壞現有框架的兼容性上 而 改變壓縮格式。 // 在2.3.0之后,這種改變將是安全的,解析器能夠接受上述兩種格式。 optional int32 old_field = 6 [deprecated=true]; // 作用:判斷該字段是否已經被棄用 // 作用同 在java中的注解@Deprecated
- 在
ProtocolBuffers中允許 自定義選項 並 使用 - 該功能屬於高級特性,使用頻率很低,此處不過多描述。有興趣可查看官方文檔
關注3:消息模型
- 作用:真正用於描述 數據結構
// 消息對象用message修飾 message Person { required string name = 1; required int32 id = 2; optional string email = 3; enum PhoneType { MOBILE = 0; HOME = 1; WORK = 2; } message PhoneNumber { optional PhoneType type = 2 [default = HOME]; } repeated PhoneNumber phone = 4; } message AddressBook { repeated Person person = 1; }
- 組成:在
ProtocolBuffers中:- 一個
.proto消息模型 = 一個.proto文件 = 消息對象 + 字段 - 一個消息對象(
Message) = 一個 結構化數據 - 消息對象(
Message)里的 字段 = 結構化數據 里的成員變量
- 一個
下面會詳細介紹 .proto 消息模型里的 消息對象 & 字段
1. 消息對象
在 ProtocolBuffers 中:
- 一個消息對象(
Message) = 一個 結構化數據 - 消息對象用 修飾符
message修飾 - 消息對象 含有 字段:消息對象(
Message)里的 字段 = 結構化數據 里的成員變量
特別注意:
a. 添加:在一個 .proto文件 中可定義多個 消息對象
- 應用場景:盡可能將與 某一消息類型 對應的響應消息格式 定義到相同的
.proto文件 中 - 實例:
message SearchRequest { required string query = 1; optional int32 page_number = 2; optional int32 result_per_page = 3; } // 與SearchRequest消息類型 對應的 響應消息類型SearchResponse message SearchResponse { … }
b. 一個消息對象 里 可以定義 另外一個消息對象(即嵌套)
message Person { required string name = 1; required int32 id = 2; optional string email = 3; // 該消息類型 定義在 Person消息類型的內部 // 即Person消息類型 是 PhoneNumber消息類型的父消息類型 message PhoneNumber { required string number = 1; } } <-- 多重嵌套 --> message Outer { // Level 0 message MiddleAA { // Level 1 message Inner { // Level 2 required int64 ival = 1; optional bool booly = 2; } } }
2. 字段
- 消息對象的字段 組成主要是:字段 = 字段修飾符 + 字段類型 +字段名 +標識號
- 下面將對每一項詳細介紹
a. 字段修飾符
- 作用:設置該字段解析時的規則
- 具體類型如下:
b. 字段類型
字段類型主要有 三 類:
- 基本數據 類型
- 枚舉 類型
- 消息對象 類型
message Person { // 基本數據類型 字段 required string name = 1; required int32 id = 2; optional string email = 3; enum PhoneType { MOBILE = 0; HOME = 1; WORK = 2; } message PhoneNumber { optional PhoneType type = 2 [default = HOME]; // 枚舉類型 字段 } repeated PhoneNumber phone = 4; // 消息類型 字段 }
1. 基本數據類型
.proto基本數據類型 對應於 各平台的基本數據類型如下:
2. 枚舉類型
- 作用:為字段指定一個 可能取值的字段集合
該字段只能從 該指定的字段集合里 取值
- 說明:如下面例子,電話號碼 可能是手機號、家庭電話號或工作電話號的其中一個,那么就將
PhoneType定義為枚舉類型,並將加入電話的集合(MOBILE、HOME、WORK)
// 枚舉類型需要先定義才能進行使用 // 枚舉類型 定義 enum PhoneType { MOBILE = 0; HOME = 1; WORK = 2; // 電話類型字段 只能從 這個集合里 取值 } // 特別注意: // 1. 枚舉類型的定義可在一個消息對象的內部或外部 // 2. 都可以在 同一.proto文件 中的任何消息對象里使用 // 3. 當枚舉類型是在一消息內部定義,希望在 另一個消息中 使用時,需要采用MessageType.EnumType的語法格式 message PhoneNumber { required string number = 1; optional PhoneType type = 2 [default = HOME]; // 使用枚舉類型的字段(設置了默認值) } // 特別注意: // 1. 枚舉常量必須在32位整型值的范圍內 // 2. 不推薦在enum中使用負數:因為enum值是使用可變編碼方式的,對負數不夠高
額外說明
當對一個 使用了枚舉類型的.proto文件 使用 Protocol Buffer編譯器編譯時,生成的代碼文件中:
- 對
Java 或 C++來說,將有一個對應的enum文件 - 對
Python來說,有一個特殊的EnumDescriptor類被用來在運行時生成的類中創建一系列的整型值符號常量(symbolic constants)
3. 消息對象 類型
一個消息對象 可以將 其他消息對象類型 用作字段類型,情況如下:
3.1 使用同一個 .proto 文件里的消息類型
a. 使用 內部消息類型
- 目的:先在 消息類型 中定義 其他消息類型 ,然后再使用
即嵌套,需要 用作字段類型的 消息類型 定義在 該消息類型里
- 實例:
message Person { required string name = 1; required int32 id = 2; optional string email = 3; // 該消息類型 定義在 Person消息類型的內部 // 即Person消息類型 是 PhoneNumber消息類型的父消息類型 message PhoneNumber { required string number = 1; } repeated PhoneNumber phone = 4; // 直接使用內部消息類型 }
b. 使用 外部消息類型
即外部重用,需要 用作字段類型的消息類型 定義在 該消息類型外部
message Person { required string name = 1; required int32 id = 2; optional string email = 3; } message AddressBook { repeated Person person = 1; // 直接使用了 Person消息類型作為消息字段 }
c. 使用 外部消息的內部消息類型
message Person { required string name = 1; required int32 id = 2; optional string email = 3; // PhoneNumber消息類型 是 Person消息類型的內部消息類型 message PhoneNumber { required string number = 1; optional PhoneType type = 2 [default = HOME]; } } // 若父消息類型外部的消息類型需要重用該內部消息類型 // 需要以 Parent.Type 的形式去使用 // Parent = 需要使用消息類型的父消息類型,Type = 需要使用的消息類型 // PhoneNumber父消息類型Person 的外部 OtherMessage消息類型 需要使用 PhoneNumber消息類型 message OtherMessage { optional Person.PhoneNumber phonenumber = 1; // 以 Parent.Type = Person.PhoneNumber 的形式去使用 }
3.2 使用不同 .proto 文件里的消息類型
- 目的:需要在
A.proto文件 使用B.proto文件里的消息類型 - 解決方案:在
A.proto文件 通過導入(import)B.proto文件中來使用B.proto文件 里的消息類型
import "myproject/other_protos.proto" // 在A.proto 文件中添加 B.proto文件路徑的導入聲明 // ProtocolBuffer編譯器 會在 該目錄中 查找需要被導入的 .proto文件 // 如果不提供參數,編譯器就在 其調用的目錄下 查找
當然,在使用 不同 .proto 文件里的消息類型 時 也會存在想 使用同一個 .proto 文件消息類型的情況,但使用都是一樣,此處不作過多描述。
3.3 將 消息對象類型 用在 RPC(遠程方法調用)系統
- 解決方案:在
.proto文件中定義一個RPC服務接口,Protocol Buffer編譯器會根據所選擇的不同語言平台 生成服務接口代碼 - 由於使用得不多,此處不作過多描述,具體請看該文檔
c. 字段名
該字段的名稱,此處不作過多描述。
d. 標識號
-
作用:通過二進制格式唯一標識每個字段
- 一旦開始使用就不能夠再改變
- 標識號使用范圍:[1,2的29次方 - 1]
- 不可使用 [19000-19999] 標識號, 因為
Protobuf協議實現中對這些標識號進行了預留。假若使用,則會報錯
-
編碼占有內存規則:
每個字段在進行編碼時都會占用內存,而 占用內存大小 取決於 標識號:- 范圍 [1,15] 標識號的字段 在編碼時占用1個字節;
- 范圍 [16,2047] 標識號的字段 在編碼時占用2個字節
-
使用建議
- 為頻繁出現的 消息字段 保留 [1,15] 的標識號
- 為將來有可能添加的、頻繁出現的 消息字段預留 [1,15] 標識號
關於 字段 的高級用法
1. 更新消息對象 的字段
- 目的:為了滿足新需求,需要更新 消息類型 而不破壞已有消息類型代碼
即新、老版本需要兼容
- 更新字段時,需要符合下列規則:
2. 擴展消息對象 的字段
- 作用:使得其他人可以在自己的
.proto文件中為 該消息對象 聲明新的字段而不必去編輯原始文件- 注:擴展 可以是消息類型也可以是字段類型
- 以下以 擴展 消息類型 為例
A.proto
message Request { … extensions 100 to 199; // 將一個范圍內的標識號 聲明為 可被第三方擴展所用 // 在消息Request中,范圍 [100,199] 的標識號被保留為擴展用 // 如果標識號需要很大的數量時,可以將可擴展標符號的范圍擴大至max // 其中max是2的29次方 - 1(536,870,911)。 message Request { extensions 1000 to max; // 注:請避開[19000-19999] 的標識號,因為已被Protocol Buffers實現中預留 }
現在,其他人 就可以在自己的 .proto文件中 添加新字段到Request里。如下:
B.proto
extend Request {
optional int32 bar = 126; // 添加字段的 標識號必須要在指定的范圍內 // 消息Request 現在有一個名為 bar 的 optional int32 字段 // 當Request消息被編碼時,數據的傳輸格式與在Request里定義新字段的效果是完全一樣的 // 注:在同一個消息類型中一定要確保不會擴展新增相同的標識號,否則會導致數據不一致;可以通過為新項目定義一個可擴展標識號規則來防止該情況的發生 }
- 要訪問 擴展字段 的方法與 訪問普通的字段 不同:使用專門的擴展訪問函數
- 實例:
// 如何在C++中設置 bar 值 Request request; request.SetExtension(bar, 15); // 類似的模板函數 HasExtension(),ClearExtension(),GetExtension(),MutableExtension(),以及 AddExtension() // 與對應的普通字段的訪問函數相符
嵌套的擴展
可以在另一個 消息對象里 聲明擴展,如:
message Carson { extend Request { optional int32 bar = 126; } … } // 訪問此擴展的C++代碼: Request request; request.SetExtension(Baz::bar, 15);
- 對於嵌套的使用,一般的做法是:在擴展的字段類型的范圍內定義該擴展
- 實例:一個 Request 消息對象需要擴展(擴展的字段類型是Car 消息類型),那么,該擴展就定義在 Car消息類型 里:
message Car { extend Request { optional Car request_ext = 127; // 注:二者並沒有子類、父類的關系 } }
- 至此,
Protoco Buffer的語法已經講解完畢 - 關於如何根據需求 通過
Protoco Buffer語法 去構建 數據結構 相信大家已經非常熟悉了。 - 在將
.proto文件保存后,進入下一個步驟
步驟2:通過 Protocol Buffer 編譯器 編譯 .proto 文件
- 作用:將
.proto文件 轉換成 對應平台的代碼文件Protoco Buffer提供C++、Java、Python三種開發語言的 API - 具體生成文件與平台有關:
- 編譯指令說明
// 在 終端 輸入下列命令進行編譯 protoc -I=$SRC_DIR --xxx_out=$DST_DIR $SRC_DIR/addressbook.proto // 參數說明 // 1. $SRC_DIR:指定需要編譯的.proto文件目錄 (如沒有提供則使用當前目錄) // 2. --xxx_out:xxx根據需要生成代碼的類型進行設置 // 對於 Java ,xxx = java ,即 -- java_out // 對於 C++ ,xxx = cpp ,即 --cpp_out // 對於 Python,xxx = python,即 --python_out // 3. $DST_DIR :編譯后代碼生成的目錄 (通常設置與$SRC_DIR相同) // 4. 最后的路徑參數:需要編譯的.proto 文件的具體路徑 // 編譯通過后,Protoco Buffer會根據不同平台生成對應的代碼文件
- 具體實例
// 編譯說明 // 1. 生成Java代碼 // 2. 需要編譯的.proto文件在桌面,希望編譯后生成的代碼也放在桌面 protoc -I=/Users/Carson_Ho/Desktop --java_out=/Users/Carson_Ho/Desktop /Users/Carson_Ho/Desktop/Demo.proto // 編譯通過后,Protoco Buffer會按照標准Java風格,生成Java類及目錄結構
在指定的目錄能看到一個Demo的包文件(含 java類文件)
編譯功能的拓展
a. 使用Android Studio插件進行編譯
- 需求場景:每次手動執行
Protocol Buffer編譯器將.proto文件轉換為Java文件 操作不方便 - 解決方案:使用
Android Studio的gradle插件protobuf-gradle-plugin,以便於在項目編譯時 自動執行Protocol Buffers 編譯器
關於protobuf-gradle-plugin插件有興趣的讀者可自行了解,但個人還是建議使用 命令行,畢竟太過折騰插件沒必要
b. 動態編譯
- 需求場景:某些情況下,人們無法預先知道 .proto 文件,他們需要動態處理一些未知的 .proto 文件
如一個通用的消息轉發中間件,它無法預先知道需要處理什么類型的數據結構消息
- 解決方案:動態編譯
.proto文件
由於使用得不多,此處不作過多描述,具體請看官方文檔
c. 編寫新的 .proto 編譯器
- 需求場景:
Protocol Buffer僅支持C++、java 和 Python三種開發語言,一旦超出該三種開發語言,Protocol Buffer將無法使用 - 解決方案:使用
Protocol Buffer的Compiler包 開發出支持其他語言的新的.proto編譯器
由於使用得不多,此處不作過多描述,具體請看官方文檔
5.3 應用到具體平台(Android平台)
- 終於到了應用到具體平台項目中的步驟了。
此處以
Android平台 為例 - 具體步驟如下:
步驟1:將生成的 代碼文件 放入到項目中
- 對於
Android(Java)平台,即將編譯.proto文件生成的Java包文件 整個復制到Android項目中 - 放置路徑:
app/src/main/java的文件夾里
步驟2:在 Gradle 添加 Protocol Buffer 版本依賴
compile 'com.google.protobuf:protobuf-java:2.6.1' // 注:protobuf-java的版本 一定要和 安裝protocobuffer的版本 一致
步驟3:具體在Android項目中使用
3.1 消息對象類介紹
通過.proto文件 轉換的 Java源代碼 = Protocol Buffer 類 + 消息對象類(含Builder內部類)
消息對象類 是
Protocol Buffer類的內部類
由於最常用的都是 消息對象類 和其內部類Builder類 的方法&成員變量,所以此處主要講解這兩者。
3.1.1 消息對象類(Message類)
- 消息對象類 類通過 二進制數組 寫 和 讀 消息類型
- 使用方法包括:
<-- 方式1:直接序列化和反序列化 消息 --> protocolBuffer.toByteArray(); // 序列化消息 並 返回一個包含它的原始字節的字節數組 protocolBuffer.parseFrom(byte[] data); // 從一個字節數組 反序列化(解析) 消息 <-- 方式2:通過輸入/ 輸出流(如網絡輸出流) 序列化和反序列化消息 --> protocolBuffer.writeTo(OutputStream output); output.toByteArray(); // 將消息寫入 輸出流 ,然后再 序列化消息 protocolBuffer.parseFrom(InputStream input); // 從一個 輸入流 讀取並 反序列化(解析)消息 // 只含包含字段的getters方法 // required string name = 1; public boolean hasName();// 如果字段被設置,則返回true public java.lang.String getName(); // required int32 id = 2; public boolean hasId(); public int getId(); // optional string email = 3; public boolean hasEmail(); public String getEmail(); // repeated .tutorial.Person.PhoneNumber phone = 4; // 重復(repeated)字段有一些額外方法 public List<PhoneNumber> getPhoneList(); public int getPhoneCount(); // 列表大小的速記 // 作用:通過索引獲取和設置列表的特定元素的getters和setters
常用的如上,更多請看官方文檔
3.1.2 Builder類
作用:創建 消息構造器 & 設置/ 獲取消息對象的字段值 & 創建 消息類 實例
屬於 消息對象類 的內部類
a. 創建 消息構造器
Demo.Person.Builder person = Person.newBuilder();
b. 設置/ 獲取 消息對象的字段值 具體方法如下:
// 標准的JavaBeans風格:含getters和setters // required string name = 1; public boolean hasName();// 如果字段被設置,則返回true public java.lang.String getName(); public Builder setName(String value); public Builder clearName(); // 將字段設置回它的空狀態 // required int32 id = 2; public boolean hasId(); public int getId(); public Builder setId(int value); public Builder clearId(); // optional string email = 3; public boolean hasEmail(); public String getEmail(); public Builder setEmail(String value); public Builder clearEmail(); // repeated .tutorial.Person.PhoneNumber phone = 4; // 重復(repeated)字段有一些額外方法 public List<PhoneNumber> getPhoneList(); public int getPhoneCount(); // 列表大小的速記 // 作用:通過索引獲取和設置列表的特定元素的getters和setters public PhoneNumber getPhone(int index); public Builder setPhone(int index, PhoneNumber value); public Builder addPhone(PhoneNumber value); // 將新元素添加到列表的末尾 public Builder addAllPhone(Iterable<PhoneNumber> value); // 將一個裝滿元素的整個容器添加到列表中 public Builder clearPhone(); public Builder isInitialized() // 檢查所有 required 字段 是否都已經被設置 public Builder toString() : // 返回一個人類可讀的消息表示(用於調試) public Builder mergeFrom(Message other) // 將 其他內容 合並到這個消息中,覆寫單數的字段,附接重復的。 public Builder clear() // 清空所有的元素為空狀態。
3.2 具體使用
-
使用步驟如下:
步驟1:通過 消息類的內部類Builder類 構造 消息構造器
步驟2:通過 消息構造器 設置 消息字段的值
步驟3:通過 消息構造器 創建 消息類 對象
步驟4:序列化 / 反序列化 消息 -
具體使用如下:(注釋非常清晰)
public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // 步驟1:通過 消息類的內部類Builder類 構造 消息類的消息構造器 Demo.Person.Builder personBuilder = Demo.Person.newBuilder(); // 步驟2:設置你想要設置的字段為你選擇的值 personBuilder.setName("Carson");// 在定義.proto文件時,該字段的字段修飾符是required,所以必須賦值 personBuilder.setId(123);// 在定義.proto文件時,該字段的字段修飾符是required,所以必須賦值 personBuilder.setEmail("carson.ho@foxmail.com"); // 在定義.proto文件時,該字段的字段修飾符是optional,所以可賦值 / 不賦值(不賦值時將使用默認值) Demo.Person.PhoneNumber.Builder phoneNumber = Demo.Person.PhoneNumber.newBuilder(); phoneNumber.setType( Demo.Person.PhoneType.HOME);// 直接采用枚舉類型里的值進行賦值 phoneNumber.setNumber("0157-23443276"); // PhoneNumber消息是嵌套在Person消息里,可以理解為內部類 // 所以創建對象時要通過外部類來創建 // 步驟3:通過 消息構造器 創建 消息類 對象 Demo.Person person = personBuilder.build(); // 步驟4:序列化和反序列化消息(兩種方式) /*方式1:直接 序列化 和 反序列化 消息 */ // a.序列化 byte[] byteArray1 = person.toByteArray(); // 把 person消息類對象 序列化為 byte[]字節數組 System.out.println(Arrays.toString(byteArray1)); // 查看序列化后的字節流 // b.反序列化 try { Demo.Person person_Request = Demo.Person.parseFrom(byteArray1); // 當接收到字節數組byte[] 反序列化為 person消息類對象 System.out.println(person_Request.getName()); System.out.println(person_Request.getId()); System.out.println(person_Request.getEmail()); // 輸出反序列化后的消息 } catch (IOException e) { e.printStackTrace(); } /*方式2:通過輸入/ 輸出流(如網絡輸出流) 序列化和反序列化消息 */ // a.序列化 ByteArrayOutputStream output = new ByteArrayOutputStream(); try { person.writeTo(output); // 將消息序列化 並寫入 輸出流(此處用 ByteArrayOutputStream 代替) } catch (IOException e) { e.printStackTrace(); } byte[] byteArray = output.toByteArray(); // 通過 輸出流 轉化成二進制字節流 // b. 反序列化 ByteArrayInputStream input = new ByteArrayInputStream(byteArray); // 通過 輸入流 接收消息流(此處用 ByteArrayInputStream 代替) try { Demo.Person person_Request = Demo.Person.parseFrom(input); // 通過輸入流 反序列化 消息 System.out.println(person_Request.getName()); System.out.println(person_Request.getId()); System.out.println(person_Request.getEmail()); // 輸出消息 } catch (IOException e) { e.printStackTrace(); } } }
Demo 地址
Carson_Ho的Github :https://github.com/Carson-Ho/ProtocolBuffer
高級功能
-
貼心的Google還提供將
Protocol Buff編碼方式 轉化為 其他編碼方式,如Json、XML等等即將
Protocol Buff對象 轉化為其他編碼方式的數據存儲對象 -
下面展示的是 將
Protocol Buff對象 轉化為Json對象
// 步驟1:在Gradle加入依賴 compile 'com.googlecode.protobuf-java-format:protobuf-java-format:1.4' // 步驟2:將`Protocol Buff` 對象 序列化 為 `Json`對象 JsonFormat jsonFormat = new JsonFormat(); String person2json = jsonFormat.printToString(mProtoBuffer);
6. 總結
- 在 傳輸數據量較大的需求場景下,
Protocol Buffer比XML、Json更小、更快、使用 & 維護更簡單! - 下面用 一張圖 總結在 Android平台中使用
Protocol Buffer的整個步驟流程:
