官方地址:
官方github:
很不错的连续教程:
C++动态编译,淘宝的技术博客
深入:
首先,什么是PB?
他是一种数据传输的方式,就跟我们使用Json传输,xml传输是一样的,但是,他的优点是fast,比他们两都快,比他们小。他的缺点是生成的是字节码,可读性很差,或许你根本不造他写的是什么鬼。
首先是语法,先看一个例子:
package com.example.liweijie.protocolbufferdemo.proto;
option java_package="com.example.liweijie.protocolbufferdemo.bean";
option java_outer_classname="PersonInfo";
message Person{
required string name=1;
required int32 age=2;
optional string email = 3;
enum PhoneType{
MOBILE = 0;
HOME = 1;
WORK = 2;
}
message PhoneNumber{
required string number = 1;
required PhoneType type = 2[default=HOME];
}
optional PhoneNumber phoneNumber = 4;
}
message PersonList
{
repeated Person person=1;
option java_package="com.example.liweijie.protocolbufferdemo.bean";
option java_outer_classname="PersonInfo";
message Person{
required string name=1;
required int32 age=2;
optional string email = 3;
enum PhoneType{
MOBILE = 0;
HOME = 1;
WORK = 2;
}
message PhoneNumber{
required string number = 1;
required PhoneType type = 2[default=HOME];
}
optional PhoneNumber phoneNumber = 4;
}
message PersonList
{
repeated Person person=1;
}
语法:
package:声明该proto文件所在的包位置
option java_package和option java_outer_classname,指定生存的java类所在包和类名
message声明一个消息体,相当于java的class。
required:表示该字段是必须的,在使用的时候必须赋值
optional:表示该字段是可选的,在使用的时候可以不赋值,
repeated 表示该字段是可重复的,相当于java的list
default 为某一个字段设置默认值 假如没有设置,则是默认值,string是null,boolean是false,int是0,enum是第一个
message是可以嵌套,也可以独立的,相当于内部类外部类之类的,假如是使用外部的proto文件,需要import进来。语法是:
import "squareup/geology/period.proto";//可以是全路径,也可以是酱,需要在同一个工作路径之下
当我们需要引用外部的message的时候就是需要这样子。
其中,每一个字段之后有一个值:是该字段的唯一标示符,在二进制编码时候会用到。数字1~15的表示需求少于一个字节,所以在编码的时候,有这样一个优化,你可以用1~15标记最常使用或者重复字段元素(repeated elements)。用16或者更大的数字来标记不太常用的可选元素。再重复字段中,每一个元素都需重复编码标签数字,所以,该优化对重复字段最佳(repeat fileds)。比如上面的name =1
数据类型:
.proto Type | Notes | C++ Type | Java Type | Python Type[2] |
---|---|---|---|---|
double | double | double | float | |
float | float | float | float | |
int32 | 使用可变长编码. 对于负数比较低效,如果负数较多,请使用sint32 | int32 | int | int |
int64 | 使用可变长编码. 对于负数比较低效,如果负数较多,请使用sint64 | int64 | long | int/long |
uint32 | 使用可变长编码 | uint32 | int | int/long |
uint64 | 使用可变长编码 | uint64 | long | int/long |
sint32 | 使用可变长编码. Signed int value. 编码负数比int32更高效 | int32 | int | int |
sint64 | 使用可变长编码. Signed int value. 编码负数比int64更高效 | int64 | long | int/long |
fixed32 | 恒定四个字节。如果数值几乎总是大于2的28次方,该类型比unit32更高效。 | uint32 | int | int |
fixed64 | 恒定四个字节。如果数值几乎总是大于2的56次方,该类型比unit64更高效。 | uint64 | long | int/long |
sfixed32 | 恒定四个字节 | int32 | int | int |
sfixed64 | 恒定八个字节 | int64 | long | int/long |
bool | bool | boolean | boolean | |
string | A string must always contain UTF-8 encoded or 7-bit ASCII text. | string | String | str/unicode |
bytes | 包含任意数量顺序的字节 | string | ByteString | str |
编译:需要使用到proto.exe,语法如下,把proto.exe放到一个目录下,cd到该目录(或者使用全路径到proto.exe),然后--java_out便是java文件输出的路径,就是我们再message定义的包所在的路径。之后使用空格指定我们需要编译的proto文件,可以使用空格同时编译多个,之后就会产生一个java文件,和你在message时候声明的类名一样的。
编译截图:
经过编译之后,我们生成了一个PersonInfo这个类(指定的类名),重点:每一个在PersonInfo中定义的message对象,都会是在PersonInfo中生成,并且是作为一个内部类的形式存在,比如我们上面的Person类型的message,他就会在PersonInfo这个类内部生产一个Person内部类,PersonList也是。对于message中的message,就是会作为内部类中的内部类。
具体使用:
//构建Person对象,先需要构建PersonBuilder
PersonInfo.Person.Builder builder = PersonInfo.Person.newBuilder();
builder.setAge(20);
builder.setEmail("110120119");
builder.setName("liweijie");
//构建PhoneNumBer的Builder
PersonInfo.Person.PhoneNumber.Builder type = PersonInfo.Person.PhoneNumber.newBuilder();
type.setNumber("13631476005");
type.setType(PersonInfo.Person.PhoneType.MOBILE);
// builder.setPhoneNumber(type); 或者是使用下面方式
PersonInfo.Person.PhoneNumber number = type.build();
builder.setPhoneNumber(number);
//完成了Person对象的构建
//然后可以使用与数据传输
person = builder.build();
//写入输出流当中CodedOutputStream或者是OutputStream中
PersonInfo.Person.Builder builder = PersonInfo.Person.newBuilder();
builder.setAge(20);
builder.setEmail("110120119");
builder.setName("liweijie");
//构建PhoneNumBer的Builder
PersonInfo.Person.PhoneNumber.Builder type = PersonInfo.Person.PhoneNumber.newBuilder();
type.setNumber("13631476005");
type.setType(PersonInfo.Person.PhoneType.MOBILE);
// builder.setPhoneNumber(type); 或者是使用下面方式
PersonInfo.Person.PhoneNumber number = type.build();
builder.setPhoneNumber(number);
//完成了Person对象的构建
//然后可以使用与数据传输
person = builder.build();
//写入输出流当中CodedOutputStream或者是OutputStream中
//person.writeTo();
所有的原始对象,都是通过Builder模式产生的,比如我们的Person对象,就需要通过Person中的Builder类build生成(不是指解析的那种生成),然后我们通过set方法为各个元素赋值,然后我们通过writeTo方法,把我们的数据写出去,写到一个输出流中,或者是CodedOutputStream(它内部封装了输出流对象)中。
解析:通过parseFrom()方法,一共有:
public static com.example.liweijie.protocolbufferdemo.bean.PersonInfo.Person parseFrom(com.google.protobuf.ByteString data)
public static com.example.liweijie.protocolbufferdemo.bean.PersonInfo.Person parseFrom(com.google.protobuf.ByteString data,com.google.protobuf.ExtensionRegistryLite extensionRegistry)
public static com.example.liweijie.protocolbufferdemo.bean.PersonInfo.Person parseFrom(byte[] data)
public static com.example.liweijie.protocolbufferdemo.bean.PersonInfo.Person parseFrom(byte[] data,com.google.protobuf.ExtensionRegistryLite extensionRegistry)
public static com.example.liweijie.protocolbufferdemo.bean.PersonInfo.Person parseFrom(java.io.InputStream input)
public static com.example.liweijie.protocolbufferdemo.bean.PersonInfo.Person parseFrom( java.io.InputStream input,com.google.protobuf.ExtensionRegistryLite extensionRegistry)
public static com.example.liweijie.protocolbufferdemo.bean.PersonInfo.Person parseFrom(com.google.protobuf.CodedInputStream input)
public static com.example.liweijie.protocolbufferdemo.bean.PersonInfo.Person parseFrom(com.google.protobuf.CodedInputStream input,com.google.protobuf.ExtensionRegistryLite extensionRegistry)
这么多中,可以还原我们的Person对象,之后,我们通过Person对象的get方法可以依次取出我们的的数据。
使用List来编译和发送数据,就是使用我们的关键字,repeated实现。
首先是构造对象,跟上面是一样的,不多说,show you the code
PersonInfo.PersonList.Builder builder = PersonInfo.PersonList.newBuilder() ;
然后,需要注意的地方来了,他有一个add方法,一个set方法,set方法需要该数据存在才能set,不然会报错。
public Builder addPerson(com.example.liweijie.protocolbufferdemo.bean.PersonInfo.Person value)
public Builder addPerson(int index, com.example.liweijie.protocolbufferdemo.bean.PersonInfo.Person value)
public Builder addPerson(com.example.liweijie.protocolbufferdemo.bean.PersonInfo.Person.Builder builderForValue)
public Builder addPerson(int index, com.example.liweijie.protocolbufferdemo.bean.PersonInfo.Person.Builder builderForValue)
public Builder addAllPerson( java.lang.Iterable<? extends com.example.liweijie.protocolbufferdemo.bean.PersonInfo.Person> values)
清楚可以看到可以添加一个Person对象或者是他的Builder对象或者是一个Collection对象(Iterable 对象)
然后,假如是添加了我们需要修改,就通过setPerson方法修改,通过removePerson方法移除(只有Builder才能删除,list通过toBuilder获取Builder,然后remove之后在build在赋值给list就删除成功)
恢复List:通过
getPersonCount()获取总数
getPerson(intdex) 获取某一个Person
然后,就就没有然后了,就可以使用Person对象的get方法获取他的所有属性。
demo地址:https://github.com/liweijieok/protobufferdemo
使用Wire,这里是使用Wire实现了ProtocolBuffer的传输,PB生成的Java文件有一个特点就是特别大,这里的例子有2000行代码,Android限制了方法数不能超过65K,当然你可以使用谷歌给出的方法,也可以使用Wire去减少java文件的方法数,这里的例子只有不到350行就OK了。
具体的使用请看官网或者是我的Demo
附上编译截图:
个人公众号