百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术分析 > 正文

IM通讯协议专题(六):手把手教你如何在Android从零使用Protobuf

liebian365 2024-10-24 14:38 10 浏览 0 评论

1、前言

最近我负责的 LiveChat 客服聊天系统到了自研阶段,任务类似于做一个腾讯云IM这样的通信层SDK。在和后台进行技术选型讨论后,确定了数据传输层协议格式使用 Protobuf。

本文基于我对Protobuf在Android端的实际使用心得,手把手教你如何在Android端IM产品中使用Protobuf,希望对你有帮助。

2、系列文章

本文是系列文章中的第6 篇,总目录如下:

  • 《IM通讯协议专题学习(一):Protobuf从入门到精通,一篇就够!》
  • 《IM通讯协议专题学习(二):快速理解Protobuf的背景、原理、使用、优缺点》
  • 《IM通讯协议专题学习(三):由浅入深,从根上理解Protobuf的编解码原理》
  • 《IM通讯协议专题学习(四):从Base64到Protobuf,详解Protobuf的数据编码原理》
  • 《IM通讯协议专题学习(五):Protobuf到底比JSON快几倍?全方位实测!》
  • 《IM通讯协议专题学习(六):手把手教你如何在Android上从零使用Protobuf》(* 本文)
  • 《IM通讯协议专题学习(七):手把手教你如何在NodeJS中从零使用Protobuf》
  • 《IM通讯协议专题学习(八):金蝶随手记团队的Protobuf应用实践(原理篇)》
  • 《IM通讯协议专题学习(九):手把手教你如何在iOS上从零使用Protobuf》

3、Protobuf 介绍

Protobuf的全称是Protocol Buffers,它是 Google 推出的一种与平台无关、语言无关、可扩展的轻便高效的序列化数据存储格式,类似于我们常用的 xml 和 json。

一个疑问:既然有了 XML 和 JSON,Google 为啥还要推出 Protobuf 呢?

存在即是合理,Protobuf 的优势用两个字总结就是:小、快。

相同的数据内容,用 Protobuf 序列化后的大小是 JSON 的十分之一,是 XML 格式的二十分之一,而且性能是他们的 5~100 倍。

通常情况下,我们使用 XML 或者 JSON 进行数据通信是没什么问题的,但是在性能优化的场景下,如果有办法压缩数据量、提高传输效率,显然会给用户带来更快更流畅的体验。因此我在做 LiveChat 自研技术选型时,Protobuf 成为了我们进行数据传输协议格式的第一选择。

4、Protobuf 环境配置

4.1概述

介绍两种安装 Protobuf 的方式:

  • 1)Github 下载 Protobuf 并安装;
  • 2)brew 安装。

4.2Github 下载 Protobuf 并安装

Protobuf 版本尽量保持前后台一致,这里是后台和我约定的一个版本(点此下载)。

1)根据自己的系统选择相应的 zip 包:

以我下载的为例,解压后结构如下:

可以看到 bin 目录下有个 protoc 的可执行文件。

我们给它配置一下环境变量就可以使用了(以我的为例):

# protobuf 环境变量

exportPROTOBUF_HOME=/Users/zhouying/Downloads/protoc-3.19.2-osx-x86_64

exportPATH=${PATH}:${PROTOBUF_HOME}/bin

配置好后,使用 protoc --version 命令验证:

可以看到打印出了版本,证明我们安装成功了。

4.3brew 安装

直接使用以下命令就可以一键安装或卸载:

//一键安装 protobuf

brew installprotobuf

//一键卸载 protobuf

brew uninstall protobuf

PS:这种方式只适用于 Mac 系统,而且安装的 protobuf 为最新版本,因此如果想要使用指定的版本,建议使用上小节里的Github下载安装这种方式。

5、使用 IDE 创建 Protobuf 文件

这里我使用的 IDE 是 AndroidStudio。如果你还在用Eclipse+ADT开发Android程序,那我也不知道说啥好。

