任何新事物的出现总有它的原因。为什么出现?能解决什么问题?怎么解决的·? 这是我们首先会问的问题。
(一 )什么是Protocol Buffer?
为什么有了XML, Json, 还需要Protocol Buffer?
XML可读性虽然最好,但是体积过于冗长,导致在解析和传输时,性能不够好。后来有了Json,在保持可读性的同时,改善了大小。但是在有些场合,json依旧太大,无法满足传输解析的需求,所以又有了Protobuffer。
优点:
1. Protobuffer的key可以简单认为是1,2,3,4这种数字,那肯定比字符串式的key要短的太多了。所以体积小,有利于传输和存储。
2. Protobuffer的解析是通过位运算。所以解析性能非常快。
缺点:
1. 体积小,解析性能好的代价,就是彻底牺牲了可读性, 对开发者不友好。
适用场景:
字节流模式的通信。 比如直接Socket通信的,那最适合使用。
(二)如何使用Protocol Buffer
在android中使用。
https://github.com/google/protobuf-gradle-plugin 有详细介绍。很多网上博客文章介绍的都已经过时了。本文按当下最新版本介绍。你在使用的时候,和官方里写的使用说明再合对一下,尤其是版本号。不然会有编译错误,曾吃过苦头。
到如今,Android Studio已经以插件的方式集成了proto buffer的编译工具。只要rebuild工程后,就会自动生成java文件放在debug目录下。也可以指定目录。
具体有下面几个地方需要修改:
在Moduld App 的gradle里添加
apply plugin: 'com.google.protobuf' //添加插件
protobuf {
protoc {
artifact = 'com.google.protobuf:protoc:3.8.0'
}
generateProtoTasks {
all().each { task ->
task.builtins {
java {
option "lite"
}
}
}
}
}
implementation 'com.google.protobuf:protobuf-lite:3.0.0' //注意是lite. 是轻量级的protobuf。 是为了解决方法数过多的问题,比如把get/set方法删除了,直接用public.
在工程gradle里添加:
classpath 'com.google.protobuf:protobuf-gradle-plugin:0.8.11'
这里记得都用最新版本号,不然会有很多莫名其妙的编译错误。版本号从上面那个链接里查看。
最后,在main文件夹下面创建一个proto文件夹,把proto文件放里面。系统默认会取这个里面的proto文件进行编译。或设置sourceset, 改变默认路径。
下面生成的java文件路径:
(三) Protocol Buffer原理
Protocol Buffer是字节流模式的。所以就是byte[]数组。
简单的理解就是,把类序列化的结果就是一个byte[]数组, 而把类反序列化的过程就一个byte[]数组变成一个类实例。所以socket通信是不是特别合适呢?!
编码机制:
编码方式是: Base 128 varints
https://blog.csdn.net/hellochenlu/article/details/51395988
消息结构:
Key-Value键值对组成。
有这么一个message体
message Person {
int32 id = 1;
string name = 2;
}
int32 id = 1; "1"被称为tag, 参与Key的构建。同样类型int32也参与Key的构建。
key是这样表示的。int32 type | 1, 用5位表示key, 还有3位表示value。 所以这里有一个问题,当message体参数成员超过15后,用两个字节存储key。 自己在用的时候message体的参数个数尽量别超过16个,可以节省空间。
person序列化后在byte[]数组是这样的: int32 type | 1 value string | 2 length value
多个参数一个一个串在一起。String 有点特殊,中间还要加一个长度。
使用指南:
有三种版本的语法。目前最新版是syntax = "proto3",更加简洁,并且性能更好。更多细节参考官方文档。
syntax = "proto2";
package tutorial;
option java_package = "com.example.tutorial";
option java_outer_classname = "AddressBookProtos";
message Person {
required string name = 1;
required int32 id = 2;
optional string email = 3;
enum PhoneType {
MOBILE = 0;
HOME = 1;
WORK = 2;
}
message PhoneNumber {
required string number = 1;
optional PhoneType type = 2 [default = HOME];
}
repeated PhoneNumber phones = 4;
}
message AddressBook {
repeated Person people = 1;
}
syntax = "proto3";
option java_package = "com.nero.testprotobuffer";
option java_outer_classname = "AddressBookProto";
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 AddressBook {
repeated Person people = 1;
}
编译后正式使用:
AddressBookProto.Person.Builder personBuilder = AddressBookProto.Person.newBuilder();
personBuilder.setName(“alan”);
personBuilder.setId(1);
AddressBookProto.Person person = personBuilder.build();
Byte[] personStreamData = person.toByteArray();
AddressBookProto.Person person2 = AddressBookProto.Person.parseFrom(personStreamData)