前言

  • 习惯用 Json、XML 数据存储格式的你们,相信大多都没听过Protocol Buffer
  • Protocol Buffer 事实上 是 Google出品的一种轻量 & 高效的结构化数据存储格式,性能比 Json、XML 真的强!太!

    多!

    由于 Google出品,我相信Protocol Buffer已经具备足够的吸引力

  • 今天,我将献上一份 Protocol Buffer的介绍 & 使用攻略,希望你们会喜欢。


文件夹


1. 定义

一种 结构化数据 的数据存储格式(相似于 `XML、Json` )

  1. Google 出品 (开源)
  2. Protocol Buffer 眼下有两个版本号:proto2proto3
  3. 由于proto3 还是beta 版,所以本次解说是 proto2

2. 作用

通过将 结构化的数据 进行 串行化(**序列化**),从而实现 **数据存储 / RPC 数据交换**的功能

  1. 序列化: 将 数据结构或对象 转换成 二进制串 的过程
  2. 反序列化:将在序列化过程中所生成的二进制串 转换成 数据结构或者对象 的过程

3. 特点

  • 对照于 常见的 XML、Json 数据存储格式。Protocol Buffer有例如以下特点:


4. 应用场景

数据传输量大 & 网络环境不稳定 的数据存储、RPC 数据交换 的需求场景

如 即时IM (QQ、微信)的需求场景


总结

数据传输量较大的需求场景下,Protocol BufferXML、Json 更小、更快、使用 & 维护更简单!


5. 使用流程

使用 Protocol Buffer 的流程例如以下:

5.1 环境配置

  • 要使用Protocol Buffer 。须要先在电脑上安装Protocol Buffer

  • 整个 安装过程 仅仅须要依照以下步骤进行就可以:

    整个安装过程请 自备梯子 以保证 网络畅通

步骤1:下载 Protocol Buffer 安装包

