Android中使用Protocol Buffer

Android中使用Protocol Buffer

Protocol Buffer 是啥

谷歌官方定义:Protocol Buffer 是一种轻便高效的结构化存储格式,使用于结构化数据序列化,适合做数据存储或者RPC数据交换格式。与语言无关,与平台无关,可扩展的序列化结构数据格式

比 json xml 更加高效

优点:

  • 序列化之后的体积比json和xml都小
  • 支持快平台,多语言
  • 消息格式升级和兼容性都不错
  • 序列化和反序列化速度很快

使用Protocol Buffer

1. 搭建Protocol Buffer环境

下载 https://github.com/protocolbuffers/protobuf/releases

Mac下安装Homebrew

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//安装依赖
brew install autoconf automack libtool curl
//运行字画生成脚本
cd xxx/protobuffer-3.xx
./autogen.sh
//运行配置脚本
./configure
//编译为编译的依赖包
make
//检查依赖包的完整性
make check
//安装 protocol buffer
make install
//检查是否安装完成
protoc --version

在AndroidStudio中使用插件的方式安装

Google提供了插件的方式,配置build.gradle

https://github.com/google/protobuf-gradle-plugin

在工程的build.gradle中

1
2
3
4
5
6
7
8
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath 'com.google.protobuf:protobuf-gradle-plugin:0.8.10'
}
}

在app的build.gradle中(每个版本的配置都不一样,这里是3.8.0)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
apply plugin: 'com.google.protobuf'

dependencies {
// You need to depend on the lite runtime library, not protobuf-java
//lite是轻量版
compile 'com.google.protobuf:protobuf-javalite:3.8.0'
}

protobuf {
protoc {
artifact = 'com.google.protobuf:protoc:3.8.0'
}
generateProtoTasks {
all().each { task ->
task.builtins {
java {
option "lite"
}
}
}
}
}

2. 定义.Proto文件(Android中在main文件夹下面创建proto文件夹在创建.proto文件) ,编译.Proto文件

构建消息

消息至少由一个字段组合而成

字段 = 字段修饰符 + 字段类型 + 字段名 + 标识号

  • 字段修饰符:设置该字段解析时的规则
  • 字段理性:基本类型 枚举类型 消息对象
  • 字段名:字段名称
  • 表示号:二进制格式唯一标识每个字段

字段默认值

Proto2支持声明默认值,Proto3使用以下规则

  • 对于string类型,默认值为空字符串
  • 对于byte类型,默认值为一个空byte数组
  • 对于bool类型,默认为false
  • 对于数值类型,默认值为0
  • 对于枚举类型,默认值为第一项,即值为0的那个枚举值
  • 对于引用其他message类型,默认值和对应的语言相关

标识号TAG

  • 对应同一个message中的字段,每个字段的Tag是必须唯一的数字
  • Tag主要用于说明字段在二进制文件中的对应关系,一旦指定字段为对应的Tag,不应该在后序进行变更
  • 对于Tag的分配。1~15只用一个byte进行编码(因此应该留给那些常用的字段),16~2047用两个byte进行编码,最大支持到536870911,但是中间有一段(19000~19999)是protobuf内部使用的
  • 可以通过reserved关键字来预留Tag和字段名,还有一种场景是如果某个字段已经被废弃,不希望后序被使用,也可以使用reserved关键字声明

.proto文件 例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
//指定是 不写默认proto23的语法 不写默认proto2
syntax = "proto3";

package tutorial;
//对应环境下的处理方式 这里是java中
option java_package = "com.sample.model";
option java_outer_classname= "AddressProto";

message Person{
string name = 1;
int32 id = 2;
string email = 3;
enum PhoneType {
MOBILE = 0;
HOME = 1;
WORK = 2;
}
message PhoneNumber {
string number = 1;
PhoneType type = 2;
}
repeated PhoneNumber phones = 4;
}
message Address {
//使用repeated关键字修饰 表示该字段可以重复多次赋值,包括0次,不指定表示0次或者1次,重复的值的顺序会被保留,相当于动态化的数组(list)[packed=true]开启高效编码
repeated Person person = 1;
}

