Google Protobuf 实践使用开发

news2024/12/28 3:46:59

Android 敏捷开发助手

  1. Lottie动画 轻松使用
  2. PNG、JPG等普通图片高保真转SVG图
  3. Android 完美的蒙层方案
  4. Android MMKV框架引入使用
  5. 强大无匹的自定义下拉列表
  6. Google Protobuf 实践使用开发

Protobuf 实践使用

  • 前言
  • Protobuf基本介绍
  • Protobuf 使用配置
  • protobuf 基本语法
    • 1. 基本使用
    • 2. 赋值与清空
    • 3. 数据集合
    • 4. 文件操作
    • 5. 数据覆盖
    • 6. 枚举
  • protobuf结构分析
  • Protobuf JNI开发
  • protobuf版本差异化
    • 1. 差异化配置
    • 2. 多版本完整配置文件
    • 3. 多版本输出差异对比
  • protobuf异常处理
    • 1. proto文件中的import失效:cannot resolve import **.proto
  • protobuf优缺点
  • 总结

博客创建时间:2023.01.28
博客更新时间:2023.01.28

以Android studio build=7.0.0,SDKVersion 31来分析讲解。如图文和网上其他资料不一致,可能是别的资料版本较低而已。


前言

protobuf是google提供的一个开源序列化框架,类似于XML,JSON这样的数据表示语言,其最大的特点是基于二进制,因此比传统的XML表示高效短小得多。

protobuf在google中是一个比较核心的基础库,作为分布式运算涉及到大量的不同业务消息的传递,如何高效简洁的表示、操作这些业务消息在google这样的大规模应用中是至关重要的。而protobuf这样的库正好是在效率、数据大小、易用性之间取得了很好的平衡。

官方文档: http://code.google.com/p/protobuf/

protobuf语言指南:https://developers.google.com/protocol-buffers/docs/proto?csw=1&hl=zh-cn
protobuf编码文档:https://developers.google.com/protocol-buffers/docs/encoding?csw=1&hl=zh-cn
protobufAPI文档:https://developers.google.com/protocol-buffers/docs/reference/overview?csw=1&hl=zh-cn


Protobuf基本介绍

每个ProtocolBuffer mesage是一小段逻辑记录,包含一系列的键值对。这里有个非常简单的 .proto 文件定义了个人信息,它的定义方式和C++类很相似

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;
}

每个消息类型拥有一个或多个特定的数字字段,每个字段拥有一个名字和一个值类型。
值类型可以是数字(整数或浮点)、布尔型、字符串、原始字节或者其他ProtocolBuffer类型,还允许数据结构的分级。你可以指定可选字段,必选字段和重复字段。

限定修饰符

Required: 表示是一个必须字段,必须相对于发送方,在发送消息之前必须设置该字段的值,对于接收方,必须能够识别该字段的意思。发送之前没有设置required字段或者无法识别required字段都会引发编解码异常,导致消息被丢弃。

Optional: 表示是一个可选字段,可选对于发送方,在发送消息时,可以有选择性的设置或者不设置该字段的值。对于接收方,如果能够识别可选字段就进行相应的处理,如果无法识别,则忽略该字段,消息中的其它字段正常处理。

—因为optional字段的特性,很多接口在升级版本中都把后来添加的字段都统一的设置为optional字段,这样老的版本无需升级程序也可以正常的与新的软件进行通信,只不过新的字段无法识别而已,因为并不是每个节点都需要新的功能,因此可以做到按需升级和平滑过渡。

Repeated: 表示该字段可以包含0~N个元素。其特性和optional一样,但是每一次可以包含多个值。可以看作是在传递一个数组的值。


Protobuf 使用配置

以protobuf v3.6.1举例

1. 插件版本配置

在project的build.gradle文件中配置插件版本

buildscript {
    dependencies {
        classpath 'com.google.protobuf:protobuf-gradle-plugin:0.8.14'
    }
}

2. 依赖包配置
在module的build.gradle中配置protobuf依赖

dependencies {
    implementation 'com.googlecode.protobuf-java-format:protobuf-java-format:1.4'
    // proto依赖库
//    implementation 'com.google.protobuf:protobuf-lite:3.0.0'
    implementation 'com.google.protobuf:protobuf-java:3.6.1'
//    implementation 'com.google.protobuf:protobuf-javalite:3.18.1'
}

3. java 或cpp 文件生成

protobuf {
    protoc {
        //编译器版本
//        artifact = 'com.google.protobuf:protoc:3.0.0'
        artifact = 'com.google.protobuf:protoc:3.6.1'
//        artifact = 'com.google.protobuf:protoc:3.18.1'
    }

//    plugins {
//        javalite {
//            // 指定当前工程使用的protobuf版本为javalite版,以生成javalite版的java类
            artifact = 'com.google.protobuf:protoc-gen-javalite:3.8.0'
            artifact = 'com.google.protobuf:protoc-gen-javalite:3.0.0'
            artifact = 'com.google.protobuf:protoc-gen-javalite:3.6.1'
//
//        }
//    }


    //这里配置生成目录,编译后会在build的目录下生成对应的java文件
    generateProtoTasks {
        all().each { task ->
            task.builtins {
//                java {
//                    option "lite"
//                }
                // 生成CPP文件
//                cpp {   }
                remove java
            }

            task.plugins {
                //
//                javalite { }
                java {}
                cpp { }
            }
        }
    }
}

