转载请标明出处: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 文件,并在文件中填下如下内容:
syntax = "proto3";
package proto;
option java_package = "com.example.demo";
option java_outer_classname = "PersonProto";
message Person {
string name = 1;
int32 id = 2;
bool boo = 3;
string email = 4;
string phone = 5;
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'
}
}
然后我们还需要在Module 目录下的 build.gradle 文件下添加配置,示例如下:
apply plugin: 'com.google.protobuf'
android{
...
sourceSets {
main {
java.srcDirs = ['src/main/java']
jniLibs.srcDirs = ['libs']
assets.srcDirs = ['assets']
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 {}
}
}
}
}
在完成了上述配置之后,执行一下 rebuild 方法,这样我们就能够自动生成proto java class 文件了。
2.、基本调用
val card1 = PersonProto.Card.newBuilder().setCName("card1").build()
val card2 = PersonProto.Card.newBuilder().setCName("card2").build()
val cardList = listOf(card1, card2)
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()
PersonProto.Person.parseFrom(ByteBuffer data)
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 创建一个新对象。赋值不改变原有对象的值。
val p2 = person.toBuilder().clearName().build()
val p3 = person.toBuilder().setName("ha ha").build()
清除属性值
清除属性值,不影响原来的对象
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
枚举
syntax = "proto3";
package proto;
option java_package = "com.example.demo";
option java_outer_classname = "MonthProto";
enum Month {
MONTH_UNSPECIFIED = 0;
JANUARY = 1;
FEBRUARY = 2;
MARCH = 3;
APRIL = 4;
MAY = 5;
}
使用
val m1 = MonthProto.Month.MARCH
val m2 = MonthProto.Month.forNumber(4)
val v1 = m1.number
val v2 = MonthProto.Month.MARCH_VALUE
val list = MonthProto.Month.values()
list.forEach { month ->
}
文件操作
var person = PersonProto.Person.newBuilder()
.setName("zhaoyanjun")
.setId(100)
.build()
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()
val p3 = p1.toBuilder().mergeFrom(p2).build()
Log.d("yt--", "${p3.name} ${p3.id} ${p3.phone}")
引入外部类导包
引入外部类时,可以用包名指定具体的类,防止类冲突
数据类型
person.proto
syntax = "proto3";
package proto;
import "month.proto";
option java_package = "com.example.demo";
option java_outer_classname = "PersonProto";
message Person {
string name = 1 ;
int32 id = 2;
bool boo = 3;
Card card = 4 ;
repeated Card cList = 5 ;
repeated string titles = 6 ;
double pro = 7 ;
float bro = 8 ;
proto.month.Month month = 9 ;
bytes data = 10 ;
map<string, int32> maps = 11 ;
int64 number = 12 ;
}
message Card {
string cName = 1;
}
month.proto
syntax = "proto3";
package proto.month;
option java_package = "com.example.demo";
option java_outer_classname = "MonthProto";
enum Month {
MONTH_UNSPECIFIED = 0;
JANUARY = 1;
FEBRUARY = 2;
MARCH = 3;
APRIL = 4;
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
|