简介
Protobuf是google 的一种数据交换的格式,它独立于语言,独立于平台。google 提供了多种语言的实现:java、c#、c++、go 和 python,每一种实现都包含了相应语言的编译器以及库文件。由于它是一种二进制的格式,比使用 xml 进行数据交换快许多。可以把它用于[分布式应用]
- GitHub
Protobuf
接入
将protobuf有两种方式,一种就是常用的配置build.gradle文件,引入库,使用protobuf插件来编译.proto(protobuf定义数据格式的文件)生成相应的平台类文件。第二种就是下载相应的protobuf版本的jar文件,放入工程,然后用google提供的编译工具,编译.proto生成相应的平台类文件,然后放入工程中。我使用的是第二种方式,因为这样可以加深对protobuf学习,当然第一种方式,我也会给出,但是可能遇到的问题就要自己去解决了。
-
第一种接入方式
更多Protobuf插件使用参考:
protobuf-gradle-plugin- 在项目的build.gradle文件中添加如下内容:
buildscript { ... dependencies { ... // Protobuf插件 classpath 'com.google.protobuf:protobuf-gradle-plugin:0.8.0' } }
- 在module使用插件
apply plugin: 'com.android.application' // Protobuf插件 apply plugin: 'com.google.protobuf'
- 在module配置Protobuf信息
android { ... protobuf{ protoc { // 也可以配置本地编译器路径 artifact = 'com.google.protobuf:protoc:3.5.1' } generateProtoTasks { all().each { task -> task.builtins { remove java } task.builtins { // 生产java源码 java {} } } } } sourceSets { main { java { srcDir 'src/main/java' } proto { //指定.proto文件路径 srcDir 'src/main/proto' } } } }
- 在module添加依赖
dependencies { ... // protobuf依赖库 api 'com.google.protobuf:protobuf-java:3.5.1' api 'com.google.protobuf:protoc:3.5.1' }
- 编译工程
在对应目录找到编译文件,如果还不明白的,请自行百度。
-
第二种接入方式
如果你喜欢将jar放在工程的libs目录的形式,不想要每次编译都去执行Protobuf编译脚本。那么下面的这种方式更加适合你。并且,通过这种方式,对Protobuf的用法会更加熟悉。- 下载需要的jar包
我下载的版本如下,protobuf-java-3.9.1.jar必须有,其它两个可以不下载。
dependencies { ... // 用implementation 运行工程报错 要用api api files('libs/protobuf-java-3.9.1.jar') implementation files('libs/protobuf-java-format-1.4.jar') implementation files('libs/protobuf-java-util-3.9.1.jar') }
maven仓库下载
百度maven,找到maven仓库并打开,搜索protobuf,结果如下:
- 下载需要的jar包
细心的人发现,Protobuf有个lite版本,lite版本是谷歌Protobuf支持库的精简版本。
-
下载编译.proto文件工具
注意:下载的工具版本,要和使用的Protobuf jar包版本相同。
GitHub下载
maven repo下载 创建.proto数据格式文件
执行编译程序,生成对于的类文件。
编译
创建.proto格式文件
首先需要创建一个.proto结尾的文件,用来定义我们需要的数据结构。字段都是定义在message节点下,message后面是名称,如果不明白,生成相应的类文件后,自己查看类文件。proto语法
参考官网proto3语法-
举例:
下面是我创建的proto文件
第一个.proto文件:socket.proto/* socket数据 结构*/ // 不加默认使用proto2 syntax = "proto3"; // 包名,如果不指定,这生成文件在输出路径的根目录 package google.protobuf; // 引入其它proto文件,使用其中的message import "body.proto"; import "header.proto"; // 生成的java文件包名 option java_package = "blog.pds.com.data.protobuf"; // 自定生成的class类名 option java_outer_classname = "SocketPackage"; // 是否将proto中的message编译成多个文件 // option java_multiple_files = true; message Package{ Header header = 1; Body body = 2; }
第二个.proto文件:header.proto
syntax = "proto3"; option java_package = "blog.pds.com.data.protobuf"; option java_outer_classname = "SocketHeader"; message Header { // singular默认规则,message可以拥有一个或者零个该字段 int32 type = 1; int32 length = 2; }
第三个.proto文件:body.proto
syntax = "proto3"; option java_package = "blog.pds.com.data.protobuf"; option java_outer_classname = "SocketBody"; message Body { uint64 id = 1; string content = 2; string ext = 3; // 保留字段编号 reserved 7, 15, 9 to 11; // 保留字段 reserved "type", "source"; }
这里一共三个proto文件,每一个文件都要经过编译,不是只编译第一个文件就可以了。
-
编译
这个过程,将我们定义好的proto文件生成我们需要的类文件,比我java,生成class文件,然后放入工程中使用,这个过程还是要注意一下包路径。
参考:
proto编译
编译命令protoc -I=$SRC_DIR --java_out=$DST_DIR $SRC_DIR/addressbook.proto 比如:./protoc_mac -I=../ --java_out=../java ../socket.proto
然后在输出目录找到你要的文件,导入到工程就可以了。
-
使用
使用可以自己试一试,下面给我简单的使用示范:val dataS = "-----------socket connect success------------" val socketProtoBody = SocketBody.Body .newBuilder() .setId(1111) .setContent(dataS) .build() val socketProtoHeader = SocketHeader.Header .newBuilder() .setType(1) .setLength(socketProtoBody.serializedSize) .build() val socketPackage = SocketPackage.Package .newBuilder() .setHeader(socketProtoHeader) .setBody(socketProtoBody) .build() val d = socketPackage.toByteArray()
override fun onReceive(type: Int, data: ByteArray?) { val pk = SocketPackage.Package.parseFrom(data) }
遇到的问题
- 项目使用了tink,这是google的一款数据加密的开源库,里面也使用了Protobuf,所以,如果项目使用了该库,编译一只会报protobuf库里面的类已经存在。
- implementation导致的问题
compile implementation 和api的区别可以百度。// 用implementation 运行工程报错 要用api api files('libs/protobuf-java-3.9.1.jar')
- socket服务端写N个字节,客户端接收到的字节数不为N,导致protobuf格式化失败。
这是因为,String和byte数组转换过程中,牵涉到编码问题,导致长度发生变化,所以在socket写数据的时候,把proto对象转成byte数组后,直接写到流里面。