// builtins protoc编译配置,如果配置生成java、cpp、python等语言 
.0
//plugins protoc编译插件配置,如配置grpc插件等。 此处配置的插件必须在 protobuf.plugins 中预先定义

注意: 根据远程依赖包的配置方式,proto生成代码的方式也不一样
配置完上面的3步就可以使用protobuf文件拉


protobuf 基本语法

1. 基本使用

1**. 创建对象**

// 创建对象
val student= Student.newBuilder().setPhone("17712345678").build()
val student2= Student.newBuilder().setPhone("17712345678").build()
Log.i("11111111111111111", "student= $student")
Log.i( "11111111111111111", "student.toString()= $student")
Log.i( "11111111111111111", "student2.toString()= $student2")

输出:
student= # com.xuanyuan.protobuf.StudentOuterClass$Student@63764208
phone: "17712345678"
student.toString()= # com.xuanyuan.protobuf.StudentOuterClass$Student@63764208
phone: "17712345678"
student2.toString()= # com.xuanyuan.protobuf.StudentOuterClass$Student@63764208
phone: "17712345678"

2. 把对象序列化成字节数组

val bt: ByteArray = student.toByteArray()
Log.i("11111111111111111","student.toByteArray()= "+ bt.contentToString())
*****************************************************
输出:
student.toByteArray()= [34, 11, 49, 55, 55, 49, 50, 51, 52, 53, 54, 55, 56]

3. 把对象转换成字节String

val bys = student.toByteString()
Log.i("11111111111111111","student.toByteString()= "+bys.toStringUtf8())
*****************************************************
输出:
student.toByteString()= "17712345678

4. 从字节数组中解析成对象

val student20 = Student.parseFrom(bt)
Log.i("11111111111111111", "20 Student.parseFrom= $student20")
val student21 = Student.parseFrom(bys)
Log.i("11111111111111111", "21 Student.parseFrom= $student21")
*****************************************************
输出:
20 Student.parseFrom= # com.xuanyuan.protobuf.StudentOuterClass$Student@63764208
phone: "17712345678"
21 Student.parseFrom= # com.xuanyuan.protobuf.StudentOuterClass$Student@63764208
phone: "17712345678"

2. 赋值与清空

1. 属性赋值
prot对象不能直接通过对象赋值,只能 toBuilder 创建一个新对象。

student=student.toBuilder().setPhone("17711112222").setEmail("17711112222@163.com").build()
Log.i("11111111111111111", "设置属性  student= $student")
*****************************************************
输出:
设置属性  student= # com.xuanyuan.protobuf.StudentOuterClass$Student@76e1af76
email: "17711112222@163.com"
phone: "17711112222"

2. 清空指定字段值

student=student.toBuilder().clearPhone().build()
Log.i("11111111111111111", "清空号码  student= $student")
*****************************************************
输出:
清空号码  student= # com.xuanyuan.protobuf.StudentOuterClass$Student@c50b3b71
email: "17711112222@163.com"

3. 清空所有字段

student=student.toBuilder().clear().build()
Log.i("11111111111111111", "清空属性值  student= $student")
*****************************************************
输出:
清空属性值  student= # com.xuanyuan.protobuf.StudentOuterClass$Student@7ba6f

注意:赋值不改变原有对象的值,而是改动了新对象的值,看打印信息发现对象的地址每次都是不一样的。

3. 数据集合

1. 获取集合长度

var student= Student.newBuilder().setName("ken").build()
val size = student.teachersCount
Log.i("11111111111111111", "student.teachersCount= $size")
*****************************************************
输出:
student.teachersCount= 0

2. 获取指定index的t对象l

// 不存在则返回nul
val t1 = student.teachersList.getOrNull(1)
Log.i("11111111111111111", " student.teachersList.getOrNull(1) $t1")
*****************************************************
输出:
student.teachersList.getOrNull(1) null

//不存在则返回某个默认值
val teacher=Teacher.newBuilder().build()
val t2 = student.teachersList.getOrElse(1) { teacher }
Log.i("11111111111111111", " student.teachersList.getOrNull(1) $t2")
*****************************************************
输出:
student.teachersList.getOrElse # com.xuanyuan.protobuf.TeacherOuterClass$Teacher@7baa4

3. 获取指定对象的index

val index = student.teachersList.indexOf(teacher)
Log.i("11111111111111111", " 指定索引对象index $t2")
*****************************************************
输出:
指定索引对象index -1

4. 获取集合最后一个元素index