5.1创建一个以 .proto 为后缀的文件

如下图:

可以看到这个文件前面的 icon 是个文本类型的,而且你在编写代码时,没有任何提示,关键字也没高亮显示,此时我们需要集成一个插件来支持 Protobuf 的编写。

5.2集成 Protocol Buffers 插件

当我们集成这个插件后,可以看到文件 icon 和代码高亮的变化,而且还会有语法提示(如下图所示)。

ok,接下来我们就可以愉快的编写 Protobuf 了,但是要怎么写呢?你是不是还不会 Protobuf 的语法,接着往下看。

6、Protobuf 语法基础

关于 Protobuf 的语法有很多,限于篇幅,我们这里只讲一些常用的(如果有兴趣进一步学习的,可以详读本系列的前几篇,比如《Protobuf从入门到精通,一篇就够!》、《快速理解Protobuf的背景、原理、使用、优缺点》)。

6.1Protobuf 常用关键字介绍

PS:在 Proto3 中不支持 required (必须)字段。

6.2Protobuf 基本数据类型

可变长度编码和固定长度编码区别:

//例如说我在 Java 里面进行如下定义:

inta = 1;

//因为 int 类型占 4 个字节,1个字节占 8 位,我们把 1 的字节占位给列出来:

00000000000000000000000000000001

//可以看到 1 的前面 3 个字节占位都是 0,在 Protobuf 里面是可以去掉的,于是就变成了:

00000001

//因此 1 在 Protobuf 里面就只占用了一个字节,节省了空间

上面这种就是可变长度编码。而固定长度编码就是即使前面的字节占位是 0,也不能去掉,我就是要占这么多字节。

基本数据类型默认值:

6.3消息类型定义

在上面创建的 Protobuf 文件中,定义一个学生,学生有姓名,年龄,邮箱和课程。

我们来实现一下它:

//指定 Protobuf 版本

syntax = "proto3";

//指定包名

package erdai;

//定义一个学生的消息类

message Student{

//姓名

string name = 1;

//年龄

int32 age = 2;

//邮箱

string email = 3;

//课程

repeated string course = 4; //相当于 Java 的 List<String>

}

有几个地方需要注意:

  • 1)一个 Protobuf 文件里面可以添加多个消息类,也可以进行嵌套;
  • 2)上面的 1,2,3,4 并不是给字段赋值,而是给每个字段定义一个唯一的编号(这些编号用于二进制格式中标识你的字段,并且在使用你的消息类型后不应更改);
  • 3)1-15 的字段编号只占一个字节进行编码,16-2047 的字段编号占两个字节,包括字段编号和字段类型,因此建议更多的使用 1-15 的字段编号;
  • 4)可以指定最小字段编号为 1,最大字段编号为 2^29 - 1 或 536870911(另外不能使用 19000-19999 的标识号,因为 protobuf 协议实现对这些进行了预留,同样也不能使用任何以前保留(reserved) 的字段编号)。

6.4Protobuf 枚举类型

message Weather{

int32 query = 1;

//季节

enum Season{

//允许对枚举常量设置别名

option allow_alias = true;

//枚举里面的 = 操作是对常量进行赋值操作

//春

SPRING = 0;

//夏

SUMMER = 1;

//秋 如果不设置别名,不允许存在两个相同的值

FALL = 2;

AUTUMN = 2;

//冬

WINTER = 3;

}

//对 season 进行编号

Season season = 2;

}

有几个地方需要注意:

  • 1)定义枚举类型使用 enum 关键字;
  • 2)枚举类型第一个字段的值为必须 0,否则编译会报错;
  • 3)枚举常量值必须在 32 位整型值的范围内(因为 enum 值是使用可变编码方式的,对负数不够高效,因此不推荐在 enum 中使用负数);
  • 4)枚举里面的 = 操作是对常量进行赋值操作,而枚举外面的 = 则是对当前字段进行编号。

6.5Protobuf 集合

