快来看看Google出品的Protocol Buffer,别仅仅会用Json和XML了
2021-06-17 05:02
前言
- 习惯用 
Json、XML数据存储格式的你们,相信大多都没听过Protocol Buffer - 
Protocol Buffer事实上 是Google出品的一种轻量 & 高效的结构化数据存储格式,性能比Json、XML真的强!太!多!
由于
Google出品,我相信Protocol Buffer已经具备足够的吸引力 - 今天,我将献上一份 
Protocol Buffer的介绍 & 使用攻略,希望你们会喜欢。
 
文件夹

1. 定义
一种 结构化数据 的数据存储格式(相似于 `XML、Json` )
Protocol Buffer眼下有两个版本号:proto2和proto3- 由于
 proto3还是beta 版,所以本次解说是proto2
2. 作用
通过将 结构化的数据 进行 串行化(**序列化**),从而实现 **数据存储 / RPC 数据交换**的功能
- 序列化: 将 数据结构或对象 转换成 二进制串 的过程
 - 反序列化:将在序列化过程中所生成的二进制串 转换成 数据结构或者对象 的过程
 
3. 特点
- 对照于 常见的  
XML、Json数据存储格式。Protocol Buffer有例如以下特点: 

4. 应用场景
数据传输量大 & 网络环境不稳定 的数据存储、RPC 数据交换 的需求场景
如 即时IM (QQ、微信)的需求场景
总结
在 数据传输量较大的需求场景下,Protocol Buffer比XML、Json 更小、更快、使用 & 维护更简单!
5. 使用流程
使用 Protocol Buffer 的流程例如以下:

5.1 环境配置
要使用
Protocol Buffer。须要先在电脑上安装Protocol Buffer- 
整个 安装过程 仅仅须要依照以下步骤进行就可以:
整个安装过程请 自备梯子 以保证 网络畅通
 
步骤1:下载 Protocol Buffer 安装包
- 下载方式1:官网下载(须要FQ)
 - 下载方式2:贴心的我 已经给你们准备好了,请移步百度网盘。password:paju 
 
此处选择 较稳定的版本号
protobuf-2.6.1.tar.gz进行演示
下载成功后,对文件进行解压,例如以下图:
步骤2:安装 HOMEBREW(已安装的能够跳过)
// 打开 终端 输入以下指令
/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
步骤3:安装 Protocol Buffer
打开 您的终端 依次输入 下列指令 就可以:
brew install autoconf automake libtool curl
// Step1:安装 Protocol Buffer 依赖
// 注:Protocol Buffer 依赖于  autoconf、automake、libtool、curl
cd Desktop/protobuf-2.6.1
// Step2:进入 Protocol Buffer安装包 解压后的文件夹(我的解压文件放在桌面)
./autogen.sh
// Step3:执行 autogen.sh 脚本
 ./configure
// Step4:执行 configure.sh 脚本
 make
// Step5:编译未编译的依赖包
 make check
// Step6:检查依赖包是否完整
make install
// Step7:開始安装Protocol Buffer
步骤4:检查 Protocol Buffer 是否成功安装
// 在 终端 下输入
protoc - - version
出现 libprotoc 2.6.1 提示即表示 成功安装。例如以下图

特别注意:
- 
protoc=Protocol Buffer的编译器 - 作用:将 
.proto文件编译成相应平台的 头文件和源码文件 - 在以下会详细介绍
 
至此, Protocol Buffer已经安装完毕。以下将解说怎样详细使用Protocol Buffer
5.2 构建 Protocol Buffer 消息对象模型
5.2.1 构建步骤

以下将通过一个实例(Android(Java) 平台为例)详细介绍每一个步骤。
5.2.2 详细介绍
- 实例说明:构建一个
Person类的数据结构。包括成员变量name、id、email等等 
// Java类
public class Person
{
    private String name;
    private Int id;
    private String email;
...
}
- 平台使用:以 
Android(Java)平台为例来进行演示 
步骤1:通过 Protocol Buffer 语法 描写叙述 须要存储的数据结构
- 新建一个文件,命名规则为:文件名称 = 类名,后缀为 
.proto此处叫
Demo.proto 