val lastIndex = student.teachersList.lastIndex
Log.i("11111111111111111", " lastIndex 对象index $t2")
*****************************************************
输出:
lastIndex对象index -1

5. 获取指定index的对象

val teachersList = student.teachersList
val list = ArrayList(teachersList)
list.add(teacher)
student = student.toBuilder().addAllTeachers(list).build()
val t3 = student.teachersList[0]
Log.i("11111111111111111", " student.teachersList[1]= $t3")
*****************************************************
输出:
student.teachersList[1]= # com.xuanyuan.protobuf.TeacherOuterClass$Teacher@7baa4

注意:

  1. 和List一样,消息数组越界异常,可能会崩溃。
  2. 如上代码直接teachersList不可以直接进行ad操作,否则会报异常,需要再转一次。

4. 文件操作

val student = Student.newBuilder().setName("ken").build()
//将二进制数据写入文件
val filePath = externalCacheDir?.absolutePath + File.separator + "cache.cache"
FileOutputStream(filePath).use { student.writeTo(it) }

//从文件中读取二进制,并且转换成对象
FileInputStream(filePath).use { val p2 = Student.parseFrom(it) }

5. 数据覆盖

val p1 = Student.newBuilder().setName("ken").setId(100).setPhone("177").build()
val p2 = Student.newBuilder().setName("luo").setId(50).build()
//p2数据覆盖p1数据,没有覆盖的字段,保留p1数据
val p3 = p1.toBuilder().mergeFrom(p2).build()
Log.i("11111111111111111", " mergeFrom= $p3")
*****************************************************
输出:
mergeFrom= # com.xuanyuan.protobuf.StudentOuterClass$Student@cb25185d
id: 50
name: "luo"
phone: "177"

6. 枚举

enum Month {
    //允许对枚举常量设置别名
    option allow_alias = true;
    // 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;
}

Student.newBuilder().setMonth(StudentOuterClass.Month.FEBRUARY).build()

注意:

  1. 枚举类型第一个字段的值为必须 0,否则编译会报错。
  2. 枚举常量值必须在 32 位整型值的范围内。因为enum 值是使用可变编码方式的,对负数不够高效,因此不推荐在enum 中使用负数。

protobuf结构分析

存储方式

protobuf是基于128bits的数值存储方式(Base 128 Varints)。数据表示方式:每块数据由接连的若干个字节表示(小的数据用1个字节就可以表示),每个字节最高位标识本块数据是否结束(1:未结束,0:结束),低7位表示数据内容。(可以看出数据封包后体积至少增大14.2%)

Protobuf 是基于128bits的小端字节序,即每个字节由 1位(最高位结束标识)+7位(数据位)表示,每个字节最高表示127。如果数据超过128则需要2个及以上字节表示。下面用一个示例说明:

数字1的表示方法为:0000 0001,这个容易理解
数字300的表示方法为:1010 1100 0000 0010,为什么会这样?
1.首先300超过128 需要两个字节表示,用正常数据表示应该为1 0010 1100
2.因为protobuf是128bits数值存储方式,每7位表示有效数据,所以其数据为10 0101100
3.protobuf是小端字节序,所以数据形式0101100 10
4.添加1位结束标识位,不足8位补齐,则1010 1100 0000 0010

所以1010 1100 0000 0010就是数据300在protobuf中传输的数据形式

组成分解

protobuf中的对象数据是怎样传输的呢?其数据组成由:
数据头(tag号(从1开始)+数据类型)+数据组成
msg1_head + msg1 + msg2_head + msg2 + …

message Student{
  string name = 1;
  int32 id = 2;
  int32 age = 3;
  string email = 4;
  string phone = 5;
  repeated Teacher teachers = 6;
  Month month = 7;
  bool sex = 8;
}

执行代码

val tt00 = Student.newBuilder().setName("ken").build()
Log.i("11111111111111111", "tt00.id= " + tt00.toByteArray().contentToString())

val tt = Student.newBuilder().setId(5).build()
Log.i("11111111111111111", "tt.id= " + tt.toByteArray().contentToString())

val tt11 = Student.newBuilder().setAge(69).build()
Log.i("11111111111111111", "tt11.id= " + tt11.toByteArray().contentToString())

val tt22 = Student.newBuilder().setEmail("1211").build()
Log.i("11111111111111111", "tt22.id= " + tt22.toByteArray().contentToString())

val tt33 = Student.newBuilder().setSex(true).build()
Log.i("11111111111111111", "tt33.id= " + tt33.toByteArray().contentToString())

输出数据

10:28:23.263  I  tt00.id= [10, 3, 107, 101, 110]
10 =00001010 分解为00001:标识第一个字段,010为数据类型为2(数据类型为String)
3 数据长度
107101,110 拼接为“ken”

10:28:23.263  I  tt.id= [16, 5]
16=00010000 分解为00010,第二个字段为id,000数据类型为0(数据类型为整型,无需长度)
5 为id值

10:28:23.263  I  tt11.id= [24, 69]
24 =分解为000113个字段,000数据类型整型