repeated string list = 1; //类似 Java 的 List<String>

map<string,string> = 2; //类似 Java 的 Map<String,String>

有几个地方需要注意:

  • 1)Protobuf 中定义集合,就是在你定义好的属性前面加 repeated 关键字;
  • 2)Protobuf 中定义 map 和 Java 类似,只不过 map 是小写的。

6.6reserved 保留字段

当我们使用 reserved 关键字指定一个字段编号或字段名为保留字段后,我们就不能去使用它,否则编译器会报错(如下图所示)。

ok,简单的介绍了 Protobuf 语法后,接下来我们使用 AndroidStudio 将 Protobuf 文件转换为对应的 Java 文件来进行开发。

7、Protobuf 转 Java 文件

首先我们要明白一点:Protobuf 是一种与平台,语言无关的数据存储格式,因此我们要在其它语言如:Java,Kotlin,Dart 等语言中使用它,则必须将 Protobuf 文件转换为对应平台的语言文件去使用。

这里我们以转 Java 文件为例,介绍两种转换的方式:

  • 1)集成 Protobuf 插件转换;
  • 2)使用 protoc 命令行转换。

在使用这两种方式转换前,我们需要先集成 protobuf-java 这个第三方库,因为我们转换的 Java 文件中会使用这个库里面的功能。

在Android Studio的工作配置中集成 protobuf-java:

implementation 'com.google.protobuf:protobuf-java:3.19.2'

最新版本可查看此链接:https://github.com/protocolbuffers/protobuf/tree/main/java

7.1集成 Protobuf 插件转换

插件链接是:https://github.com/google/protobuf-gradle-plugin

第一步:在项目的根 build.gradle 文件中,添加如下依赖:

buildscript {

repositories {

gradlePluginPortal()

}

dependencies {

classpath 'com.google.protobuf:protobuf-gradle-plugin:0.8.19'

}

}

第二步:在 app 的 build.gradle 文件中应用插件:

//1、如果使用的是 apply 方式,Android 插件必须在 Protobuf 插件之前,如下:

apply plugin: 'com.android.application'

apply plugin: 'com.google.protobuf'

//2、如果使用的是 Gradle DSL 方式,则与顺序无关

plugins {

id'com.google.protobuf'

id'com.android.application'

}

第三步:在 app 的 build.gradle 文件中配置 protobuf 编译任务:

android {

//...

}

protobuf {

//配置 protoc 编译器

protoc {

artifact = 'com.google.protobuf:protoc:3.19.2'

}

//配置生成目录,编译后会在 build 的目录下生成对应的java文件

generateProtoTasks {

all().each { task ->

task.builtins {

remove java

}

task.builtins {

java {}

}

}

}

}

第四步:指定 protobuf 文件所在路径:

android {

//...

sourceSets {

main {

//实际测试指不指定无所谓,不影响 Java 文件生成

proto {

srcDir 'src/main/proto'

}

}

}

}

经过上面 4 步,我们的插件就算配置完成了,接下来我们测试一下。

以我们上面那个 student.proto 为例,里面的内容如下:

//指定 Protobuf 版本

syntax = "proto3";

//指定包名

package erdai;

//定义一个学生的消息类

message Student{

//姓名

string name = 1;

//年龄

int32 age = 2;

//邮箱

string email = 3;

//课程

repeated string course = 4;

}

//定义一个天气的消息类

message Weather{

int32 query = 1;

//季节

enum Season{

option allow_alias = true;

//春

SPRING = 0;

//夏

SUMMER = 1;

//秋

FALL = 2;

AUTUMN = 2;

//冬

WINTER = 3;

}

Season season = 2;

}

编译后,会在 app/build/generated/source/proto/ 目录下生成如下文件:

这种方式会自动给我们生成对应的 Java 类,就是集成的时候麻烦一点。

7.2使用 protoc 命令行转换

执行如下命令:

protoc --java_out=${"你要生成的 Java 文件目录"} ${"Protobuf 文件位置"}