编译消息 (命令行方式)

1
2
3
4
5
6
7
8
在终端输入下面命令
protoc -I=#SRC_DIR -xxx_out=$DST_DIR $SRC_DIR/xxx.proto

//比如:
protoc -I=/User/doc/Android/prorobuffer/app/src/proto -- java_out=/User/doc
/Users/doc/Android/protobuffer/app/src/main/proto/address.proto

//在/User/doc下面生成对应的java文件

  • SRC_DIR 编译.proto的文件目录
  • –xxx_out 设置生成的代码类型 java: –java_out c++: -cpp_out
  • DST_DIR 代码生成目录 xxx.proto 的路径

在AndroidStudio中,配置好之后直接build即可

生成文件在 build/generated/source/proto中

3. Android中使用Protocol Buffer

在项目中使用序列化和反序列化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
//创建对象
AddressProte.Person.Builder builder = AddressProto.Person.newBuilder();
AddressProto.Person person = builder.setEmain("xxx").build();
//序列化
byte[] bytes = person.toByteArray();
//反序列化
try{
AddressProto.Person person1 = AddressProto.Person.parseFron(bytes);
}catch(InvalidPriticilBufferException e){
e.prientStackTrace()
}

//序列化
ByteArrayOutputStream output = new ByteArrayOutputStream();
try(){
person.witeTo(output);
byte[] byte1 = output.toByteArray();
}catch(IOException e){
e.prientStackTrace()
}
//反序列化
try(){
AddressProto.Person person1 = AddressProto.Person.parseFrom(new ByteArrayInputStream(byte));
}catch(IOException e){
e.prientStackTrace()
}

Protocol Buffer 原理

  • 编码机制 Base 128 Varints
  • 消息结构 key-value 键值对组成

Varints是一种可变字节序列化整形的方法

  • 每个Byte的最高为(msb)是标志位,如果该位是1,表示该Byte后面还有其他的Byte,如果该位为0,表示Byte是最后一个Byte
  • 每个Byte的第7位是用来村存数值的位
  • Varints方法用Little-Endian(小端)字节序

一个多位整数,按照其存储地址的最高或者最低字节进行排序,如果最低有效位在最高有效位的前面,则成为小端序,反之成为大端序

消息结构

编码类型

Type Mesning Used For
0 Varints int32 ,int64,uint32,uint64,sint32,sint64,bool,enum
1 64-bit fiexd64 ,sfixed64 ,double
2 Length-delimited string,bytes,embedded message,packed,repeated fields
3 Start group groups(deprecated)
4 End group groups(deprecated)
5 32-bit fixed32,sfixed32,float

消息结构

key

key 的具体值为(field_number<<3|wire_type)

key的范围

wire_type只有6种类型,用3bit表示,在一个Byte里,去掉mbs,以3bit的wire_type,只剩下4bit来表示field_number,因此在一个Byte里,field_number只能表达0-15,如果超过15,则需要两个或者多个Byte来表示。常用字段放在前面

Varints优缺点

  • Varints适用于表达比较小的证书,当数字很大时,采用定长编码类型(64bit,32bit)
  • Varints不擅长表达负数,负数采用补码表示,会占用更多的字节,因此如果确定会出现负数,可以采用sint32或者sint64,他会采用ZigZig编码将负数映射为整数,之后再使用Varints编码

Length-delimited:编码格式则会将数据的length也编码进最终数据中,使用Length-delimited编码格式的数据类型包括string bytes和自定义消息

repeated/packed:repeated类型是把每个字段依次进行序列化,key相同,value不同,但是如果repeated的字段比较多,每次都带上相同的key则会浪费空间,因此 protobuf提供了packed选项,当repeated字段设置了packed选项,则会使用Length-delimited格式来编码字段值

Protocol Buffer使用注意事项

  • 尽量不要修改tag
  • 字段数量不要超过16个,否则会采用2个字节进行编码
  • 如果确定使用负数,采用sint32/sint64
# 架构

コメント

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×