10:28:23.263  I  tt22.id= [34, 4, 49, 50, 49, 49]
34 =00100 0104:第4个字段,2:字符串类型数据
4 数据4个长度
49, 50, 49, 49 标识1211

10:28:23.263  I  tt33.id= [64, 1]
10:28:23.264  I  tt44.id= [10, 3, 107, 101, 110, 16, 5, 24, 69, 34, 4, 49, 50, 49, 49, 64, 1]

数据类型

类型含义数据类型
0Varintint32, int64, uint32, uint64, sint32, sint64, bool, enum
164-bitfixed64, sfixed64, double
2Length-delimitedstring, bytes, embedded messages, packed repeated fields
3Start groupgroups (deprecated)
4End groupgroups (deprecated)
532-bitfixed32, sfixed32, float

Protobuf JNI开发

1. 生成protobuf cpp文件

protobuf的原始文件格式是这样的,文件是以.proto格式结尾。
当我们前面的步骤配置好运行代码,则编译器会自动生成proto的java文件和cpp文件,该文件生成位置在build文件中
在这里插入图片描述
java文件会根据proto文件名生成对应的 名称+OuterClass.java 文件

在这里插入图片描述
cpp文件会生成对应名称的.pb.cc和.pb.h文件
在这里插入图片描述

2. proto JNI开发配置

  1. 在build>>generated 文件中的proto cpp文件是一种缓存文件,在cpp的其他.h或者.cpp文件文件中是无法使用的,需要将proto cpp文件复制到main目录中。
    在这里插入图片描述
  2. 将.pb.cc与.pb.h文件放入main的 cpp文件夹后任然不可使用,因为生成的proto文件是依赖于google protobuf文件的,这些文件也需要将其导入到main 中的JNI目录下。
    在这里插入图片描述
  3. 需要下载google 官网中的proto支持文件置于JNI目录下。如图中main>>cpp>>google文件夹中
    下载地址https://github.com/protocolbuffers/protobuf/tree/main/src
    在这里插入图片描述

protobuf版本差异化

protobuf版本有很多,不同版本可能互相不兼容,当你使用proto用于输出传输时,如固件端、云端与APP端需要确认两端使用的protobuf版本一致。

1. 差异化配置

1. proto编译器配置
protobuf的编译器版本配置格式是一样的,只用切换不同版本就行了

    protoc {
        //编译器版本
//        artifact = 'com.google.protobuf:protoc:3.0.0'
        artifact = 'com.google.protobuf:protoc:3.6.1'
//        artifact = 'com.google.protobuf:protoc:3.18.1'
    }

2. plugins.javalite 配置
当你的远程依赖包使用的protobuf-lite、protobuf-javalite时,会使用的上plugins#javalite ,当远程仓库使用方式为protobuf-java时,可以屏蔽javalite 插件配置

//    plugins {
//        javalite {
//            // 指定当前工程使用的protobuf版本为javalite版,以生成javalite版的java类
            artifact = 'com.google.protobuf:protoc-gen-javalite:3.8.0'
            artifact = 'com.google.protobuf:protoc-gen-javalite:3.0.0'
            artifact = 'com.google.protobuf:protoc-gen-javalite:3.6.1'
//
//        }
//    }

3. generateProtoTasks 配置
generateProtoTasks是生成proto java或cpp文件的关键配置

    generateProtoTasks {
        all().each { task ->
            task.builtins {
//                java {
//                    option "lite"
//                }
                // 生成CPP文件
//                cpp {   }
                remove java
            }

            task.plugins {
                // 远程依赖使用protobuf-lite、protobuf-javalite 则使用javalite { }
                // javalite { }
                // 远程依赖使用protobuf-java方式则使用java {}
                java {}
                cpp { }
            }
        }
    }

2. 多版本完整配置文件

不同版本project的build.gradle文件是一致的,但是不通的protobuf版本在module中的配置是不一样的,生成的java 和cpp也有差异

buildscript {
    dependencies {
        classpath 'com.google.protobuf:protobuf-gradle-plugin:0.8.14'
    }
}

1.v3.0.0

// 生成proto JNI开发.pb.h 和.pb.cc 格式文档
plugins {
    id 'com.android.application'
    // 需要添加插件支持
    id 'com.google.protobuf'
}