- 依据上述数据结构的需求。在
Demo.proto里 通过Protocol Buffer语法写入相应.proto对象模型的代码,例如以下: 
package protocobuff_Demo;
// 关注1:包名
option java_package = "com.carson.proto";
option java_outer_classname = "Demo";
// 关注2:option选项
// 关注3:消息模型
// 以下详细说明
// 生成 Person 消息对象(包括多个字段。以下详细说明)
message Person {
  required string name = 1;
  required int32 id = 2;
  optional string email = 3;
  enum PhoneType {
    MOBILE = 0;
    HOME = 1;
    WORK = 2;
  }
  message PhoneNumber {
    required string number = 1;
    optional PhoneType type = 2 [default = HOME];
  }
  repeated PhoneNumber phone = 4;
}
message AddressBook {
  repeated Person person = 1;
}
- 以下将结合 上述样例 对 
Protocol Buffer语法 进行详细介绍 

关注1:包名
package protocobuff_Demo;
// 关注1:包名
- 作用:防止不同 
.proto项目间命名 发生冲突 - 
Protocol buffer包的解析过程例如以下:- 
Protocol buffer的类型名称解析与C++一致:从 最内部 開始查找,依次 向外 进行每一个包会被看作是其父类包的内部类
 - 
Protocol buffer编译器会解析.proto文件里定义的全部类型名 - 生成器会依据 不同语言 生成 相应语言 的代码文件 
a. 即对 不同语言 使用了 不同的规则 进行处理
b.Protoco Buffer提供C++、Java、Python三种语言的 API 
 - 
 
关注2:Option选项
option java_package = "com.carson.proto";
option java_outer_classname = "Demo";
// 关注2:option选项
- 
作用:影响 特定环境下 的处理方式
但不改变整个文件声明的含义
 经常使用Option选项例如以下:
option java_package = "com.carson.proto";
// 定义:Java包名
// 作用:指定生成的类应该放在什么Java包名下
// 注:如不显式指定,默认包名为:依照顾用名称倒序方式进行排序
option java_outer_classname = "Demo";
// 定义:类名
// 作用:生成相应.java 文件的类名(不能跟以下message的类名同样)
// 注:如不显式指定,则默觉得把.proto文件名称转换为首字母大写来生成
// 如.proto文件名称="my_proto.proto",默认情况下,将使用 "MyProto" 做为类名
option optimize_for = ***;
// 作用:影响 C++  & java 代码的生成
// ***參数例如以下:
// 1. SPEED (默认)::protocol buffer编译器将通过在消息类型上执行序列化、语法分析及其它通用的操作。(最优方式)
// 2. CODE_SIZE::编译器将会产生最少量的类,通过共享或基于反射的代码来实现序列化、语法分析及各种其它操作。
  // 特点:採用该方式产生的代码将比SPEED要少非常多。 可是效率较低;
  // 使用场景:经常使用在 包括大量.proto文件 但 不追求效率 的应用中。
//3.  LITE_RUNTIME::编译器依赖于执行时 核心类库 来生成代码(即採用libprotobuf-lite 替代libprotobuf)。
  // 特点:这样的核心类库要比全类库小得多(忽略了 一些描写叙述符及反射 );编译器採用该模式产生的方法实现与SPEED模式不相上下,产生的类通过实现 MessageLite接口,但它仅仅是Messager接口的一个子集。
  // 应用场景:移动手机平台应用