//以我的为例:

protoc --java_out=./app/src/main/java ./app/src/main/proto/student.proto

生成的文件:

这种方式的优点是使用非常简单,但是每一次创建新的 Protobuf 文件都需要执行一下命令。

7.3生成的 Java 文件介绍

关于这个类的内容我简单介绍下,大家也可以生成后自行看源码(如下图)。

接下来简单进行说明。

1)成的类 StudentOuterClass 有 4 个子类:

Student (实现了 StudentOrBuilder 接口)

Weather (实现了 WeatherOrBuilder 接口)

StudentOrBuilder (接口)

WeatherOrBuilder (接口)

2)Student 是 StudentOuterClass 的静态内部类,构造方法私有化,需通过 Builder 模式创建对象。另外 Student 提供了属性的 get 方法,无 set 方法,Builder 作为 Student 的静态内部类提供了属性的 get,set,clear方法,clear 就是清空某个属性回到初始值。

3)Weather 同 2。

8、Protobuf 的使用

接下来我们对刚才生成的 Java 类进行使用。

8.1基本调用

fun main(){

//构建 Protobuf 对象

val student = StudentOuterClass.Student.newBuilder()

.setName("erdai")

.setAge(18)

.setEmail("erdai666@qq.com")

.addAllCourse(mutableListOf("Math", "English", "Computer"))

.build()

println(student)

println()

println(StudentOuterClass.Weather.Season.SPRING.number)

println(StudentOuterClass.Weather.Season.SUMMER.number)

println(StudentOuterClass.Weather.Season.AUTUMN.number)

println(StudentOuterClass.Weather.Season.WINTER.number)

}

//打印结果

name: "erdai"

age: 18

email: "erdai666@qq.com"

course: "Math"

course: "English"

course: "Computer"

0

1

2

3

8.2序列化和反序列化

fun main(){

//1、构建 Protobuf 对象

val student = StudentOuterClass.Student.newBuilder()

.setName("erdai")

.setAge(18)

.setEmail("erdai666@qq.com")

.addAllCourse(mutableListOf("Math", "English", "Computer"))

.build()

//2、序列化并返回一个包含其原始字节的字节数组

val byteArray: ByteArray = student.toByteArray()

//3、反序列化从字节数组中解析消息

val parseStudent: StudentOuterClass.Student = StudentOuterClass.Student.parseFrom(byteArray)

}

9、Protobuf 和 JSON 相互转换

首先我们需要添加一个第三方依赖:

implementation 'com.google.protobuf:protobuf-java-util:3.19.2'

Protobuf 转 JSON:

fun main(){

//1、构建 Protobuf 对象

val student = StudentOuterClass.Student.newBuilder()

.setName("erdai")

.setAge(18)

.setEmail("erdai666@qq.com")

.addAllCourse(mutableListOf("Math", "English", "Computer"))

.build()

//2、Protobuf 转 Json

val json = JsonFormat.printer().print(student)

//3、打印 json

println(json)

}

//打印结果

{

"name": "erdai",

"age": 18,

"email": "erdai666@qq.com",

"course": ["Math", "English", "Computer"]

}

JSON 转 Protobuf:

fun main(){

//1、构建 Protobuf 对象

val student = StudentOuterClass.Student.newBuilder()

.setName("erdai")

.setAge(18)

.setEmail("erdai666@qq.com")

.addAllCourse(mutableListOf("Math", "English", "Computer"))

.build()

//2、Protobuf 对象转 Json

val json = JsonFormat.printer().print(student)

//3、Json 转 Protobuf 对象

val builder = StudentOuterClass.Student.newBuilder()

JsonFormat.parser().ignoringUnknownFields().merge(json,builder)

//4、打印 Protobuf 对象

println(builder.build())

}

//打印结果

name: "erdai"

age: 18

email: "erdai666@qq.com"

course: "Math"

course: "English"

course: "Computer"

