转载请标明出处:http://blog.csdn.net/zhaoyanjun6/article/details/125116393
本文出自【赵彦军的博客】
文章目录
前言
Protobuf,类似于json和xml,是一种序列化结构数据机制,可以用于数据通讯等场景,相对于xml而言更小,相对于json而言解析更快,支持多语言。
官方网站:https://developers.google.com/protocol-buffers/docs/proto3#maps
数据类型:https://developers.google.com/protocol-buffers/docs/proto#scalar
一、Proto文件示例
Protobuf使用 .proto
文件来定义数据格式,所以我们首先新建立一个person.proto
文件,并在文件中填下如下内容:
//指定proto的版本为proto3,不写的话默认为proto2.
syntax = "proto3";
//包名
package proto;
//引入包
//import "";
//指定生成类所在的Java包名
option java_package = "com.example.demo";
//重命名,如果不写,默认为文件名的首字母大写转化生成,如本文件如果不写则是Person
option java_outer_classname = "PersonProto";
message Person {
string name = 1;
int32 id = 2;
bool boo = 3;
string email = 4;
string phone = 5;
//repeated 相当于java 里的 List<Card>
repeated Card cList = 6 ;
}
message Card {
string cName = 1;
}
这样我们就定义好了一个基本的Person对象,下面我们对文件中的关键字进行一一说明:
-
syntax:指定proto的版本,protobuf目前有proto2和proto3两个常用版本,如果没有声明,则默认是proto2.
-
package:指定包名。
-
import:导入包,类似于java的import.
-
java_package:指定生成类所在的包名
-
java_outer_classname:定义当前文件的类名,如果没有定义,则默认为文件的首字母大写名称
-
message:定义类,类似于java class;可以嵌套
-
repeated:字段可以有多个内容(包括0),类似于array
需要注意的是在声明了属性之后,需要对属性声明一个tag(示例代码中的:1,2,3)。
这个tag是ProtoBuf编码是使用来标识属性的,因此在定义了一个message的属性之后,最好不要再去修改属性的tag值以免造成旧数据解析错误。
message 定义的对象,在编译期间都会自动生成 java 类,我们在build/generated/javac
目录中可以看到 ,如下图所示
二、在Android中的使用
protobuf
可以在Android中进行使用,并且集成对应的 Gradle Plugin
能够快速的编译 proto
文件。
其基本的编译流程如下:
下面我们直接使用上面的person.proto文件来举例说明。
1、 plugin配置
首先我们需要在工程目录下的build.gradle
文件中引入protobuf
,示例代码如下:
buildscript {
ext.kotlin_version = "1.3.72"
repositories {
google()
jcenter()
maven { url "https://jitpack.io" }
mavenCentral()
}
dependencies {
classpath "com.android.tools.build:gradle:4.1.0"
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath 'com.google.protobuf:protobuf-gradle-plugin:0.8.8'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
然后我们还需要在Module
目录下的 build.gradle
文件下添加配置,示例如下:
apply plugin: 'com.google.protobuf'
android{
...
sourceSets {
main {
java.srcDirs = ['src/main/java']
jniLibs.srcDirs = ['libs']
assets.srcDirs = ['assets']
proto {
//指定proto文件位置,你的proto文件放置在此文件夹中
srcDir 'src/main/proto'
}
}
}
}
dependencies{
...
implementation 'com.google.protobuf:protobuf-java:3.5.1'
implementation 'com.google.protobuf:protoc:3.5.1'
}
protobuf {
protoc {
artifact = 'com.google.protobuf:protoc:3.5.1' // 也可以配置本地编译器路径
}
generateProtoTasks {
all().each { task ->
task.builtins {
remove java
}
task.builtins {
java {}// 生产java源码
}
}
}
}
在完成了上述配置之后,执行一下 rebuild
方法,这样我们就能够自动生成proto java class
文件了。
2.、基本调用
//创建card对象
val card1 = PersonProto.Card.newBuilder().setCName("card1").build()
val card2 = PersonProto.Card.newBuilder().setCName("card2").build()
val cardList = listOf(card1, card2)
//创建person对象
val person = PersonProto.Person.newBuilder()
.setName("zhaoyanjun")
.setId(344)
.setBoo(false)
.addAllCList(cardList) //添加集合
.build()
//把对象序列化成字节数组
val array = person.toByteArray()
//从字节数组中解析成对象
val newPerson = PersonProto.Person.parseFrom(array)
把对象序列化成字节数组,这个很有用,可以吧字节数组存本地,或者发送给服务器。
除此之外,还有
//把对象序列化成字节字符串
val bys = person.toByteString()
//从ByteBuffer中解析成对象
PersonProto.Person.parseFrom(ByteBuffer data)
//InputStream中解析成对象
PersonProto.Person.parseFrom(InputStream input)
从网络获取数据解析成对象。
Request request = new Request.Builder().url(url).build();
Call call = okHttpClient.newCall(request);
Response response = call.execute();
if (response.isSuccessful()) {
ResponseBody responseBody = response.body();
if (responseBody != null) {
return Person.parseFrom(responseBody.byteStream());
}
}
如何给已有对象赋值,不能直接通过原对象赋值,只能 toBuilder
创建一个新对象。赋值不改变原有对象的值。
//清除 person 原来的名字,回到默认值
val p2 = person.toBuilder().clearName().build()
//重新赋值
val p3 = person.toBuilder().setName("ha ha").build()
清除属性值
清除属性值,不影响原来的对象
//清除name属性值,变为默认值
val p1 = person.toBuilder().clearName().build()
//清除所有属性值,所有属性变为默认值
val p2 = person.toBuilder().clear().build()
集合
//获取集合长度
val size = person.cListCount
//获取集合
val c1 = person.cListList.get(1)
val c2 = person.cListList.getOrNull(1)
val c3 = person.cListList.getOrElse(1,null)
//获取对象的索引
val index = person.cListList.indexOf(card)
val lastIndex = person.cListList.lastIndex
默认值
string 默认是空字符串也就是双引号 "" , 注意不是 null
int32 默认是 0
bool 默认 false
repeated 默认是空集合,注意不是 null
枚举
//指定proto的版本为proto3,不写的话默认为proto2.
syntax = "proto3";
//包名
package proto;
//引入包
//import "";
//指定生成类所在的Java包名
option java_package = "com.example.demo";
//重命名,如果不写,默认为文件名的首字母大写转化生成,如本文件如果不写则是Person
option java_outer_classname = "MonthProto";
enum Month {
// The unspecified month.
MONTH_UNSPECIFIED = 0;
// The month of January.
JANUARY = 1;
// The month of February.
FEBRUARY = 2;
// The month of March.
MARCH = 3;
// The month of April.
APRIL = 4;
// The month of May.
MAY = 5;
}
使用
//获取枚举对象
val m1 = MonthProto.Month.MARCH //MARCH
val m2 = MonthProto.Month.forNumber(4) //APRIL
//获取枚举对象的 number 值
val v1 = m1.number //3
val v2 = MonthProto.Month.MARCH_VALUE //3
//获取所有枚举值
val list = MonthProto.Month.values()
list.forEach { month ->
}
文件操作
//创建person对象
var person = PersonProto.Person.newBuilder()
.setName("zhaoyanjun")
.setId(100)
.build()
//把person二进制写入文件
val filePath = externalCacheDir?.absolutePath + File.separator + "cache.cache"
FileOutputStream(filePath).use {
person.writeTo(it)
}
//从文件中读取二进制,并且转换成对象
FileInputStream(filePath).use {
val p2 = PersonProto.Person.parseFrom(it)
}
数据合并 mergeFrom
var p1 = PersonProto.Person.newBuilder()
.setName("zhaoyanjun")
.setId(100)
.setPhone("120")
.build()
var p2 = PersonProto.Person.newBuilder()
.setName("haha")
.setId(50)
.build()
//p2 数据覆盖 p1 数据,没有覆盖的字段,保留 p1 数据
val p3 = p1.toBuilder().mergeFrom(p2).build()
//结果:yt--: haha 50 120
Log.d("yt--", "${p3.name} ${p3.id} ${p3.phone}")
引入外部类导包
引入外部类时,可以用包名指定具体的类,防止类冲突
数据类型
person.proto
//指定proto的版本为proto3,不写的话默认为proto2.
syntax = "proto3";
//包名
package proto;
//引入包
import "month.proto";
//指定生成类所在的Java包名
option java_package = "com.example.demo";
//重命名,如果不写,默认为文件名的首字母大写转化生成,如本文件如果不写则是Person
option java_outer_classname = "PersonProto";
message Person {
string name = 1 ; //字符串类型
int32 id = 2; //int 类型
bool boo = 3; //布尔 类型
Card card = 4 ; //内部类 类型
repeated Card cList = 5 ; //List<Card> 类型
repeated string titles = 6 ; //List<String> 类型
double pro = 7 ; //double 类型
float bro = 8 ; //float 类型
proto.month.Month month = 9 ; //外部类类型,注意需要导包 import "month.proto"
bytes data = 10 ; //ByteString 类型
map<string, int32> maps = 11 ; //map 类型
int64 number = 12 ; //long类型
}
message Card {
string cName = 1;
}
month.proto
//指定proto的版本为proto3,不写的话默认为proto2.
syntax = "proto3";
//包名
package proto.month;
//引入包
//import "";
//指定生成类所在的Java包名
option java_package = "com.example.demo";
//重命名,如果不写,默认为文件名的首字母大写转化生成,如本文件如果不写则是Person
option java_outer_classname = "MonthProto";
enum Month {
// The unspecified month.
MONTH_UNSPECIFIED = 0;
// The month of January.
JANUARY = 1;
// The month of February.
FEBRUARY = 2;
// The month of March.
MARCH = 3;
// The month of April.
APRIL = 4;
// The month of May.
MAY = 5;
}
使用:
为什么没有 long 类型
https://stackoverflow.com/questions/18248839/how-to-declare-an-unsigned-long-long-in-protobuf
https://developers.google.com/protocol-buffers/docs/proto#scalar
在 protobuf 中,是没有 long
类型字段的 ,可以用 int64
表示 java 中的 long