option cc_generic_services = false;
option java_generic_services = false;
option py_generic_services = false;
// 作用:定义在C++、java、python中,protocol buffer编译器是否应该 基于服务定义 产生 抽象服务代码(2.3.0版本号前该值默认 = true)
// 自2.3.0版本号以来,官方觉得通过提供 代码生成器插件 来对 RPC实现 更可取,而不是依赖于“抽象”服务
optional repeated int32 samples = 4 [packed=true];
// 假设该选项在一个整型基本类型上被设置为真,则採用更紧凑的编码方式(不会对数值造成损失)
// 在2.3.0版本号前。解析器将会忽略 非期望的包装值。因此,它不可能在 不破坏现有框架的兼容性上 而 改变压缩格式。
// 在2.3.0之后,这样的改变将是安全的。解析器能够接受上述两种格式。
optional int32 old_field = 6 [deprecated=true];
// 作用:推断该字段是否已经被弃用
// 作用同 在java中的注解@Deprecated
- 在 
ProtocolBuffers中同意 自己定义选项 并 使用 - 该功能属于高级特性。使用频率非常低,此处只是多描写叙述。有兴趣可查看官方文档
 
关注3:消息模型
- 作用:真正用于描写叙述 数据结构
 
// 消息对象用message修饰
message Person {
  required string name = 1;
  required int32 id = 2;
  optional string email = 3;
  enum PhoneType {
    MOBILE = 0;
    HOME = 1;
    WORK = 2;
  }
  message PhoneNumber {
    optional PhoneType type = 2 [default = HOME];
  }
  repeated PhoneNumber phone = 4;
}
message AddressBook {
  repeated Person person = 1;
}
- 组成:在 
ProtocolBuffers中:- 一个 
.proto消息模型 = 一个.proto文件 = 消息对象 + 字段 - 一个消息对象(
Message) = 一个 结构化数据 - 消息对象(
Message)里的 字段 = 结构化数据 里的成员变量 
 - 一个 
 

以下会详细介绍  .proto 消息模型里的 消息对象 & 字段

1. 消息对象
在 ProtocolBuffers 中:
- 一个消息对象(
Message) = 一个 结构化数据 - 消息对象用 修饰符 
message修饰 - 消息对象 含有 字段:消息对象(
Message)里的 字段 = 结构化数据 里的成员变量 

特别注意:

a. 加入:在一个 .proto文件 中可定义多个 消息对象
- 应用场景:尽可能将与 某一消息类型 相应的响应消息格式 定义到同样的 
.proto文件 中 - 实例:
 
message SearchRequest {
  required string query = 1;
  optional int32 page_number = 2;
  optional int32 result_per_page = 3;
}
// 与SearchRequest消息类型 相应的 响应消息类型SearchResponse
message SearchResponse {
 …
}
b. 一个消息对象 里 能够定义 另外一个消息对象(即嵌套)
message Person {
  required string name = 1;
  required int32 id = 2;
  optional string email = 3;
// 该消息类型 定义在 Person消息类型的内部
// 即Person消息类型 是 PhoneNumber消息类型的父消息类型
  message PhoneNumber {
    required string number = 1;
  }
}
message Outer {   // Level 0
  message MiddleAA {  // Level 1
    message Inner {   // Level 2
      required int64 ival = 1;
      optional bool  booly = 2;
    }
  }
}
2. 字段
- 消息对象的字段 组成主要是:字段 = 字段修饰符 + 字段类型 +字段名 +标识号
 

- 以下将对每一项详细介绍
 
a. 字段修饰符
- 作用:设置该字段解析时的规则
 - 详细类型例如以下:
 

b. 字段类型
字段类型主要有 三 类:
- 基本数据 类型
 - 枚举 类型
 - 消息对象 类型
 
message Person {
  // 基本数据类型 字段
  required string name = 1;
  required int32 id = 2;
  optional string email = 3;
  enum PhoneType {
    MOBILE = 0;
    HOME = 1;
    WORK = 2;
  }
  message PhoneNumber {
    optional PhoneType type = 2 [default = HOME];
    // 枚举类型 字段
  }
  repeated PhoneNumber phone = 4;
  // 消息类型 字段
}
1. 基本数据类型
.proto基本数据类型 相应于 各平台的基本数据类型例如以下: 
2. 枚举类型
- 作用:为字段指定一个 可能取值的字段集合 
该字段仅仅能从 该指定的字段集合里 取值
 - 说明:如以下样例,电话号码 可能是手机号、家庭电话号或工作电话号的当中一个。那么就将