10、Protobuf 和 Java 对象相互转换

这个实则是在前述的基础上,当我们将 Protobuf 转换为 JSON 后,在把 JSON 转换为对应的 Java 对象。

这里我们先引入 Gson库:

implementation 'com.google.code.gson:gson:2.8.9'

PS:类似于fastjson,Gson是Google出品的著名JSON解析库。

先定义一个接收当前 Json 的数据模型:

data class MyStudent(

varname: String? = "",

varage: Int = 0,

varemail: String? = "",

varcourse: MutableList<String>? = null

)

最后进行转换:

fun main(){

//1、构建 Protobuf 对象

val student = StudentOuterClass.Student.newBuilder()

.setName("erdai")

.setAge(18)

.setEmail("erdai666@qq.com")

.addAllCourse(mutableListOf("Math", "English", "Computer"))

.build()

//2、将 Protobuf 转换为 Json

val json = JsonFormat.printer().print(student)

//3、将 Json 转换为 Java Bean 对象

val myStudent = Gson().fromJson(json,MyStudent::class.java)

println(myStudent)

}

//打印结果

MyStudent(name=erdai, age=18, email=erdai666@qq.com, course=[Math, English, Computer])

11、本文小结

这里我小结一下关于 Protobuf 的开发流程:

  • 1)一般都是后台定义好 Protobuf;
  • 2)然后给到我们客户端;
  • 3)客户端在根据 Protobuf 生成对应平台的代码进行使用即可。

因此,上面我讲的这些内容如果你都掌握了,基本上就能满足Android端使用Protobuff进行日常开发了。

好了,本篇文章到这里就结束了,如果有啥问题,欢迎评论区一起交流。

12、参考资料

[1] Protobuf 官方开发者指南(中文译版)

[2] Protobuf官方手册

[3] Protobuf从入门到精通,一篇就够!

[4] 如何选择即时通讯应用的数据传输格式

[5] 强列建议将Protobuf作为你的即时通讯应用数据传输格式

[6] APP与后台通信数据格式的演进:从文本协议到二进制协议

[7] 面试必考,史上最通俗大小端字节序详解

[8] 移动端IM开发需要面对的技术问题(含通信协议选择)

[9] 简述移动端IM开发的那些坑:架构设计、通信协议和客户端

[10] 理论联系实际:一套典型的IM通信协议设计详解

[11] 58到家实时消息系统的协议设计等技术实践分享

[12] 金蝶随手记团队的Protobuf应用实践(原理篇)

[13] 新手入门一篇就够:从零开发移动端IM

学习交流:

- 移动端IM开发入门文章:《新手入门一篇就够:从零开发移动端IM》

- 开源IM框架源码:https://github.com/JackJiang2011/MobileIMSDK(备用地址点此)

