Markdown版本筆記 | 我的GitHub首頁 | 我的博客 | 我的微信 | 我的郵箱 |
---|---|---|---|---|
MyAndroidBlogs | baiqiantao | baiqiantao | bqt20094 | baiqiantao@sina.com |
protobuf Protocol Buffers 簡介 案例
目錄
簡介
優缺點
proto3簡介
使用步驟
下載 protobuf 編譯器
編寫 .proto 文件
編譯 .proto 文件生成 java 類
項目中添加 protobuf runtime
測試 java 類
在 Android 中使用 Java Lite runtime
簡介
Protocol Buffers
(a.k.a.,protobuf
) 是Google開發的一種用於序列化結構化數據
(比如Java中的Object,C中的Structure)的語言中立、平台中立、可擴展
的數據描述語言
,可用於數據存儲、通信協議等方面。- Protocol Buffers可以理解為是更快、更簡單、更小的JSON或者XML,區別在於Protocol Buffers是
二進制格式
,而JSON和XML是文本格式
。 - 目前 protobuf 支持的語言包括:
C++、C#、Java、JS、OC、PHP、Ruby
這七種。
優缺點
Protobuf 相對於 XML 的優點
- 簡潔,具有更少的歧義性
- 體積小,消息大小只需要XML的
1/10 ~ 1/3
- 速度快,解析速度比XML快
20 ~ 100
倍 - 自動生成數據訪問類,方便應用程序的使用
- 更好的兼容性,能夠很好的支持向下或向上兼容
注意:protobuf傳輸的是
對象的二進制內容
,而非編譯前的.proto文件
,更非編譯后的Java源文件
,.proto文件只是為了方便閱讀、調試和編輯用的。而json和xml傳輸的就是你看到的json和xml文本內容
(字符串)
Protobuf 的優點
- Protobuf 有如 XML,不過它
更小、更快、也更簡單
。你可以定義自己的數據結構,然后使用代碼生成器
生成的代碼來讀寫這個數據結構。你甚至可以在無需重新部署程序的情況下更新數據結構。只需使用 Protobuf 對數據結構進行一次描述,即可利用各種不同語言或從各種不同數據流中對你的結構化數據輕松讀寫。 - 它有一個非常棒的特性,即
向后兼容性好
,人們不必破壞已部署的、依靠“老”數據格式的程序就可以對數據結構進行升級。這樣您的程序就可以不必擔心因為消息結構的改變而造成的大規模的代碼重構或者遷移的問題。因為添加新的消息中的 field 並不會引起已經發布的程序的任何改變。 - Protobuf 語義更清晰,無需類似 XML
解析器
的東西,因為 Protobuf編譯器
會將 .proto 文件編譯生成對應的數據訪問類
以對 Protobuf 數據進行序列化、反序列化
操作。 - 使用 Protobuf 無需學習復雜的文檔對象模型,Protobuf 的編程模式比較友好,簡單易學,同時它擁有良好的文檔和示例,對於喜歡簡單事物的人們而言,Protobuf 比其他的技術更加有吸引力。
Protobuf 的不足
- Protbuf 與 XML 相比也有不足之處。它功能簡單,
無法用來表示復雜的概念
。 - XML 已經成為多種行業標准的編寫工具,Protobuf 只是 Google 公司內部使用的工具,在
通用性
上還差很多。 - Protobuf 不適合用來描述一個基於文本的標記型文檔(比如HTML),因為你無法輕易的交錯文本的結構。另外,XML具有很好的可讀性和可編輯性,而protocol buffers,至少在它們的原生形式上並不具備這個特點。XML同時也是可擴展、自描述的,而一個protocol buffer只有在具有message 定義(在.proto文件中定義)時才會有意義。
個人覺得在 Android 應用中,protocol buffer 有一個致命的缺點,那就是生成的 java 文件太大,會是我們的 apk 體積增大很多!例如,在我之前做過的 ogod 項目中,由pb生成的十幾個Java類,體積竟然有幾 MB ,其大小幾乎等於其余四百多個類的總和。
proto3簡介
我們最新的版本version 3 alpha release
引進了一個新的語言版本--Protocol Buffers version 3
(稱之為proto3
),它在我們現存的語言版本(proto2)上引進了一些新特性。proto3簡化了protocol buffer language,這使其可以更便於使用和支持更多的編程語言:我們現在的alpha release
版本可以讓你能產生JAVA、C++、Pthyon、JavaNano、Ruby、Objective-C和C#版本的protocol buffer code,不過可能有時會有一些局限性。另外,你可以使用最新的Go protoc
插件來產生Go語言版本的proto3 code,這可以從golang/protobuf
Github repository獲取。
以下情況下,我們現在只推薦你使用proto3:
- 如果你想嘗試在我們新支持的語言中使用protocol buffers
- 如果你想嘗試我們最新開源的
RPC
實現gRPC(目前仍處於alpha release版本),我們建議你為所有的 gRPC 服務器和客戶端都使用proto3以避免兼容性問題。
注意兩個版本的語言APIs並不是完全兼容的,為了避免給原來的用戶造成不便,我們將會繼續維護之前的那個proto2版本。
使用步驟
下載 protobuf 編譯器
下載路徑
注意這個東西在每個版本的下載部分的最下面,例如 protoc-3.6.1-win32.zip
它包含 protoc
二進制文件以及與 protobuf 一起分發的一組標准 .proto
文件。
解壓后可以把protoc.exe
所在的目錄加入系統變量Path,例如【C:\Android\protobuf\protoc-3.6.1-win32\bin】
然后可以在任何路徑測試protoc
命令是否可用,例如:
protoc --version
libprotoc 3.6.1
編寫 .proto 文件
可以在 PC 任意位置通過任意純文本編輯器編寫 .proto 文件,例如:
syntax = "proto3";//指定編譯時所使用的語法版本,不指定時會有 WARNING 提示:默認使用 proto2。
option java_package = "com.bqt.test.protomodel";//生成的類的【包名】
option java_outer_classname = "PersonEntity";//生成的類的【類名】
message Person { //真正用來封裝數據的【內部類】的類名
int32 id = 1;
string name = 2;
string email = 3;
}
編譯 .proto 文件生成 java 類
通過protoc
命令編譯.proto
文件生成java
類:
命令:protoc -I=目錄1 --java_out=目錄2 文件3
- 【-I=目錄1】:
輸入目錄
。這個參數是簡稱,全稱為:【--proto_path=目錄1】。可同時指定多個,目錄將按順序搜索。如果沒有給出,則使用當前工作目錄。 - 【--java_out=目錄2】:
輸出目錄
,指定生成的Java源文件的目錄。 - 【文件3】:
輸入文件
。- 如果指定了相對文件路徑,則將在工作目錄[working directory]中搜索該文件。
- 選項
--proto_path
不會影響搜索此參數文件的方式。 - 文件內容將在參數列表中的
@<filename>
位置展開。 - 請注意,shell擴展不會應用於文件的內容(即,您不能使用引號、通配符、轉義、命令[quotes, wildcards, escapes, commands]等)。
- 每行對應一個參數,即使它包含空格。
生成 Java 類時使用【java_out】,生成其他語言的結構化數據時只需更改這個參數即可,例如如果用在iOS上,可以使用【objc_out】,這就是其跨平台的體現。
使用案例:protoc
-I=D:\bqt\proto
--java_out=D:\bqt\out
test.proto
protoc -I=D:\bqt\proto --java_out=D:\bqt\out test.proto
如果在當前目錄執行命令,可以簡化為:protoc
--java_out=D:\bqt\out
test.proto
執行上面代碼后會發現,在指定的目錄下面生成了包含包名
的PersonEntity.java
類(雖然定義很簡單,但是生成的類有將近1000行代碼)。
項目中添加 protobuf runtime
確保 runtime 的版本號與 protoc 的版本號匹配(或者版本更新)。
方式一:下載源碼(下載路徑同上) ,下載后將需要的源碼連同包名
一起復制到項目中。
需要的源碼一般在以下目錄中:
protobuf-3.6.1\java\core\src\main\java\com\google\protobuf //核心包
protobuf-3.6.1\java\util\src\main\java\com\google\protobuf\util //可選包,提供 JsonFormat 等功能
方式二:直接添加protobuf依賴即可,版本號要比使用的 protoc.exe 版本相同或更高:
implementation 'com.google.protobuf:protobuf-java:3.6.1'//protobuf
//implementation 'com.google.protobuf:protobuf-lite:3.0.1'//protobuf lite
//implementation 'com.google.protobuf:protobuf-java-util:3.6.1'//可選,JsonFormat 等功能
測試 java 類
將上面生成的 java 類連同包名復制到項目中,然后就可以很方便的調用封裝好的Java類的API了。
public class MainActivity extends ListActivity {
private static final String PROTO_FILE_PATH = Environment.getExternalStorageDirectory().getAbsolutePath() + "/test_proto";
private static final String JSON_FILE_PATH = Environment.getExternalStorageDirectory().getAbsolutePath() + "/test_json";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
String[] array = {"以proto格式保存對象",
"解析proto方式保存的對象內容",
"以Json格式保存對象",
"解析son方式保存的對象內容",};
setListAdapter(new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, Arrays.asList(array)));
}
@Override
protected void onListItemClick(ListView l, View v, int position, long id) {
switch (position) {
case 0:
PersonEntity.Person protoPerson = PersonEntity.Person.newBuilder()
.setId(3)
.setName("zhangsan")
.setEmail("test@qq.com")
.build();
Log.i("bqt", "原始的proto格式對象內容:" + protoPerson.toString());
FileUtils.writeToFile(protoPerson.toByteArray(), PROTO_FILE_PATH);//以proto格式保存對象,保存的長度=25
break;
case 1:
byte[] protoFileBytes = FileUtils.readFromFile(PROTO_FILE_PATH);
try {
PersonEntity.Person parseProtoPerson = PersonEntity.Person.parseFrom(protoFileBytes);
Log.i("bqt", "解析proto方式保存的對象內容:" + parseProtoPerson.toString());
} catch (InvalidProtocolBufferException e) {
e.printStackTrace();
}
break;
case 2:
Person jsonPerson = Person.newBuilder().id(3).name("zhangsan").email("test@qq.com").build();
Log.i("bqt", "原始的Json格式對象內容:" + jsonPerson.toString());
FileUtils.writeToFile(new Gson().toJson(jsonPerson).getBytes(), JSON_FILE_PATH);//以Json格式保存對象,保存的長度=48
break;
case 3:
byte[] jsonFilebytes = FileUtils.readFromFile(JSON_FILE_PATH);
String json = new String(jsonFilebytes);
Person parseJsonPerson = new Gson().fromJson(json, Person.class);
Log.i("bqt", parseJsonPerson.toString());
break;
default:
break;
}
}
}
在 Android 中使用 Java Lite runtime
對於Android用戶,為了更小的代碼大小,建議使用 protobuf 的Java Lite runtime
。Java Lite runtime 也可以與Proguard
一起使用,因為它不依賴於Java反射
,並且已經過優化以允許盡可能多的代碼剝離[code stripping]。
要使用 Java Lite runtime,除了和上面一樣需要安裝 protobuf 編譯器外,還需要安裝 protoc-gen-javalite 插件。
同樣選擇適用於您的平台的版本,例如protoc-gen-javalite-3.0.0-windows-x86_64.exe
,然后將其重命名為protoc-gen-javalite.exe
,並在 PATH 中配置其路徑,建議將其和 protoc
放在同一目錄中:
之后,只需使用【javalite_out】代替【java_out】,您就可以和上面一樣編譯 .proto 文件生成 Java Lite 代碼,例如:
//protoc -I=D:\bqt\proto --java_out=D:\bqt\out test.proto
protoc -I=D:\bqt\proto --javalite_out=D:\bqt\out test.proto
對比兩次生成的文件我們發現,Java Lite runtime 生成的 Java 類雖然明顯比之前小了很多,但是仍舊比較大!
同樣,項目中應該添加 Java Lite 的源文件(或jar包)或添加 Java Lite 的依賴:
implementation 'com.google.protobuf:protobuf-lite:3.0.1'//protobuf lite
這里的版本號要比使用的 protoc 插件版本相同或更高(文檔中說只要大於3.0.0就可以)。
以下內容我還沒去研究
Use Protobuf Java Lite Runtime with Bazel:
Bazel has native build rules to work with protobuf。對於Java Lite運行時,您可以使用java_lite_proto_library規則。 查看我們的構建文件示例以了解如何使用它。
2018-8-17