PhoneType定义为枚举类型,并将加入电话的集合(MOBILE、HOME、WORK) 
// 枚举类型须要先定义才干进行使用
// 枚举类型 定义
 enum PhoneType {
    MOBILE = 0;
    HOME = 1;
    WORK = 2;
// 电话类型字段 仅仅能从 这个集合里 取值
  }
// 特别注意:
// 1. 枚举类型的定义可在一个消息对象的内部或外部
// 2. 都能够在 同一.proto文件 中的不论什么消息对象里使用
// 3. 当枚举类型是在一消息内部定义,希望在 还有一个消息中 使用时。须要採用MessageType.EnumType的语法格式
  message PhoneNumber {
    required string number = 1;
    optional PhoneType type = 2 [default = HOME];
    // 使用枚举类型的字段(设置了默认值)
  }
// 特别注意:
// 1.  枚举常量必须在32位整型值的范围内
// 2. 不推荐在enum中使用负数:由于enum值是使用可变编码方式的。对负数不够高
额外说明
当对一个 使用了枚举类型的.proto文件 使用 Protocol Buffer编译器编译时,生成的代码文件里:
- 对 
Java 或 C++来说。将有一个相应的enum文件 - 对 
Python来说。有一个特殊的EnumDescriptor类
 
被用来在执行时生成的类中创建一系列的整型值符号常量(symbolic constants)
3. 消息对象 类型
一个消息对象 能够将 其它消息对象类型 用作字段类型。情况例如以下:

3.1 使用同一个 .proto 文件里的消息类型
a. 使用 内部消息类型
- 
目的:先在 消息类型 中定义 其它消息类型 。然后再使用
即嵌套,须要 用作字段类型的 消息类型 定义在 该消息类型里
 实例:
message Person {
  required string name = 1;
  required int32 id = 2;
  optional string email = 3;
// 该消息类型 定义在 Person消息类型的内部
// 即Person消息类型 是 PhoneNumber消息类型的父消息类型
  message PhoneNumber {
    required string number = 1;
  }
  repeated PhoneNumber phone = 4;
  // 直接使用内部消息类型
}
b. 使用 外部消息类型
即外部重用。须要 用作字段类型的消息类型 定义在 该消息类型外部
message Person {
  required string name = 1;
  required int32 id = 2;
  optional string email = 3;
}
message AddressBook {
  repeated Person person = 1;
  // 直接使用了 Person消息类型作为消息字段
}
c. 使用 外部消息的内部消息类型
message Person {
  required string name = 1;
  required int32 id = 2;
  optional string email = 3;
// PhoneNumber消息类型 是 Person消息类型的内部消息类型
  message PhoneNumber {
    required string number = 1;
    optional PhoneType type = 2 [default = HOME];
  }
}
// 若父消息类型外部的消息类型须要重用该内部消息类型
// 须要以 Parent.Type 的形式去使用
// Parent = 须要使用消息类型的父消息类型。Type = 须要使用的消息类型
// PhoneNumber父消息类型Person 的外部 OtherMessage消息类型 须要使用 PhoneNumber消息类型
message OtherMessage {
  optional Person.PhoneNumber phonenumber = 1;
// 以 Parent.Type = Person.PhoneNumber  的形式去使用
}
3.2 使用不同 .proto 文件里的消息类型
- 目的:须要在 
A.proto文件 使用B.proto文件里的消息类型 - 解决方式:在 
A.proto文件 通过导入(import)B.proto文件里来使用B.proto文件 里的消息类型 
import "myproject/other_protos.proto"
// 在A.proto 文件里加入 B.proto文件路径的导入声明
// ProtocolBuffer编译器 会在 该文件夹中 查找须要被导入的 .proto文件
// 假设不提供參数。编译器就在 其调用的文件夹下 查找
当然,在使用 不同  .proto 文件里的消息类型 时 也会存在想 使用同一个 .proto 文件消息类型的情况,但使用都是一样,此处不作过多描写叙述。
3.3 将 消息对象类型 用在 RPC(远程方法调用)系统
- 解决方式:在 
.proto文件里定义一个RPC服务接口,Protocol Buffer编译器会依据所选择的不同语言平台 生成服务接口代码 - 由于使用得不多,此处不作过多描写叙述。详细请看该文档
 