此处选择 较稳定的版本号 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包的解析过程例如以下:
    1. Protocol buffer 的类型名称解析与 C++ 一致:从 最内部 開始查找,依次 向外 进行

      每一个包会被看作是其父类包的内部类

    2. Protocol buffer 编译器会解析 .proto文件里定义的全部类型名
    3. 生成器会依据 不同语言 生成 相应语言 的代码文件

      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 中:

    1. 一个 .proto 消息模型 = 一个 .proto文件 = 消息对象 + 字段
    2. 一个消息对象(Message) = 一个 结构化数据
    3. 消息对象(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定义为枚举类型,并将加入电话的集合( MOBILEHOMEWORK
// 枚举类型须要先定义才干进行使用

// 枚举类型 定义
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. 消息对象 类型

一个消息对象 能够将 其它消息对象类型 用作字段类型。情况例如以下:

imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt="消息对象 类型情况" title="">

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文件 通过导入( importB.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. 标识号使用范围:[1,2的29次方 - 1]
    3. 不可使用 [19000-19999] 标识号, 由于 Protobuf 协议实现中对这些标识号进行了预留。假若使用,则会报错
  • 编码占有内存规则:

    每一个字段在进行编码时都会占用内存,而 占用内存大小 取决于 标识号:

    1. 范围 [1,15] 标识号的字段 在编码时占用1个字节。
    2. 范围 [16,2047] 标识号的字段 在编码时占用2个字节
  • 使用建议

    1. 为频繁出现的 消息字段 保留 [1,15] 的标识号
    2. 为将来有可能加入的、频繁出现的 消息字段预留 [1,15] 标识号

关于 字段 的高级使用方法

1. 更新消息对象 的字段

  • 目的:为了满足新需求,须要更新 消息类型 而不破坏已有消息类型代码

    即新、老版本号须要兼容

  • 更新字段时,须要符合下列规则:

2. 扩展消息对象 的字段

  • 作用:使得其它人能够在自己的 .proto 文件里为 该消息对象 声明新的字段而不必去编辑原始文件

    1. 注:扩展 能够是消息类型也能够是字段类型
    2. 以下以 扩展 消息类型 为例

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

  • 详细生成文件与平台有关:

imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt="相应平台生成文件" title="">

  • 编译指令说明
// 在 终端 输入下列命令进行编译
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 Studiogradle 插件 protobuf-gradle-plugin,以便于在项目编译时 自己主动执行 Protocol Buffers 编译器

关于protobuf-gradle-plugin插件有兴趣的读者可自行了解。但个人还是建议使用 命令行,毕竟太过折腾插件不是必需

b. 动态编译

  • 需求场景:某些情况下。人们无法预先知道 .proto 文件,他们须要动态处理一些未知的 .proto 文件

    如一个通用的消息转发中间件。它无法预先知道须要处理什么类型的数据结构消息

  • 解决方式:动态编译.proto文件

由于使用得不多,此处不作过多描写叙述。详细请看官方文档

c. 编写新的 .proto 编译器

  • 需求场景: Protocol Buffer 仅支持 C++、java 和 Python 三种开发语言。一旦超出该三种开发语言,Protocol Buffer将无法使用
  • 解决方式:使用 Protocol BufferCompiler 包 开发出支持其它语言的新的.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 List<PhoneNumber> getPhoneList();
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 List<PhoneNumber> getPhoneList();
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(Iterable<PhoneNumber> value);
// 将一个装满元素的整个容器加入到列表中
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 编码方式 转化为 其它编码方式,如 JsonXML等等

    即将 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 BufferXML、Json 更小、更快、使用 & 维护更简单!

  • 以下用 一张图 总结在 Android平台中使用 Protocol Buffer 的整个步骤流程:

  • 看完本文,你应该会非常好奇为什么Protocol Buffer 的优势这么大:为什么序列化后的数据包比XML、Json更小、传输速度更快?
  • 下一篇文章我将对Protocol Buffer 进行源码分析。有兴趣能够继续关注我的CSDN博客!

请帮顶或评论点赞!

由于你的鼓舞是我写作的最大动力!

快来看看Google出品的Protocol Buffer,别仅仅会用Json和XML了的更多相关文章

  1. 快来看看Google出品的Protocol Buffer,别只会用Json和XML了

    前言 习惯用 Json.XML 数据存储格式的你们,相信大多都没听过Protocol Buffer Protocol Buffer 其实 是 Google出品的一种轻量 & 高效的结构化数据存 ...

  2. Android:Google出品的序列化神器Protocol Buffer使用攻略

    习惯用 Json.XML 数据存储格式的你们,相信大多都没听过Protocol Buffer Protocol Buffer 其实 是 Google出品的一种轻量 & 高效的结构化数据存储格式 ...

  3. Protocol Buffer 序列化原理大揭秘 - 为什么Protocol Buffer性能这么好?

    前言 习惯用 Json.XML 数据存储格式的你们,相信大多都没听过Protocol Buffer Protocol Buffer 其实 是 Google出品的一种轻量 & 高效的结构化数据存 ...

  4. 一个基于protocol buffer的RPC实现

    Protocol Buffer仅仅是提供了一套序列化和反序列化结构数据的机制,本身不具有RPC功能,但是可以基于其实现一套RPC框架. Services protocol buffer的Service ...

  5. Google Protocol Buffer 的使用和原理[转]

    本文转自: http://www.ibm.com/developerworks/cn/linux/l-cn-gpb/ Protocol Buffers 是一种轻便高效的结构化数据存储格式,可以用于结构 ...

  6. 学习Google Protocol buffer之概述

    XML这种属于非常强大的一种格式,能存储任何你想存的数据,而且编辑起来还是比较方便的.致命的缺陷在于比较庞大,在某些情况下,序列化和解析都会成为瓶颈.这种对于实时性很强的应用来说,就不太适合了,想象下 ...

  7. Google Protocol Buffer 简单介绍

    以下内容主要整理自官方文档. 为什么使用 Protocol Buffers .proto文件 Protocol Buffers 语法 编译.proto文件 Protocol Buffers API 枚 ...

  8. Google protocol buffer在windows下的编译

    在caffe框架中,使用的数据格式是google的 protocol buffer.对这个不了解,所以,想简单学习一下.简单来说,Protocol Buffer 是一种轻便高效的结构化数据存储格式,可 ...

  9. Google Protocol Buffer 的使用和原理

    Google Protocol Buffer 的使用和原理 Protocol Buffers 是一种轻便高效的结构化数据存储格式,可以用于结构化数据串行化,很适合做数据存储或 RPC 数据交换格式.它 ...

随机推荐

  1. 原生js大总结五

    041.在js中如何用方法将10进制的字符转换成16进制和8进制   数字.toString(16) 数字.toString(8)     042.如何创建时间对象   new Date()   04 ...

  2. Linux 命令笔记(1)

    [root@Oracle11_2 ~]# ll total -rw-------. root root May : anaconda-ks.cfg drwxr-xr-x. root root May ...

  3. FTP 访问的形式

    主要是扼要的列举一下访问的方式,不涉及太具体的内容.大家可以在百度上搜索一下具体的操作方法. 主要有: 1. 网页浏览器中输入 ftp://192.168.0.111的形式. 2. 资源管理器中输入f ...

  4. maven 解决Cannot change version of project facet Dynamic web module to 2.5

    我们用Eclipse创建Maven结构的web项目的时候选择了Artifact Id为maven-artchetype-webapp,由于这个catalog比较老,用的servlet还是2.3的,而一 ...

  5. IOS开发常用的开源组件

    .AFNetworking是一个开源的网络库 .EGORefreshTableHeaderView是一个实现向下拉刷新列表的组件 .MBProgressHUD是一个进度显示的组件 .EGOImageL ...

  6. 程序猿必备软件转载自 www.uhdesk.com

    XMLSpy 2012 企业版中文破解版 软件描写叙述: XMLSpy是XML(标准通用标记语言的子集)编辑器,支持WYSWYG.支持Unicode.多字符集,支持Well-formed和Valida ...

  7. php实现记忆化递归--以斐波那契数列为例(还是以边学边做为主,注重练习)

    php实现记忆化递归--以斐波那契数列为例(还是以边学边做为主,注重练习) 一.总结 1.递归不优化的话,30层开外就有点吃力了 2.php因为定义变量的时候不用定义变量类型,所以数组里面的类型也是p ...

  8. 嵌入式arm linux环境中gdb+gdbserver调试

    一.前言嵌入式Linux系统中,应用开发过程中,很多情况下,用户需要对一个应用程序进行反复调试,特别是复杂的程序.采用GDB方法调试,由于嵌入式系统资源有限性,一般不能直接在目标系统上进行调试,通常采 ...

  9. [Angular2Fire] Firebase auth (Google, Github)

    To do auth, first you need to go firebase.console.com to enable the auth methods, for example, enabl ...

  10. 编译pano13的一些注意事项

    作者:朱金灿 来源:error C2037: "jmpbuf"的左侧部分指定未定义的结构/联合"png_struct_def"e:\src\Test\libpa ...