protobuf {
    protoc {
        //编译器版本
        artifact = 'com.google.protobuf:protoc:3.0.0'
//        artifact = 'com.google.protobuf:protoc:3.6.1'
//        artifact = 'com.google.protobuf:protoc:3.18.1'
    }

    plugins {
        javalite {
            // 指定当前工程使用的protobuf版本为javalite版,以生成javalite版的java类
//            artifact = 'com.google.protobuf:protoc-gen-javalite:3.8.0'
            artifact = 'com.google.protobuf:protoc-gen-javalite:3.0.0'
//            artifact = 'com.google.protobuf:protoc-gen-javalite:3.6.1'

        }
    }

    //这里配置生成目录,编译后会在build的目录下生成对应的java文件
    //当你的远程依赖包使用的protobuf-lite、protobuf-javalite时,会使用的上plugins#javalite,
    // 当远程仓库使用方式为protobuf-java时,可以屏蔽javalite 插件配置
    generateProtoTasks {
        all().each { task ->
            task.builtins {
//                java {
//                    option "lite"
//                }
                // 生成CPP文件
//                cpp {   }
                remove java
            }

            task.plugins {
                // 远程依赖使用protobuf-lite、protobuf-javalite 则使用javalite { }
                 javalite { }
                // 远程依赖使用protobuf-java方式则使用java {}
                // java {}
                cpp { }
            }
        }
    }
}

android {
    compileSdk 32

    defaultConfig {
        applicationId "com.xuanyuan.testndk"
        minSdk 21
        targetSdk 32
        versionCode 1
        versionName "1.0"

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
        externalNativeBuild {
            cmake {
                cppFlags ''
            }
        }
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }

    sourceSets {
        main {
            // 也可以自定义 proto文件的读取位置
//            proto {
//                // In addition to the default 'src/main/proto'
//                srcDir 'src/main/protobuf'
//                srcDir 'src/main/protocolbuffers'
//                // In addition to the default '**/*.proto' (use with caution).
//                // Using an extension other than 'proto' is NOT recommended,
//                // because when proto files are published along with class files, we can
//                // only tell the type of a file from its extension.
//                include '**/*.protodevel'
//            }
            java {

            }
        }
        test {
            proto {
                // In addition to the default 'src/test/proto'
                srcDir 'src/test/protocolbuffers'
            }
        }
    }

    ndkVersion "22.1.7171670"
    externalNativeBuild {
        cmake {
            path file('src/main/cpp/CMakeLists.txt')
            version '3.18.1'
        }
    }
    buildFeatures {
        viewBinding true
    }
}

dependencies {
    implementation 'androidx.appcompat:appcompat:1.5.1'
    implementation 'com.google.android.material:material:1.6.1'
    implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
    testImplementation 'junit:junit:4.13.2'
    androidTestImplementation 'androidx.test.ext:junit:1.1.3'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'


    implementation 'com.googlecode.protobuf-java-format:protobuf-java-format:1.4'

    // proto依赖库
    implementation 'com.google.protobuf:protobuf-lite:3.0.0'
//    implementation 'com.google.protobuf:protobuf-java:3.6.1'
//    implementation 'com.google.protobuf:protobuf-javalite:3.18.1'
}

2 v3.6.1

// 生成proto JNI开发.pb.h 和.pb.cc 格式文档
plugins {
    id 'com.android.application'
    // 需要添加插件支持
    id 'com.google.protobuf'
}

protobuf {
    protoc {
        //编译器版本
//        artifact = 'com.google.protobuf:protoc:3.0.0'
        artifact = 'com.google.protobuf:protoc:3.6.1'
//        artifact = 'com.google.protobuf:protoc:3.18.1'
    }

//    plugins {
//        javalite {
//            // 指定当前工程使用的protobuf版本为javalite版,以生成javalite版的java类
            artifact = 'com.google.protobuf:protoc-gen-javalite:3.8.0'
            artifact = 'com.google.protobuf:protoc-gen-javalite:3.0.0'
            artifact = 'com.google.protobuf:protoc-gen-javalite:3.6.1'
//
//        }
//    }

    //这里配置生成目录,编译后会在build的目录下生成对应的java文件
    generateProtoTasks {
        all().each { task ->
            task.builtins {
//                java {
//                    option "lite"
//                }
                // 生成CPP文件
//                cpp {   }
                remove java
            }

            task.plugins {
                //
//                javalite { }
                java {}
                cpp { }
            }
        }
    }
}

android {
    compileSdk 32

    defaultConfig {
        applicationId "com.xuanyuan.testndk"
        minSdk 21
        targetSdk 32
        versionCode 1
        versionName "1.0"

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
        externalNativeBuild {
            cmake {
                cppFlags ''
            }
        }
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }

    sourceSets {
        main {
            // 也可以自定义 proto文件的读取位置
//            proto {
//                // In addition to the default 'src/main/proto'
//                srcDir 'src/main/protobuf'
//                srcDir 'src/main/protocolbuffers'
//                // In addition to the default '**/*.proto' (use with caution).
//                // Using an extension other than 'proto' is NOT recommended,
//                // because when proto files are published along with class files, we can
//                // only tell the type of a file from its extension.
//                include '**/*.protodevel'
//            }
            java {

            }
        }
        test {
            proto {
                // In addition to the default 'src/test/proto'
                srcDir 'src/test/protocolbuffers'
            }
        }
    }

    ndkVersion "22.1.7171670"
    externalNativeBuild {
        cmake {
            path file('src/main/cpp/CMakeLists.txt')
            version '3.18.1'
        }
    }
    buildFeatures {
        viewBinding true
    }
}