c. 字段名
该字段的名称,此处不作过多描写叙述。
d. 标识号
- 
作用:通过二进制格式唯一标识每一个字段
- 一旦開始使用就不能够再改变
 - 标识号使用范围:[1,2的29次方 - 1]
 - 不可使用 [19000-19999] 标识号, 由于 
Protobuf协议实现中对这些标识号进行了预留。假若使用,则会报错 
 - 
编码占有内存规则:
每一个字段在进行编码时都会占用内存,而 占用内存大小 取决于 标识号:- 范围 [1,15] 标识号的字段 在编码时占用1个字节。
 - 范围 [16,2047] 标识号的字段 在编码时占用2个字节
 
 - 
使用建议
- 为频繁出现的 消息字段 保留 [1,15] 的标识号
 - 为将来有可能加入的、频繁出现的 消息字段预留 [1,15] 标识号
 
 
关于 字段 的高级使用方法

1. 更新消息对象 的字段
- 目的:为了满足新需求,须要更新 消息类型 而不破坏已有消息类型代码 
即新、老版本号须要兼容
 - 更新字段时,须要符合下列规则:
 

2. 扩展消息对象 的字段
- 作用:使得其它人能够在自己的 
.proto文件里为 该消息对象 声明新的字段而不必去编辑原始文件- 注:扩展 能够是消息类型也能够是字段类型 
 - 以下以 扩展 消息类型 为例
 
 - 注:扩展 能够是消息类型也能够是字段类型 
 
