protobuf的java库比较大,为满足android移动设备在内存、性能等各方面的要求,google也推出了android定制版protobuf-lite库。
使用步骤
- 在 .proto 文件中定义消息格式。
语法学习可参考官方文档。
语法有syntax2 syntax3的区分,可在书写schema的时候声明用哪个版本,3相对2来说有更好的压缩特性。 - 使用 Protocol Buffer 编译器编译生成所需的java文件。
编译器生成及用法参见http://www.jianshu.com/p/e8712962f0e9
每次手动执行 Protocol Buffers 编译器将 .proto 文件转换为Java文件显然有点太麻烦,因此google提供了一个Android Studio gradle插件 protobuf-gradle-plugin ,以便于在我们项目的编译期间自动地执行 Protocol Buffers 编译器。 -
使用Java Protocol Buffer API读写消息。
整个使用过程如下图
下面就以Demo展示protobuf-gradle-plugin+protobuf-lite库实现消息序列化和反序列化的过程。
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'
}
}
}
//编写编译任务,调用plugin编译生成java文件
protobuf {
protoc {
artifact = 'com.google.protobuf:protoc:3.0.0'//编译器版本
}
plugins {
javalite {
artifact = 'com.google.protobuf:protoc-gen-javalite:3.0.0'//指定当前工程使用的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库
}
工程目录如下:
关于protobuf-gradle-plugin的更多用法可参考官方文档https://github.com/google/protobuf-gradle-plugin
schema编写(.proto文件)
我们将以下Json示例转为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已经将上面json示例的层次结构体现的很明显了。简单解释一下各参数的意义及用法:
- syntax指定用哪个版本的语法,proto3比2有更好的压缩特性
- package指定编译生成java类的包名
- java_outer_classname指定java类的类名
- 修饰符:required表示必填,optional可选,repeated表示重复的list
- 每个层级内的数据要按顺序编号,也就是上一篇文中讲的field_number
- 每个结构体是一个message,message之间可以互相引用。
编译
build工程,可以看到在java包下生成了debug目录(由于当前是debug模式),再下面就是我们想要的包及schema编译后的java文件。
使用编译生成的java类,就可以进行数据的序列化和反序列化了。
序列化操作
构造一个response数据并写入文件
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
//构造builder
ResponsePB.Response.Builder responseBuilder = ResponsePB.Response.newBuilder();
//填充数据
responseBuilder.setHasNextPage(resultJson.hasNextPage);
responseBuilder.setDirtag(resultJson.dirtag);
...//此处省略若干行
//结束 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();
}
}
用法很简单,生成对象的构造器builder,用提供的各种Set方法填充数据,最后build,二进制数据就生成了。用法跟普通的pojo类没有区别。
反序列化
将文件中的数据解析到Response对象中
public static void parseXinruiPb(byte[] bytes) {
ResponsePB.Response response = ResponsePB.Response.parseFrom(bytes);
boolean hasNextPage = response.getHasNextPage();
String dirtag = response.getDirtag();
...
}
用起来也非常简单,parseFrom搞定。 值得一提的是,由于protobuf的存储结构决定了它在进行数据解析的时候必须将整个数据完整解析一遍才能得到你想要的数据,也就是数据传输过程中所谓的封包-解析过程,这与json解析的过程类似,区别在于它对key键的特殊编码,省去了字符匹配的过程。
更高级的用法--动态编译
Protobuf 提供了 google::protobuf::compiler 包来完成动态编译的功能。感兴趣的同学可以自行研究。