protobuf的java庫比較大,為滿足android移動設(shè)備在內(nèi)存、性能等各方面的要求,google也推出了android定制版protobuf-lite庫。
使用步驟
- 在 .proto 文件中定義消息格式。
語法學(xué)習(xí)可參考官方文檔。
語法有syntax2 syntax3的區(qū)分,可在書寫schema的時候聲明用哪個版本,3相對2來說有更好的壓縮特性。 - 使用 Protocol Buffer 編譯器編譯生成所需的java文件。
編譯器生成及用法參見http://www.itdecent.cn/p/e8712962f0e9
每次手動執(zhí)行 Protocol Buffers 編譯器將 .proto 文件轉(zhuǎn)換為Java文件顯然有點太麻煩,因此google提供了一個Android Studio gradle插件 protobuf-gradle-plugin ,以便于在我們項目的編譯期間自動地執(zhí)行 Protocol Buffers 編譯器。 -
使用Java Protocol Buffer API讀寫消息。
整個使用過程如下圖
下面就以Demo展示protobuf-gradle-plugin+protobuf-lite庫實現(xiàn)消息序列化和反序列化的過程。
gradle集成protobuf-gradle-plugin
app module的gradle腳本如下:
apply plugin: 'com.android.application'
apply plugin: 'com.google.protobuf' //在gradle腳本開始處聲明依賴的插件
buildscript {
repositories {
mavenCentral()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:2.2.3'
classpath 'com.google.protobuf:protobuf-gradle-plugin:0.8.0'//配置plugin的版本信息
}
}
android {
compileSdkVersion 25
buildToolsVersion "25.0.2"
defaultConfig {
applicationId "com.dy.testprotocolbuffer"
minSdkVersion 15
targetSdkVersion 25
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
//編寫編譯任務(wù),調(diào)用plugin編譯生成java文件
protobuf {
protoc {
artifact = 'com.google.protobuf:protoc:3.0.0'//編譯器版本
}
plugins {
javalite {
artifact = 'com.google.protobuf:protoc-gen-javalite:3.0.0'//指定當(dāng)前工程使用的protobuf版本為javalite版,以生成javalite版的java類
}
}
generateProtoTasks.generatedFilesBaseDir = "$projectDir/src/main/java" //指定編譯生成java類的存放位置
generateProtoTasks {
all().each { task ->
task.plugins {
javalite {
outputSubDir = '' //指定存放位置的二級目錄,這里未指定
}
}
}
}
}
//指定原始.proto文件的位置
android {
sourceSets {
main {
java {
srcDirs 'src/main/java'
}
proto {
srcDirs 'src/main/proto'
}
}
}
}
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
exclude group: 'com.android.support', module: 'support-annotations'
})
compile 'com.android.support:appcompat-v7:25.1.0'
compile 'com.google.protobuf:protobuf-lite:3.0.0' //依賴protobuf-lite庫
}
工程目錄如下:

關(guān)于protobuf-gradle-plugin的更多用法可參考官方文檔https://github.com/google/protobuf-gradle-plugin
schema編寫(.proto文件)
我們將以下Json示例轉(zhuǎn)為pb格式:
{
"data":[
{
"datatype":1,
"itemdata":
{//共有字段45個
"sname":"\u5fae\u533b",
"packageid":"330611",
…
"tabs":[
{
"type":1,
"f":"abc"
},
…
]
}
},
…
],
"hasNextPage":true,
"dirtag":"soft"
}
schema編寫如下:
syntax = "proto2";
package com.dy.messagepackdemo.protobuffer.model;
option java_package = "com.dy.messagepackdemo.protobuffer.model";
option java_outer_classname = "ResponsePB";
message Tab {
required int32 type = 1;
optional string f = 2;
}
message ItemData {
required string sname = 1;
required string packageid = 2;
...
repeated Tab tabs = 45;
}
message DataItem {
required int32 datatype = 1;
required ItemData itemdata = 2;
}
message ResponsePB {
repeated DataItem data = 1;
required bool hasNextPage = 2;
required string dirtag = 3;
}
這個schema已經(jīng)將上面json示例的層次結(jié)構(gòu)體現(xiàn)的很明顯了。簡單解釋一下各參數(shù)的意義及用法:
- syntax指定用哪個版本的語法,proto3比2有更好的壓縮特性
- package指定編譯生成java類的包名
- java_outer_classname指定java類的類名
- 修飾符:required表示必填,optional可選,repeated表示重復(fù)的list
- 每個層級內(nèi)的數(shù)據(jù)要按順序編號,也就是上一篇文中講的field_number
- 每個結(jié)構(gòu)體是一個message,message之間可以互相引用。
編譯
build工程,可以看到在java包下生成了debug目錄(由于當(dāng)前是debug模式),再下面就是我們想要的包及schema編譯后的java文件。

使用編譯生成的java類,就可以進行數(shù)據(jù)的序列化和反序列化了。
序列化操作
構(gòu)造一個response數(shù)據(jù)并寫入文件
public static void writeResponseToPbFile(String pbfilepath, ResponseJson responseJson) {
File fproto = new File(pbfilepath);
if (!fproto.exists()) {
try {
fproto.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
}
//build response
//構(gòu)造builder
ResponsePB.Response.Builder responseBuilder = ResponsePB.Response.newBuilder();
//填充數(shù)據(jù)
responseBuilder.setHasNextPage(resultJson.hasNextPage);
responseBuilder.setDirtag(resultJson.dirtag);
...//此處省略若干行
//結(jié)束 build
ResponsePB.Response response = responseBuilder.build();
//寫文件
try {
FileOutputStream foProto = new FileOutputStream(fproto);
response.writeTo(foProto);
foProto.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
用法很簡單,生成對象的構(gòu)造器builder,用提供的各種Set方法填充數(shù)據(jù),最后build,二進制數(shù)據(jù)就生成了。用法跟普通的pojo類沒有區(qū)別。
反序列化
將文件中的數(shù)據(jù)解析到Response對象中
public static void parseXinruiPb(byte[] bytes) {
ResponsePB.Response response = ResponsePB.Response.parseFrom(bytes);
boolean hasNextPage = response.getHasNextPage();
String dirtag = response.getDirtag();
...
}
用起來也非常簡單,parseFrom搞定。 值得一提的是,由于protobuf的存儲結(jié)構(gòu)決定了它在進行數(shù)據(jù)解析的時候必須將整個數(shù)據(jù)完整解析一遍才能得到你想要的數(shù)據(jù),也就是數(shù)據(jù)傳輸過程中所謂的封包-解析過程,這與json解析的過程類似,區(qū)別在于它對key鍵的特殊編碼,省去了字符匹配的過程。
更高級的用法--動態(tài)編譯
Protobuf 提供了 google::protobuf::compiler 包來完成動態(tài)編譯的功能。感興趣的同學(xué)可以自行研究。