dependencies {
    implementation 'androidx.appcompat:appcompat:1.5.1'
    implementation 'com.google.android.material:material:1.6.1'
    implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
    testImplementation 'junit:junit:4.13.2'
    androidTestImplementation 'androidx.test.ext:junit:1.1.3'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'


    implementation 'com.googlecode.protobuf-java-format:protobuf-java-format:1.4'

    // proto依赖库
//    implementation 'com.google.protobuf:protobuf-lite:3.0.0'
    implementation 'com.google.protobuf:protobuf-java:3.6.1'
//    implementation 'com.google.protobuf:protobuf-javalite:3.18.1'
}

3. v3.18.1

// 生成proto JNI开发.pb.h 和.pb.cc 格式文档
plugins {
    id 'com.android.application'
    // 需要添加插件支持
    id 'com.google.protobuf'
}

protobuf {
    protoc {
        //编译器版本
//        artifact = 'com.google.protobuf:protoc:3.0.0'
//        artifact = 'com.google.protobuf:protoc:3.6.1'
        artifact = 'com.google.protobuf:protoc:3.18.1'
    }

//    plugins {
//        javalite {
//            // 指定当前工程使用的protobuf版本为javalite版,以生成javalite版的java类
//            artifact = 'com.google.protobuf:protoc-gen-javalite:3.18.1'
            artifact = 'com.google.protobuf:protoc-gen-javalite:3.0.0'
            artifact = 'com.google.protobuf:protoc-gen-javalite:3.6.1'
//
//        }
//    }

    //这里配置生成目录,编译后会在build的目录下生成对应的java文件
    //当你的远程依赖包使用的protobuf-lite、protobuf-javalite时,会使用的上plugins#javalite,
    // 当远程仓库使用方式为protobuf-java时,可以屏蔽javalite 插件配置
    generateProtoTasks {
        all().each { task ->
            task.builtins {
                java {
                    option "lite"
                }
                // 生成CPP文件
                cpp {   }
                remove java
            }

            task.plugins {
                // 远程依赖使用protobuf-lite、protobuf-javalite 则使用javalite { }
//                 javalite { }
//                // 远程依赖使用protobuf-java方式则使用java {}
//                // java {}
//                cpp { }
            }
        }
    }
}

android {
    compileSdk 32

    defaultConfig {
        applicationId "com.xuanyuan.testndk"
        minSdk 21
        targetSdk 32
        versionCode 1
        versionName "1.0"

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
        externalNativeBuild {
            cmake {
                cppFlags ''
            }
        }
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }

    sourceSets {
        main {
            // 也可以自定义 proto文件的读取位置
//            proto {
//                // In addition to the default 'src/main/proto'
//                srcDir 'src/main/protobuf'
//                srcDir 'src/main/protocolbuffers'
//                // In addition to the default '**/*.proto' (use with caution).
//                // Using an extension other than 'proto' is NOT recommended,
//                // because when proto files are published along with class files, we can
//                // only tell the type of a file from its extension.
//                include '**/*.protodevel'
//            }
            java {

            }
        }
        test {
            proto {
                // In addition to the default 'src/test/proto'
                srcDir 'src/test/protocolbuffers'
            }
        }
    }

    ndkVersion "22.1.7171670"
    externalNativeBuild {
        cmake {
            path file('src/main/cpp/CMakeLists.txt')
            version '3.18.1'
        }
    }
    buildFeatures {
        viewBinding true
    }
}

dependencies {
    implementation 'androidx.appcompat:appcompat:1.5.1'
    implementation 'com.google.android.material:material:1.6.1'
    implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
    testImplementation 'junit:junit:4.13.2'
    androidTestImplementation 'androidx.test.ext:junit:1.1.3'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'


    implementation 'com.googlecode.protobuf-java-format:protobuf-java-format:1.4'

    // proto依赖库
//    implementation 'com.google.protobuf:protobuf-lite:3.0.0'
//    implementation 'com.google.protobuf:protobuf-java:3.6.1'
    implementation 'com.google.protobuf:protobuf-javalite:3.18.1'
}

3. 多版本输出差异对比

以proto v3.0.0、v3.6.1、v3.18.1三个版本生成的.java和.pb.h文件进行对比

java
在这里插入图片描述
在这里插入图片描述

cpp
在这里插入图片描述
在这里插入图片描述


protobuf异常处理

1. proto文件中的import失效:cannot resolve import **.proto

现象
当我们在一个A.proto文件中import另一文件B.proto时,会出现B.proto无法被引入的问题。
其实编译也能成功,不影响使用。但是看着不爽,不能自动引入文件中的message对象。
在这里插入图片描述

原因
其实是Idea的问题,不能识别proto目录下的XXX.proto文件

解决方案

  1. 进入ASsetting > Languages & Frameworks> Protocol Buffers然后取消选中Configure automatically
  2. 然后Add proto的目录路径即可
    在这里插入图片描述
  3. 然后会发现文件import 正常可以使用了
    在这里插入图片描述

