文章目录
什么是 Protocol buffer
它是用于对结构化数据进行序列化的一种灵活、高效、自动化的机制——类似XML,但是更小、更快、更简单。您可以定义数据的结构化方式,然后使用特殊生成的源代码轻松地在各种数据流和使用的各种语言之间读写结构化数据。
对于使用 Java, C++, 或 Python 等多种语言 的开发者,想要进行结构化数据的序列化,并用于通信或存储的场景时,可以采用。
使用平台无关的特定语法建立 ".proto"文件,来描述结构化数据;通过相应工具生成相关语言平台的结构化数据文件,在通信时可以调用相关函数,转成二进制数据进行传递。
参考
下载
https://github.com/protocolbuffers/protobuf/releases/
主要用到 bin/protc 命令;将其加入环境变量
定义.proto 文件
创建一个名为 test.proto 的文件:
syntax = "proto3"; //使用3.x 版语法
package com.stone.bean; //生成文件将在这个包名下,包名目录不存在将自动创建
//常量
enum Version {
VERSION_NAME = 0;
VERSION_CODE = 1;
}
message VersionCheck {
Version curVersion = 1;
Version newVersion = 2;
}
message User {
int32 uid = 1;
string uname = 2;
bool active = 3;
float weight = 4;
repeated Role roles = 5;
map<int32, string> subUser = 6;
}
message Role {
int32 rid = 1;
string rname = 2;
}
enum 类型,会转成 java的 enum 类型;定义的数据标号需要从0开始。
message 类型,会转成 java 的 class;定义的数据标号需要从1开始。
message 内部定义
- 关于上文中的 repeated 类型,对应一个List集合。
repeated Role roles = 3;
===>List<Role> roles_;
map<int32, string> subUser
===>Map<int, String> subUser_
; - 内部,还可以定义 enum,即在 java 中是一个内部类的形式。
当然,也可以嵌套 message。
option 选项
- java_outer_classname 指定输出
option java_outer_classname = "MyTest";
指定输出文件名;若不指定,则以.proto 文件名为类名。 - java_multiple_files 是否生成多文件
option java_multiple_files = true;
默认为 false,即将只生成一个类文件,所有定义的类、枚举等都以内部类形式定义; 为 true,则生成多个 java 文件 - optimize_for 优化方式
option optimize_for = SPEED;
有三种可选值:SPEED, CODE_SIZE, LITE_RUNTIME
SPEED 是默认值
CODE_SIZE 生成代码比 SPEED 少,但操作效率低
LITE_RUNTIME 生成的代码也少,且生成的类不再继承 GeneratedMessageV3,而是继承一个更轻量级的类 GeneratedMessageLite; 这个非常适合移动端;但在将来,代码生成工具protoc
将忽略它,建议使用Java Lite plugin
: `` ,
转成 java 文件
转换命令:
protoc --proto_path=. --java_out=. test.proto
或
protoc -I. --java_out=. test.proto
第一条命令中 --proto_path=
可以简写成-I
。
--proto_path=
指定 .proto
文件目录。 "."表示当前目录下(即执行命令的目录)。
--java_out=
指定输出目录。
最后 跟上 proto 文件名。
若要转多语言平台的文件,命令为:
protoc --proto_path=IMPORT_PATH --cpp_out=DST_DIR --java_out=DST_DIR --python_out=DST_DIR --go_out=DST_DIR --ruby_out=DST_DIR --objc_out=DST_DIR --csharp_out=DST_DIR path/to/file.proto
转换后,java 类中的常量用方法
- enum
getNumber();
返回当前枚举对象对应的 int 值
forNumber(int value);
返回枚举对象 - message
每个 message 对应的 java类中,都生成了一个静态内部类 Builder;
有一个静态方法newBuilder()
来创建 Builder 对象。
Builder 对象有getter/setter方法,创建对象时调用 setter 方法,最后调用builder.build();
返回 message 对应的类的实例对象。
message 类,公开了 getter 方法;还有如下两个重要的方法:
parseFrom(byte[] data)
该方法有一系重载,通常用含有这个参数的就可以了;表示从字节数组中返回 message 对应类的对象;
toByteArray();
将对象转成字节数组;
示例:
/*创建 user 对象*/
MyTest.User user = MyTest.User.newBuilder()
.setActive(false)
// .set ... 其它 set 方法
.build();
//转 byte 数组
byte[] bytes1 = user.toByteArray();
//ByteString是 protobuf 中定义的,通常不使用它;
//ByteString bytes2 = user.toByteString();
//从 byte 数组,转成 user 对象;并获取user 对象属性
try {
MyTest.User user1 = MyTest.User.parseFrom(bytes1);
// MyTest.User user2 = MyTest.User.parseFrom(bytes2);
boolean active = user.getActive();
} catch (InvalidProtocolBufferException e) {
e.printStackTrace();
}
自定义 java bean 封装 message 类
实际场景中,protocol buffer 中的 java bean,可能不能直接使用;
因为,可能我们有些本地的属性需要定义;可能使用数据库框架,或其它框架需要对 java bean 的类或属性添加注解等等。
所以,需要自行定义java bean
例:
public class UserBean {
boolean active;
public boolean isActive() {
return active;
}
public void setActive(boolean active) {
this.active = active;
}
public UserBean() {
}
public byte[] toByteArray() {
return MyTest.User.newBuilder()
.setActive(active)
.build()
.toByteArray();
}
public UserBean parseFrom(byte[] data) throws InvalidProtocolBufferException {
MyTest.User user = MyTest.User.parseFrom(data);
UserBean userBean = new UserBean();
userBean.setActive(user.getActive());
return userBean;
}
}
android 使用 gradle 插件实现 protocol buffer lite
前文说了,可以使用protoc 工具,手动转换 ".proto"文件。
这里使用 gradle 的插件进行转换,且转出的是 对应 option optimize_for = LITE_RUNTIME
的 java 文件。
gradle插件地址
gradle 配置
- project-path/build.gradle
buildscript {
dependencies {
...
classpath 'com.google.protobuf:protobuf-gradle-plugin:0.8.7'
}
}
添加在这里对所有 module 都有效;
- app-path/build.gradle
apply plugin: 'com.android.application'
apply plugin: 'com.google.protobuf'
//若不在 project-path/build.gradle下配置 classpath,需要添加如下
buildscript {
repositories {
maven { url "https://plugins.gradle.org/m2/" }
}
dependencies {//只会从上面定义的仓库中去查找 classpath
classpath 'com.google.protobuf:protobuf-gradle-plugin:0.8.7'
}
}
android {
...
sourceSets {
main {
proto {//配置.proto 文件目录
srcDir 'src/main/protobuf'
}
}
}
dependencies {
...
//仅添加这个 lite 版即可
implementation 'com.google.protobuf:protobuf-lite:3.0.1'
}
protobuf {
protoc {
// The artifact spec for the Protobuf Compiler. Download from repositories
artifact = 'com.google.protobuf:protoc:3.6.1'
//or specify a local path
// path = '/usr/local/bin/protoc'
}
plugins {
javalite {
// The codegen for lite comes as a separate artifact
artifact = 'com.google.protobuf:protoc-gen-javalite:3.0.0'
}
}
generateProtoTasks {
all().each { task ->
task.builtins {
// In most cases you don't need the full Java output
// if you use the lite output.
remove java
}
task.plugins {
javalite { }
}
task.generateDescriptorSet = true
task.descriptorSetOptions.path =
"${projectDir}/build/descriptors/${task.name}.dsc"
task.descriptorSetOptions.includeSourceInfo = true
task.descriptorSetOptions.includeImports = true
}
// (Android-only selectors) 即何种条件,产生 protobuf 对应的源码文件
// Returns tasks for a flavor
ofFlavor('demo')
// Returns tasks for a buildType
ofBuildType('release')
ofBuildType('debug')
// Returns tasks for a variant
ofVariant('demoRelease')
// Returns non-androidTest tasks
ofNonTest()
// Return androidTest tasks
ofTest()
}
}
编译生成java文件
编译后,生成的 java 文件在:app/build/generated/source/proto 下。