1. Protocol Buffers概念与优势
Protocol Buffers是Google公司开发的一种数据描述语言,类似于XML能够将结构化数据序列化,可用于数据存储、通信协议等方面,但更小,更快,更简单。
它不依赖于语言和平台并且可扩展性极强。google 为其提供了多种语言的实现:java、c#、c++、Objective-C、JavaScript、PHP、Dart、Ruby、go 和 python,每一种实现都包含了相应语言的编译器以及库文件。
2. 下载并编译安装Protocol Buffers
下载开发包:
https://github.com/google/protobuf/releases
进入该目录,执行autogen.sh,该命令用于编译配置的 configure 等文件:
$ cd protobuf $ ./autogen.sh
上面的操作过程中,如果你还缺少安装一些命令libtool和autoconf,可以通过如下命令安装:
$ brew install libtool $ brew install automake
查看configure 命令是否安装成功:
$ ./configure --help
执行configure对编译进行配置:
$ ./configure
当生成了makefile文件,编译并安装:
$ make $ sudo make install
上面的安装过程需要执行10多分钟,请耐性等待。安装之后执行如下命令以确认已经装好:
$ protoc --version libprotoc 3.5.1
如果是windows平台,只需要下载即可protoc-3.5.1-win32版本,并且需要配置path环境变量.
3. 创建 .proto 文件定义传输数据格式
.proto 文件中的定义很简单:为每个想要序列化的数据结构添加一个 消息(message) ,然后为消息中的每个字段指定一个名字和类型(如下基本类型)以及一个tag数字。
Java Type | .proto Type |
---|---|
double | double |
float | float |
int | int32 uint32 sint32 |
long | int64 uint64 sint64 |
boolean | bool |
String | string |
ByteString | bytes |
1. 如下为我们创建的传输格式文件shopcar.proto:
//定义传输数据的协议格式文件语法版本,目前主流版本有proto2 proto3
syntax = "proto3";
//这用于防止不同项目间的命名冲突。在Java中,包名被用作Java包(前提没声明java_package)
package com.a520it.protobuffersdemo.bean;
//生成的类应该放在什么Java包名下。如果你没有显式地指定这个值,则它简单地匹配由package 声明给出的Java包名
option java_package="com.a520it.protobuffersdemo.bean";
//定义应该包含这个文件中所有类的类名.如果你没有显式地给定java_outer_classname ,
//则将通过把文件名转换为首字母大写来生成。比如"my_proto.proto",默认情况下,将使用 "MyProto" 做为它的外层类的类名。
option java_outer_classname = "ShopcarProtos";
//消息只是包含了具有类型的字段的聚合。
message Store{
//required:声明该值是必要的值,不能为空(proto3舍弃)
//optional:声明该值是可选的值,可以为空(proto3舍弃)
//proto3舍弃以上两种修饰,值都能为空
//repeated:声明该值为多个数值,可以组成数组的形式
//许多标准数据类型可用作字段类型,包括bool,int32,float,double,和string。
//每个元素上的 " = 1"," = 2"标记标识在二进制编码中使用的该字段唯一的 "tag" 。
// 1~15 :在编码上少一个字节,将那些数字用作常用的或重复的元素的tag
// 16~ :留给更加不常用的可选元素
string name = 1;
}
message Product{
int64 id = 1;
string name = 2;
double price = 3;
int32 buyCount = 4;
Store store = 5;
}
message Shopcar{
repeated Product products = 1;
}
- 进入下载好的protocal buffer文件夹目录,可以通过如下命令编译 .proto 文件:
protoc -I=$SRC_DIR --java_out=$DST_DIR $SRC_DIR/shopcar.proto
-I,–java_out 分别用于指定源目录和目的目录 (放置生成的代码的位置;通常与目的相同),最后的参数为 .proto 文件的路径,下面是我自己的实际配置:
$ protoc -I=/Users/lean/Desktop/ProtoBuffersDemo/app/src/main/java --java_out=/Users/lean/Desktop/ProtoBuffersDemo/app/src/main/java /Users/lean/Desktop/ProtoBuffersDemo/app/src/main/java/shopcar.proto
- 将生成的代码放到我们项目中,打开文件,发现出现了未识别类的错误。添加依赖:
compile 'com.google.protobuf:protobuf-java:3.5.1'
4.生成发送数据流
public void test1(View v) throws Exception {
ShopcarProtos.Shopcar.Builder builder = ShopcarProtos.Shopcar.newBuilder();
for (int i = 0; i < 10; i++) {
ShopcarProtos.Store store = ShopcarProtos.Store.newBuilder()
.setName("奇趣旗舰店")
.build();
ShopcarProtos.Product product = ShopcarProtos.Product.newBuilder()
.setId(i)
.setBuyCount(10 + i)
.setName("奇趣巧克力")
.setPrice(18.8d)
.setStore(store)
.build();
builder.addProducts(i, product);
}
ShopcarProtos.Shopcar shopcar = builder.build();
shopcar.writeTo(openFileOutput("shopcar.txt", MODE_PRIVATE));
}
5.解码数据流
public void test2(View v) throws Exception {
ShopcarProtos.Shopcar shopcar = ShopcarProtos.Shopcar.parseFrom(openFileInput("shopcar.txt"));
int productsCount = shopcar.getProductsCount();
for (int i = 0; i < productsCount; i++) {
ShopcarProtos.Product product = shopcar.getProducts(i);
Log.v("IT520", "*************Product************");
Log.v("IT520", product.getId()+"");
Log.v("IT520", product.getName()+"");
Log.v("IT520", product.getPrice()+"");
Log.v("IT520", product.getBuyCount()+"");
Log.v("IT520", product.getStore().getName()+"");
}
}
6. 集成自动解析.proto文件插件
找到项目的build.gradle文件,添加如下代码:
classpath 'com.google.protobuf:protobuf-gradle-plugin:0.8.0'
找到模块的build.gradle文件,添加如下代码:
... apply plugin: 'com.google.protobuf' android { ... sourceSets { main { java { srcDir 'src/main/java' } proto { srcDir 'src/main/proto' //指定.proto文件路径 } } } } protobuf { protoc { artifact = 'com.google.protobuf:protoc:3.5.1' // 也可以配置本地编译器路径 } generateProtoTasks { all().each { task -> task.builtins { remove java } task.builtins { java {}// 生产java源码 } } } } dependencies { ... //解析.proto文件的在线工具protoc compile 'com.google.protobuf:protoc:3.5.1' }
在文件的这个位置定义.proto文件:
syntax="proto3";
package com.a520it.protobuffersdemo.proto;
option java_package="com.a520it.protobuffersdemo.bean";
option java_outer_classname="OrderInfoProtos";
message Product{
int64 pid = 1;
string name = 2;
double price = 3;
int64 buyCount = 4;
}
message OrderInfo{
int64 oid = 1;
string name = 2;
double totalPrice = 3;
repeated Product products= 4;
}
当build成功后,就会在如下地址产生java文件:
编码解码数据:
public void test3(View v) throws Exception { OrderInfoProtos.OrderInfo.Builder infoBuilder = OrderInfoProtos.OrderInfo.newBuilder(); for (int i = 0; i < 6; i++) { OrderInfoProtos.Product product = OrderInfoProtos.Product.newBuilder() .setPid(i + 1) .setName("德芙巧克力") .setBuyCount(4) .setPrice(18.8d) .build(); infoBuilder.setOid(1001) .setName("奇趣巧克力高级订单") .setTotalPrice(10008.00) .addProducts(i, product); } OrderInfoProtos.OrderInfo orderInfo = infoBuilder.build(); orderInfo.writeTo(openFileOutput("orderinfo.txt", MODE_PRIVATE)); } public void test4(View v) throws Exception { OrderInfoProtos.OrderInfo orderInfo = OrderInfoProtos.OrderInfo.parseFrom(openFileInput("orderinfo.txt")); Log.v("IT520", "*************OrderInfo************"); Log.v("IT520", "orderInfo.getOid()" + orderInfo.getOid()); Log.v("IT520", "orderInfo.getName()" + orderInfo.getName()); Log.v("IT520", "orderInfo.getTotalPrice()" + orderInfo.getTotalPrice()); int productsCount = orderInfo.getProductsCount(); for (int i = 0; i < productsCount; i++) { OrderInfoProtos.Product product = orderInfo.getProducts(i); Log.v("IT520", "*************Product:"+i+"************"); Log.v("IT520", "orderInfo.getPid()" + product.getPid()); Log.v("IT520", "orderInfo.getName()" + product.getName()); Log.v("IT520", "orderInfo.getBuyCount()" + product.getBuyCount()); Log.v("IT520", "orderInfo.getPrice()" + product.getPrice()); } }