A.proto
message Request {
…
  extensions 100 to 199;
  // 将一个范围内的标识号 声明为 可被第三方扩展所用
  // 在消息Request中。范围 [100,199] 的标识号被保留为扩展用
  // 假设标识号须要非常大的数量时。能够将可扩展标符号的范围扩大至max
  // 当中max是2的29次方 - 1(536,870,911)。
  message Request {
    extensions 1000 to max;
  // 注:请避开[19000-19999] 的标识号。由于已被Protocol Buffers实现中预留
}
如今,其它人 就能够在自己的 .proto文件里 加入新字段到Request里。例如以下:
B.proto
extend Request {
  optional int32 bar = 126;
  // 加入字段的 标识号必须要在指定的范围内
  // 消息Request 如今有一个名为 bar 的 optional int32 字段
  // 当Request消息被编码时,数据的传输格式与在Request里定义新字段的效果是全然一样的
  //  注:在同一个消息类型中一定要确保不会扩展新增同样的标识号。否则会导致数据不一致;能够通过为新项目定义一个可扩展标识号规则来防止该情况的发生
}
- 要訪问 扩展字段 的方法与 訪问普通的字段 不同:使用专门的扩展訪问函数
 - 实例:
 
// 怎样在C++中设置 bar 值
Request request;
request.SetExtension(bar, 15);
// 相似的模板函数 HasExtension()。ClearExtension(),GetExtension(),MutableExtension(),以及 AddExtension()
// 与相应的普通字段的訪问函数相符
嵌套的扩展
能够在还有一个 消息对象里 声明扩展,如:
message Carson {
  extend Request {
    optional int32 bar = 126;
  }
  …
}
// 訪问此扩展的C++代码:
Request request;
request.SetExtension(Baz::bar, 15);
- 对于嵌套的使用,一般的做法是:在扩展的字段类型的范围内定义该扩展
 - 实例:一个 Request 消息对象须要扩展(扩展的字段类型是Car 消息类型)。那么,该扩展就定义在 Car消息类型 里:
 
message Car {
  extend Request {
    optional Car request_ext = 127;
// 注:二者并没有子类、父类的关系
  }
}
- 至此,
Protoco Buffer的语法已经解说完毕 - 关于怎样依据需求 通过
Protoco Buffer语法 去构建 数据结构 相信大家已经非常熟悉了。 - 在将 
.proto文件保存后,进入下一个步骤 
步骤2:通过 Protocol Buffer 编译器 编译 .proto 文件
- 作用:将 
.proto文件 转换成 相应平台的代码文件Protoco Buffer提供C++、Java、Python三种开发语言的 API - 详细生成文件与平台有关:
 

- 编译指令说明
 
// 在 终端 输入下列命令进行编译
protoc -I=$SRC_DIR --xxx_out=$DST_DIR   $SRC_DIR/addressbook.proto
// 參数说明
// 1. $SRC_DIR:指定须要编译的.proto文件文件夹 (如没有提供则使用当前文件夹)
// 2. --xxx_out:xxx依据须要生成代码的类型进行设置
// 对于 Java ,xxx =  java ,即 -- java_out
// 对于 C++ ,xxx =  cpp ,即 --cpp_out
// 对于 Python。xxx =  python。即 --python_out
// 3. $DST_DIR :编译后代码生成的文件夹 (通常设置与$SRC_DIR同样)
// 4. 最后的路径參数:须要编译的.proto 文件的详细路径
// 编译通过后。Protoco Buffer会依据不同平台生成相应的代码文件
- 详细实例
 
// 编译说明
// 1. 生成Java代码
// 2. 须要编译的.proto文件在桌面,希望编译后生成的代码也放在桌面
protoc -I=/Users/Carson_Ho/Desktop --java_out=/Users/Carson_Ho/Desktop /Users/Carson_Ho/Desktop/Demo.proto
// 编译通过后,Protoco Buffer会依照标准Java风格。生成Java类及文件夹结构
在指定的文件夹能看到一个Demo的包文件(含 java类文件)

编译功能的拓展
a. 使用Android Studio插件进行编译
- 需求场景:每次手动执行 
Protocol Buffer编译器将.proto文件转换为Java文件 操作不方便 - 解决方式:使用 
Android Studio的gradle插件protobuf-gradle-plugin,以便于在项目编译时 自己主动执行Protocol Buffers 编译器 
关于protobuf-gradle-plugin插件有兴趣的读者可自行了解。但个人还是建议使用 命令行,毕竟太过折腾插件不是必需
b. 动态编译
- 需求场景:某些情况下。人们无法预先知道 .proto 文件,他们须要动态处理一些未知的 .proto 文件 
如一个通用的消息转发中间件。它无法预先知道须要处理什么类型的数据结构消息
 - 解决方式:动态编译
.proto文件 
由于使用得不多,此处不作过多描写叙述。详细请看官方文档
c. 编写新的 .proto 编译器
- 需求场景: 
Protocol Buffer仅支持C++、java 和 Python三种开发语言。一旦超出该三种开发语言,Protocol Buffer将无法使用 - 解决方式:使用  
Protocol Buffer的Compiler包 开发出支持其它语言的新的.proto编译器 
由于使用得不多,此处不作过多描写叙述,详细请看官方文档
5.3 应用到详细平台(Android平台)
- 最终到了应用到详细平台项目中的步骤了。 
此处以
Android平台 为例 - 详细过程例如以下:
 

步骤1:将生成的 代码文件 放入到项目中
- 对于
Android(Java)平台。即将编译.proto文件生成的Java包文件 整个拷贝到Android项目中 - 放置路径: 
app/src/main/java的文件夹里 

步骤2:在 Gradle 加入 Protocol Buffer 版本号依赖
compile ‘com.google.protobuf:protobuf-java:2.6.1‘
// 注:protobuf-java的版本号 一定要和 安装protocobuffer的版本号 一致
步骤3:详细在Android项目中使用
3.1 消息对象类介绍
通过.proto文件 转换的 Java源码 = Protocol Buffer 类 + 消息对象类(含Builder内部类)
消息对象类 是
Protocol Buffer类的内部类
由于最经常使用的都是 消息对象类 和其内部类Builder类 的方法&成员变量,所以此处主要解说这两者。
3.1.1 消息对象类(Message类)
- 消息对象类 类通过 二进制数组 写 和 读 消息类型
 - 使用方法包括:
 
1:直接序列化和反序列化 消息 -->
protocolBuffer.toByteArray()。
// 序列化消息 并 返回一个包括它的原始字节的字节数组
protocolBuffer.parseFrom(byte[] data);
// 从一个字节数组 反序列化(解析) 消息
2:通过输入/ 输出流(如网络输出流) 序列化和反序列化消息 -->
protocolBuffer.writeTo(OutputStream output)。
output.toByteArray();
// 将消息写入 输出流 ,然后再 序列化消息 
protocolBuffer.parseFrom(InputStream input)。
// 从一个 输入流 读取并 反序列化(解析)消息
// 仅仅含包括字段的getters方法
// required string name = 1;
public boolean hasName();// 假设字段被设置,则返回true
public java.lang.String getName();
// required int32 id = 2;
public boolean hasId();
public int getId();
// optional string email = 3;
public boolean hasEmail();
public String getEmail();
// repeated .tutorial.Person.PhoneNumber phone = 4;
// 反复(repeated)字段有一些额外方法
public ListgetPhoneList();
public int getPhoneCount();
// 列表大小的速记
// 作用:通过索引获取和设置列表的特定元素的getters和setters 
经常使用的如上,很多其它请看官方文档
3.1.2 Builder类
作用:创建 消息构造器 & 设置/ 获取消息对象的字段值 & 创建 消息类 实例
属于 消息对象类 的内部类
a. 创建 消息构造器
Demo.Person.Builder person = Person.newBuilder();
b. 设置/ 获取 消息对象的字段值 详细方法例如以下:
// 标准的JavaBeans风格:含getters和setters
// required string name = 1;
public boolean hasName();// 假设字段被设置,则返回true
public java.lang.String getName();
public Builder setName(String value);
public Builder clearName(); // 将字段设置回它的空状态
// required int32 id = 2;
public boolean hasId();
public int getId();
public Builder setId(int value);
public Builder clearId();
// optional string email = 3;
public boolean hasEmail();
public String getEmail();
public Builder setEmail(String value);
public Builder clearEmail();
// repeated .tutorial.Person.PhoneNumber phone = 4;
// 反复(repeated)字段有一些额外方法
public ListgetPhoneList();
public int getPhoneCount();
// 列表大小的速记
// 作用:通过索引获取和设置列表的特定元素的getters和setters
public PhoneNumber getPhone(int index);
public Builder setPhone(int index, PhoneNumber value);
public Builder addPhone(PhoneNumber value);
// 将新元素加入到列表的末尾
public Builder addAllPhone(Iterablevalue);
// 将一个装满元素的整个容器加入到列表中
public Builder clearPhone();
public Builder isInitialized() 
// 检查全部 required 字段 是否都已经被设置
public Builder toString() :
// 返回一个人类可读的消息表示(用于调试)
public Builder mergeFrom(Message other)
// 将 其它内容 合并到这个消息中,覆写单数的字段,附接反复的。
public Builder clear()
// 清空全部的元素为空状态。  
3.2 详细使用
使用过程例如以下:
步骤1:通过 消息类的内部类Builder类 构造 消息构造器
步骤2:通过 消息构造器 设置 消息字段的值
步骤3:通过 消息构造器 创建 消息类 对象
步骤4:序列化 / 反序列化 消息详细使用例如以下:(凝视非常清晰)
public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        // 步骤1:通过 消息类的内部类Builder类 构造 消息类的消息构造器
        Demo.Person.Builder personBuilder =  Demo.Person.newBuilder();
        // 步骤2:设置你想要设置的字段为你选择的值
        personBuilder.setName("Carson");// 在定义.proto文件时,该字段的字段修饰符是required,所以必须赋值
        personBuilder.setId(123);// 在定义.proto文件时,该字段的字段修饰符是required,所以必须赋值
        personBuilder.setEmail("carson.ho@foxmail.com"); // 在定义.proto文件时,该字段的字段修饰符是optional,所以可赋值 / 不赋值(不赋值时将使用默认值)
        Demo.Person.PhoneNumber.Builder phoneNumber =  Demo.Person.PhoneNumber.newBuilder();
        phoneNumber.setType( Demo.Person.PhoneType.HOME);// 直接採用枚举类型里的值进行赋值
        phoneNumber.setNumber("0157-23443276");
        // PhoneNumber消息是嵌套在Person消息里,能够理解为内部类
        // 所以创建对象时要通过外部类来创建
        // 步骤3:通过 消息构造器 创建 消息类 对象
        Demo.Person person = personBuilder.build();
        // 步骤4:序列化和反序列化消息(两种方式)
        /*方式1:直接 序列化 和 反序列化 消息 */
        // a.序列化
        byte[] byteArray1 = person.toByteArray();
        // 把 person消息类对象 序列化为 byte[]字节数组
        System.out.println(Arrays.toString(byteArray1));
        // 查看序列化后的字节流
        // b.反序列化
        try {
            Demo.Person person_Request = Demo.Person.parseFrom(byteArray1);
            // 当接收到字节数组byte[] 反序列化为 person消息类对象
            System.out.println(person_Request.getName());
            System.out.println(person_Request.getId());
            System.out.println(person_Request.getEmail());
            // 输出反序列化后的消息
        } catch (IOException e) {
            e.printStackTrace();
        }
        /*方式2:通过输入/ 输出流(如网络输出流) 序列化和反序列化消息 */
        // a.序列化
        ByteArrayOutputStream output = new ByteArrayOutputStream();
         try {
            person.writeTo(output);
            // 将消息序列化 并写入 输出流(此处用 ByteArrayOutputStream 取代)
        } catch (IOException e) {
            e.printStackTrace();
        }
        byte[] byteArray = output.toByteArray();
        // 通过 输出流 转化成二进制字节流
        // b. 反序列化
        ByteArrayInputStream input = new ByteArrayInputStream(byteArray);
        // 通过 输入流 接收消息流(此处用 ByteArrayInputStream 取代)
        try {
            Demo.Person person_Request = Demo.Person.parseFrom(input);
            // 通过输入流 反序列化 消息
            System.out.println(person_Request.getName());
            System.out.println(person_Request.getId());
            System.out.println(person_Request.getEmail());
            // 输出消息
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
Demo 地址
Carson_Ho的Github :https://github.com/Carson-Ho/ProtocolBuffer
高级功能
- 
贴心的Google还提供将
Protocol Buff编码方式 转化为 其它编码方式,如Json、XML等等即将
Protocol Buff对象 转化为其它编码方式的数据存储对象 以下展示的是 将
Protocol Buff对象 转化为Json对象
// 步骤1:在Gradle加入依赖
compile ‘com.googlecode.protobuf-java-format:protobuf-java-format:1.4‘
// 步骤2:将`Protocol Buff` 对象 序列化 为 `Json`对象
JsonFormat jsonFormat = new JsonFormat();  
String person2json = jsonFormat.printToString(mProtoBuffer); 
6. 总结
- 在 数据传输量较大的需求场景下,
Protocol Buffer比XML、Json更小、更快、使用 & 维护更简单! - 以下用 一张图 总结在 Android平台中使用 
Protocol Buffer的整个步骤流程: 

- 看完本文,你应该会非常好奇为什么
Protocol Buffer的优势这么大:为什么序列化后的数据包比XML、Json更小、传输速度更快? - 下一篇文章我将对
Protocol Buffer进行源码分析。有兴趣能够继续关注我的CSDN博客! 
请帮顶或评论点赞!
由于你的鼓舞是我写作的最大动力!
上一篇:关于List集合中元素排序问题
下一篇:web.js
文章标题:快来看看Google出品的Protocol Buffer,别仅仅会用Json和XML了
文章链接:http://soscw.com/index.php/essay/94884.html