protobuf优缺点

protobuf的优点
优点前面也提到了,主要有两个:

  1. 序列化和反序列化效率比 xml 和 json 都高(这个protobuf 自己做了测试,链接要翻墙);
  2. 字段可以乱序,欠缺,因此可以用来兼容旧的协议,或者是减少协议数据。

protobuf的缺点
但是字段允许乱序欠缺,反过来也是缺点。所以这里总结 protobuf 两个缺点,一个跟这有关:

  1. 如果字段过多,或者嵌套过深,都会影响反序列化效率,解析每一块数据都要根据序号找到对应的位置然后再插入到已解析好的数据中。
  2. 数据基于128bits的存储方式,单块数据比较大时效率很受影响。解析数据需要取到所有字节的低7位,然后再拼成一整块数据。以上两个缺点,特别是对于erlang这类没有指针的语言来说,代价就相当昂贵。
  3. 协议序号也要占空间,序号越大占空间越大,当序号小于16时无需额外增加字节就可以表示。

总结

一般都是后台定义好 Protobuf ,然后给到我们客户端,客户端再根据 Protobuf 生成对应平台的代码进行使用即可。

千万注意在使用protobuf时,一定要多方对齐protobuf的版本,因为protobuf源文件一样时,通过不同版本的protobuf编译器生成的proto java文件或者cpp文件其内容是不一样的。如果多端的版本不一致,使用中可能会引起异常。

使用注意

  1. 一个.proto文件中可以有多个message

  2. 这个tag是ProtoBuf编码使用来标识属性的,因此在定义了一个message的属性之后,最好不要再去修改属性的tag值以免造成旧数据解析错误。如代码中1,2,3,4就是tag。

    string name = 1;
    int32 id = 2;
    string email = 3;
    string phone = 4;
    
  3. 1-15 的字段编号只占一个字节进行编码,16-2047 的字段编号占两个字节,包括字段编号和字段类型,因此建议更多的使用 1-15 的字段编号。


相关链接

  1. Lottie动画 轻松使用
  2. PNG、JPG等普通图片高保真转SVG图
  3. Android 完美的蒙层方案
  4. Android MMKV框架引入使用
  5. 强大无匹的自定义下拉列表
  6. Google Protobuf 实践使用开发

扩展链接:

  1. Android CameraX 使用入门
  2. Android 今日头条屏幕适配详细使用攻略
  3. Android 史上最新最全的ADB及命令百科,没有之一

博客书写不易,您的点赞收藏是我前进的动力,千万别忘记点赞、 收藏 ^ _ ^ !

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/182994.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

JavaWeb-Ajax

JavaWeb-Ajax 3,Ajax 3.1 概述 AJAX (Asynchronous JavaScript And XML):异步的 JavaScript 和 XML。 我们先来说概念中的 JavaScript 和 XML,JavaScript 表明该技术和前端相关;XML 是指以此进行数据交换。 3.1.1 作用 AJAX…

用Python绘制傅里叶级数和泰勒级数逼近已知函数的动态过程

文章目录Taylor级数Fourier级数本文代码: Fourier级数和Taylor级数对原函数的逼近动画Taylor级数 级数是对已知函数的一种逼近,比较容易理解的是Taylor级数,通过多项式来逼近有限区间内的函数,其一般形式为 f(x)∑n0Nanxnf(x)\su…

Lua 运算符 - 较为特殊部分

Lua 运算符 - 较为特殊部分 参考至菜鸟教程。 算术运算符 操作符描述实例^乘幂A^2 输出结果 100-负号-A 输出结果 -10//整除运算符(>lua5.3)5//2 输出结果 2在 lua 中,/ 用作除法运算,计算结果包含小数部分,// 用作整除运算,计…

Shiro学习文档

Shiro Java安全框架 1.什么是权限管理 ​ 基本上涉及到用户参与的系统都要进行权限管理,权限管理属于系统安全的范畴,权限管理实现对用户访问系统的控制,按照安全规则或者安全策略控制用户可以访问而且只能访问自己被授权的资源。 权限管理…

oracle mysql postgresql opengauss 批量kill session

oracle alter system kill session sid,serial# immed; (根据v$session中查出sid和serial#进行替换) 这里提供一个常用脚本,支持跨实例kill会话 (替换&1条件或放到脚本调用都行) select alter system kill sessi…

Dubbo 简介

Apache Dubbo 是一款 RPC 服务开发框架,用于解决微服务架构下的服务治理与通信问题,官方提供了 Java、Golang 等多语言 SDK 实现。使用 Dubbo 开发的微服务原生具备相互之间的远程地址发现与通信能力, 利用 Dubbo 提供的丰富服务治理特性&…

嵌入式工程师的2022 || 2023

因为一些个人关系,2022年初我从北京回到了石家庄。在找工作,包括后续的研发工作中,不同地点的经历在对比中我逐渐总结出了一些经验。关于“人”方面的感悟我就不赘述了,下面主要在这里总结一些找工作,做工作的经验&…

八、迁移学习和多任务学习

文章目录1、迁移学习2、多任务学习3、端到端学习THE END1、迁移学习 \qquad迁移学习是将某个任务学习到的知识(神经网络的参数信息)迁移到另外一个相似的任务中使用,从而重复利用相似任务学习共同之处,节省模型训练的时间,提高模型的训练效率…

一名普通22届本科毕业生|前端程序员|22年年终总结

文章目录22年上半年:最后的学生时光隔离实习币基金迷茫困惑难受不要去想人生意义读书景点环境的力量再次隔离返校入职前的学习22年下半年:上班工作生活总结本来准备在22年年末写的,奈何那段时间工作太忙没抽出时间。现在是23年的1月27日&…

paddleOCR代码工程的MD模式改造

一、下载cmake_3.22.3,用于生成vs工程 Index of /files/v3.22 下载完成,解压文件,打开cmake-gui.exe,即可使用: 二、下载Git(Git-2.36.0-64-bit.exe)并按照默认路径安装,cmake需调用…

使用Moment格式化时间出现时间差

诶嘿,很离奇。前些天后端老哥给我说我这时间展示不对。 我就奇了个大怪,就是取的这个字段嘛,怎么肥事😭 看半天发现是时间格式化出的问题。 原代码⬇️,看起来没毛病嘛,值打印出来也与接口里的这个时间一…

到底卡在了哪里,2023年再撒谎网慢就说不过去了

前言互联网下行带来灵魂追问。钱花哪去了?产出在哪里?动辄自建的遮羞布逐步显现,不过自建的成本可能还不是最大的负担,掣肘的可能是把不重要的事情当成了主业来做,比如:互联网比如数字化转型比如研发效率和…

Verilog HDL基本语法规则

⭐本专栏针对FPGA进行入门学习,从数电中常见的逻辑代数讲起,结合Verilog HDL语言学习与仿真,主要对组合逻辑电路与时序逻辑电路进行分析与设计,对状态机FSM进行剖析与建模。 🔥文章和代码已归档至【Github仓库&#xf…

10大面试必备的排序算法 Python 实现(附源码)

今天给大家分享一篇关于Python实现排序算法的文章,来自GitHub。 排序算法是《数据结构与算法》中最基本的算法之一。 排序算法可以分为内部排序和外部排序,内部排序是数据记录在内存中进行排序,而外部排序是因排序的数据很大,一…

STM32入门基础

GPIO <1>说明 引脚电平&#xff1a;0~3.3V&#xff0c;部分可达到5V(参考引脚定义&#xff0c;带FT的就可以) 同时 GPIO有两个模式 输出模式&#xff1a;进行端口高低电平的输出&#xff0c;用于驱动LED&#xff0c;蜂鸣器等 输入模式&#xff1a;读取端口高低电平…

SimBERT剖析

SimBERT SimBERT&#xff0c;它是以Google开源的BERT模型为基础&#xff0c;基于微软的UniLM思想设计了融检索与生成于一体的任务&#xff0c;来进一步微调后得到的模型&#xff0c;所以它同时具备相似问生成和相似句检索能力。 UniLM UniLM是一个融合NLU和NLG能力的Transfo…

远程桌面控制:SmartCode VNC ViewerX ActiveX 3.10 Crack

SmartCode::ViewerX VNC 查看器 ActiveX 毫不费力地将 VNC 查看器功能添加到您的应用程序 SmartCode ViewerX VNC 查看器 ActiveX 使开发人员可以使用一组直观的 ActiveX 属性和方法完全访问 VNC 查看器功能。借助ViewerX 控件&#xff0c;开发人员可以轻松地为其应用程序提供屏…

将自己写的代码利用git 上传到码云(gitee)或者github----最简洁明了版

前置操作 注册并激活码云账号&#xff08; 注册页面地址&#xff1a;https://gitee.com/signup &#xff09; 生成并配置 SSH 公钥 官方提供的生成并配置公钥的方法点此 创建空白的码云仓库 输入仓库名称之后路径会自动生成 把本地项目上传到码云对应的空白仓库中 如果您是…

MyBatis-Plus快速开始

文章目录介绍特性框架结构传统编程模式快速开始介绍 MyBatis-Plus&#xff08;简称 MP&#xff09;是一个 MyBatis 的增强工具&#xff0c;在 MyBatis 的基础上只做增强不做改变&#xff0c;为简化开发、提高效率而生。只做增强不做改变的意思是&#xff1a;MP不会影响原生的 M…

[HITCON 2017]SSRFme(perl脚本中get命令执行漏洞)

目录 代码审计 思路 知识补充 PHP中$_SERVER的详细用法 pathinfo() 函数 str_replace() 函数 escapeshellarg peal函数中get命令漏洞 Perl中open命令执行&#xff08;GET&#xff09; 代码审计 <?phpif (isset($_SERVER[HTTP_X_FORWARDED_FOR])) {$http_x_headers…