(本文已同步发布于:http://www.52im.net/thread-4135-1-1.html)

相关推荐

快递查询教程,批量查询物流,一键管理快递

作为商家,每天需要查询许许多多的快递单号,面对不同的快递公司,有没有简单一点的物流查询方法呢?小编的回答当然是有的,下面随小编一起来试试这个新技巧。需要哪些工具?安装一个快递批量查询高手快递单号怎么快...

一键自动查询所有快递的物流信息 支持圆通、韵达等多家快递

对于各位商家来说拥有一个好的快递软件,能够有效的提高自己的工作效率,在管理快递单号的时候都需要对单号进行表格整理,那怎么样能够快速的查询所有单号信息,并自动生成表格呢?1、其实方法很简单,我们不需要一...

快递查询单号查询,怎么查物流到哪了

输入单号怎么查快递到哪里去了呢?今天小编给大家分享一个新的技巧,它支持多家快递,一次能查询多个单号物流,还可对查询到的物流进行分析、筛选以及导出,下面一起来试试。需要哪些工具?安装一个快递批量查询高手...

3分钟查询物流,教你一键批量查询全部物流信息

很多朋友在问,如何在短时间内把单号的物流信息查询出来,查询完成后筛选已签收件、筛选未签收件,今天小编就分享一款物流查询神器,感兴趣的朋友接着往下看。第一步,运行【快递批量查询高手】在主界面中点击【添...

快递单号查询,一次性查询全部物流信息

现在各种快递的查询方式,各有各的好,各有各的劣,总的来说,还是有比较方便的。今天小编就给大家分享一个新的技巧,支持多家快递,一次能查询多个单号的物流,还能对查询到的物流进行分析、筛选以及导出,下面一起...

快递查询工具,批量查询多个快递快递单号的物流状态、签收时间

最近有朋友在问,怎么快速查询单号的物流信息呢?除了官网,还有没有更简单的方法呢?小编的回答当然是有的,下面一起来看看。需要哪些工具?安装一个快递批量查询高手多个京东的快递单号怎么快速查询?进入快递批量...

快递查询软件,自动识别查询快递单号查询方法

当你拥有多个快递单号的时候,该如何快速查询物流信息?比如单号没有快递公司时,又该如何自动识别再去查询呢?不知道如何操作的宝贝们,下面随小编一起来试试。需要哪些工具?安装一个快递批量查询高手快递单号若干...

教你怎样查询快递查询单号并保存物流信息

商家发货,快递揽收后,一般会直接手动复制到官网上一个个查询物流,那么久而久之,就会觉得查询变得特别繁琐,今天小编给大家分享一个新的技巧,下面一起来试试。教程之前,我们来预览一下用快递批量查询高手...

简单几步骤查询所有快递物流信息

在高峰期订单量大的时候,可能需要一双手当十双手去查询快递物流,但是由于逐一去查询,效率极低,追踪困难。那么今天小编给大家分享一个新的技巧,一次能查询多个快递单号的物流,下面一起来学习一下,希望能给大家...

物流单号查询,如何查询快递信息,按最后更新时间搜索需要的单号

最近有很多朋友在问,如何通过快递单号查询物流信息,并按最后更新时间搜索出需要的单号呢?下面随小编一起来试试吧。需要哪些工具?安装一个快递批量查询高手快递单号若干怎么快速查询?运行【快递批量查询高手】...

连续保存新单号功能解析,导入单号查询并自动识别批量查快递信息

快递查询已经成为我们日常生活中不可或缺的一部分。然而,面对海量的快递单号,如何高效、准确地查询每一个快递的物流信息,成为了许多人头疼的问题。幸运的是,随着科技的进步,一款名为“快递批量查询高手”的软件...

快递查询教程,快递单号查询,筛选更新量为1的单号

最近有很多朋友在问,怎么快速查询快递单号的物流,并筛选出更新量为1的单号呢?今天小编给大家分享一个新方法,一起来试试吧。需要哪些工具?安装一个快递批量查询高手多个快递单号怎么快速查询?运行【快递批量查...

掌握批量查询快递动态的技巧,一键查找无信息记录的两种方法解析

在快节奏的商业环境中,高效的物流查询是确保业务顺畅运行的关键。作为快递查询达人,我深知时间的宝贵,因此,今天我将向大家介绍一款强大的工具——快递批量查询高手软件。这款软件能够帮助你批量查询快递动态,一...

从复杂到简单的单号查询,一键清除单号中的符号并批量查快递信息

在繁忙的商务与日常生活中,快递查询已成为不可或缺的一环。然而,面对海量的单号,逐一查询不仅耗时费力,还容易出错。现在,有了快递批量查询高手软件,一切变得简单明了。只需一键,即可搞定单号查询,一键处理单...

物流单号查询,在哪里查询快递

如果在快递单号多的情况,你还在一个个复制粘贴到官网上手动查询,是一件非常麻烦的事情。于是乎今天小编给大家分享一个新的技巧,下面一起来试试。需要哪些工具?安装一个快递批量查询高手快递单号怎么快速查询?...

取消回复欢迎 发表评论: