Android-protobuf

本文深入探讨了Google的Protobuf序列化技术,包括其工作原理、特性及在数据交换和存储场景的应用。通过实例对比Protobuf与JSON、Gson的序列化效率,展示了其在大数据传输中的优势。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

https://developers.google.cn/protocol-buffers/docs/javatutorial
https://developers.google.cn/protocol-buffers/docs/proto
https://github.com/protocolbuffers/protobuf
https://zh.wikipedia.org/wiki/Protocol_Buffers
https://github.com/google/protobuf-gradle-plugin

1什么是Protobuf?

google开源项目。序列化数据结构的方案,通常用于编写需要数据交换或者需要存储数据的程序。这套方案包含一种用于描述数据结构的接口描述语言(Interface Description Language)和一个生成器,用于生成描述该数据结构的不同编程语言的源代码。

序列化与反序列化
序列化 : 将数据结构或对象转换成二进制串的过程

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

特点:
优点:
性能:
体积小:序列化后,数据大小可缩小3-10倍
序列化速度快:比XML和JSON快20-100倍
传输速度快:因为体积小,传输起来带宽和速度会有优化
使用

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

使用Gradle插件

https://github.com/google/protobuf-gradle-plugin

1 修改项目根目录下build.gradle
dependencies下增加classpath ‘com.google.protobuf:protobuf-gradle-plugin:0.8.8’

2 修改app目录下build.gradle
apply plugin: 'com.android.application’后加上apply plugin: ‘com.google.protobuf’

3 然后加入protobuf配置(与android{}同级)

protobuf {
    protoc {
        artifact = 'com.google.protobuf:protoc:3.0.0'
    }
    plugins {
        javalite {
            artifact = 'com.google.protobuf:protoc-gen-javalite:3.0.0'
        }
    }
    generateProtoTasks {
        all().each { task ->
            task.builtins {
                remove java
            }
            task.plugins {
                javalite { }
            }
        }
    }
}

4 最后dependencies下加入implementation ‘com.google.protobuf:protobuf-lite:3.0.0’

dependencies {
   ...
    implementation 'com.google.protobuf:protobuf-lite:3.0.0'
}

官方例子:
https://developers.google.cn/protocol-buffers/docs/javatutorial

在main下新建proto文件夹,新建test.proto,复制以下代码。

syntax = "proto2";

package tutorial;

option java_package = "com.example.tutorial";
option java_outer_classname = "AddressBookProtos";

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 phones = 4;//数组
}

message AddressBook {
  repeated Person people = 1;
}

点击make project
然后在app/build/generated/source/proto/debug/javalite/com/example/tutorial目录下可以看到生成了一个AddressBookProtos文件


接下来看下与Gson和fastjson的对比:

public class MainActivity extends AppCompatActivity {

    private static final String TAG = "MainActivity";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        protoTest();
    }
    
    private void protoTest() {
        AddressBookProtos.Person.PhoneNumber.Builder builder
                = AddressBookProtos.Person.PhoneNumber.newBuilder().setNumber("110");
        AddressBookProtos.Person.Builder zs = AddressBookProtos.Person.newBuilder()
                .setName("张三")
                .setId(1)
                .addPhones(builder);
        AddressBookProtos.Person.PhoneNumber.Builder builder1
                = AddressBookProtos.Person.PhoneNumber.newBuilder().setNumber("120");
        AddressBookProtos.Person.Builder ls = AddressBookProtos.Person.newBuilder()
                .setName("李四")
                .setId(2)
                .addPhones(builder1);
        AddressBookProtos.AddressBook addressBook = AddressBookProtos.AddressBook.newBuilder()
                .addPeople(zs)
                .addPeople(ls).build();

        long l = System.currentTimeMillis();
        byte[] bytes = addressBook.toByteArray();
        Log.e(TAG, "protobuf 序列化耗时:" + (System.currentTimeMillis() - l));
        Log.e(TAG, "protobuf 序列化数据大小:" + bytes.length);
        try {
            l = System.currentTimeMillis();
            AddressBookProtos.AddressBook.parseFrom(bytes);
            Log.e(TAG, "protobuf 反序列化耗时:" + (System.currentTimeMillis() - l));
        } catch (InvalidProtocolBufferException e) {
            e.printStackTrace();
        }

        JsonTest.fastJson();
        JsonTest.gson();
    }

}

导入依赖

    implementation 'com.google.code.gson:gson:2.8.2'
    implementation 'com.alibaba:fastjson:1.1.67.android'
import android.location.Address;
import android.util.Log;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.google.gson.Gson;

import java.io.Serializable;


public class JsonTest  {

    private static final String TAG = "JsonTest";

    public static void fastJson() {

        AddressBook addressBook = getObject();

        long l = System.currentTimeMillis();
        String data = JSON.toJSONString(addressBook);
        byte[] bytes = data.getBytes();
        Log.e(TAG, "FastJson 序列化耗时:" + (System.currentTimeMillis() - l));
        Log.e(TAG, "FastJson 序列化数据大小:" + bytes.length);


        l = System.currentTimeMillis();
        AddressBook addressBook1 = JSON.parseObject(new String(bytes), AddressBook.class);
        Log.e(TAG, "FastJson 反序列化耗时:" + (System.currentTimeMillis() - l));
//        Log.e(TAG,addressBook1.getPersons().get(0).getName());
//        Log.e(TAG,addressBook1.getPersons().get(0).getPhones().get(0).getNumber());
    }
    
    public static void gson(){
        AddressBook addressBook = getObject();
        long l = System.currentTimeMillis();
        Gson gson = new Gson();
        String data = gson.toJson(addressBook);
        byte[] bytes = data.getBytes();
        Log.e(TAG, "Gson 序列化耗时:" + (System.currentTimeMillis() - l));
        Log.e(TAG, "Gson 序列化数据大小:" + bytes.length);

        l = System.currentTimeMillis();

        AddressBook addressBook1 = gson.fromJson(new String(bytes), AddressBook.class);
        Log.e(TAG, "Gson 反序列化耗时:" + (System.currentTimeMillis() - l));
        
//        Log.e(TAG,addressBook1.getPersons().get(0).getName());
//        Log.e(TAG,addressBook1.getPersons().get(0).getPhones().get(0).getNumber());
    }
    
    
    
    private static AddressBook getObject(){
        AddressBook addressBook = new AddressBook();

        PhoneNumber p_110 = new PhoneNumber();
        p_110.setNumber("110");
        Person zs = new Person();
        zs.setId(1);
        zs.setName("张三");
        zs.addPhones(p_110);
        addressBook.addPersons(zs);


        PhoneNumber p_120 = new PhoneNumber();
        p_120.setNumber("120");
        Person ls = new Person();
        ls.setId(2);
        ls.setName("李四");
        ls.addPhones(p_120);
        addressBook.addPersons(ls);
        return addressBook;
    }
}

import java.util.ArrayList;
import java.util.List;

public class AddressBook {
    List<Person> persons;

    public AddressBook() {
        this.persons = new ArrayList<>();
    }

    public void addPersons(Person person) {
         persons.add(person);
    }

    public List<Person> getPersons( ) {
        return persons;
    }
}

import java.util.ArrayList;
import java.util.List;

public class Person {
    private String name;
    private int id;
    private String email;

    private List<PhoneNumber> phones;

    public Person() {
        phones = new ArrayList<>();
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }


    public void addPhones(PhoneNumber number) {
        phones.add(number);
    }

    public List<PhoneNumber> getPhones() {
        return phones;
    }
}

package com.hongx.protobuf;

public class PhoneNumber {
    enum PhoneType {
        MOBILE,
        HOME,
        WORK;
    }

    private String number;
    private PhoneType type;

    public String getNumber() {
        return number;
    }

    public void setNumber(String number) {
        this.number = number;
    }

    public PhoneType getType() {
        return type;
    }

    public void setType(PhoneType type) {
        this.type = type;
    }
}

MainActivity: protobuf 序列化耗时:51
MainActivity: protobuf 序列化数据大小:38
MainActivity: protobuf 反序列化耗时:15
JsonTest: FastJson 序列化耗时:116
JsonTest: FastJson 序列化数据大小:119
JsonTest: FastJson 反序列化耗时:62
JsonTest: Gson 序列化耗时:163
JsonTest: Gson 序列化数据大小:119
JsonTest: Gson 反序列化耗时:6

2 Protobuf序列化原理

2.1 protobuf数据结构

TLV存储方式
TAG-Length-Value
标识-长度-字段值
在这里插入图片描述

protobuf数据编码存储原理

对数据进行编码 + 存储
速度快:编解码方式简单(简单数学运算)
数据体积小:
对于不同数据类型 采用不同的 序列化方式(编码方式 & 数据存储方式)
将 消息里的每个字段 进行编码后,再利用T - L - V 存储方式 进行数据的存储,最终得到的是一个 二进制字节流

Varint编码方式

在这里插入图片描述
图:Varint编码方式

在这里插入图片描述
图:Varint解码过程

对于int32 / int64 类型的字段值(正数),Protocol Buffer直接采用 Varint编码
对于sint32 / sint64 类型的字段值(负数),Protocol Buffer会先采用 Zigzag 编码,再采用 Varint编码

Zigzag编码方式

在这里插入图片描述

T-V存储方式&编码原理

新建:HxTest.proto

syntax = "proto2";

package test;

option java_package = "com.hx.test";
option java_outer_classname = "HxTestProtos";

message HxTest{
//wire_type=0, field_number=1
//Tag  = (field_number << 3) | wire_type
// TAG=8

    required int32 id1 = 1;
    //wire_type=0, field_number=2
    //Tag  = (field_number << 3) | wire_type
    // TAG=16
    required int32 id2 = 2;
}
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        HxTestProtos.HxTest test = HxTestProtos.HxTest.newBuilder()
                .setId1(300)
                .setId2(296)
                .build();
        byte[] bytes = test.toByteArray();
        Log.e(TAG, Arrays.toString(bytes));

//        protoTest();
    }

Log输出:

MainActivity: [8, -84, 2, 16, -88, 2]

protobuf整个编码流程如下图:
在这里插入图片描述

核心源码分析

protobuf-3.8.0\java\core\src\main\java\com\google\protobuf\CodedOutputStream.java[1724行]

    @Override
    public void writeUInt32NoTag(int value) throws IOException {
      try {
        while (true) {
          if ((value & ~0x7F) == 0) {
            buffer.put((byte) value);
            return;
          } else {
            buffer.put((byte) ((value & 0x7F) | 0x80));
            value >>>= 7;
          }
        }
      } catch (BufferOverflowException e) {
        throw new OutOfSpaceException(e);
      }
    }
0x7F:	0111 1111
~0x7F:	1000 0000

	value
&	1000 0000
------------------
	1000 0000
或	0000 0000:value<128 能被1个字节存进去

	value
&	0111 1111
-----------------------
	0??? ????
|	1000 0000
-----------------------
	1??? ????

3 网络通信协议中使用

通讯协议分层模型
粘包和分包问题
自定义协议

4总结

1 尽量使用多用 optional或 repeated修饰符
因为若optional 或 repeated 字段没有被设置字段值,那么该字段在序列化时的数据中是完全不存在的,即不需要进行编码

2字段标识号(Field_Number)尽量只使用 1-15,且不要跳动使用
因为Tag里的Field_Number是需要占字节空间的。如果Field_Number>16时,Field_Number的编码就会占用2个字节,那么Tag在编码时也就会占用更多的字节;如果将字段标识号定义为连续递增的数值,将获得更好的编码和解码性能

3若需要使用的字段值出现负数,请使用 sint32 / sint64,不要使用int32 / int64
因为采用sint32 / sint64数据类型表示负数时,会先采用Zigzag编码再采用Varint编码,从而更加有效压缩数据

4 对于repeated字段,尽量增加packed=true修饰
因为加了packed=true修饰repeated字段采用连续数据存储方式,即T - L - V - V -V方式

<think>我们面对的是一个Android构建系统(Soong)中的模块依赖错误。错误信息指出在构建过程中某些模块(如`libged_sys`和`libged`)未定义,导致构建失败。根据提供的引用,错误发生在`vendor/mediatek/proprietary/hardware/gralloc_extra/Android.bp`文件中。 解决步骤: 1. **确认问题**:错误信息明确指出了未定义的模块,例如: - `vendor/mediatek/proprietary/hardware/gralloc_extra/Android.bp:22:1: "libgralloc_extra_sys" depends on undefined module "libged_sys"` - `vendor/mediatek/proprietary/hardware/gralloc_extra/Android.bp:59:1: "libgralloc_extra" depends on undefined module "libged"` 2. **分析原因**:通常,这种错误是由于以下原因之一: - 依赖的模块确实不存在(可能是拼写错误,或者该模块未被正确添加)。 - 模块定义在另一个Android.bp文件中,但当前构建系统没有包含该文件(可能是路径问题,或者该模块所在的目录未被包含在构建中)。 - 模块被定义,但由于条件编译(例如,只在特定产品或架构下编译)而未被包含。 3. **检查模块定义**: - 我们需要在代码库中搜索名为`libged_sys`和`libged`的模块。这些模块可能位于`vendor/mediatek`的其他部分。例如,使用`grep`命令搜索: ```bash grep -r "name: \"libged_sys\"" . grep -r "name: \"libged\"" . ``` - 如果找不到,则可能是模块名称错误,或者它们位于未被包含的路径中。 4. **检查模块依赖关系**: - 在`gralloc_extra`的Android.bp文件中,确保依赖的模块名称正确。例如,检查第22行和第59行附近的`shared_libs`或`static_libs`字段。 5. **检查构建配置**: - 确保包含这些模块的目录被添加到构建系统中。这通常通过`PRODUCT_PACKAGES`或类似机制完成。在设备特定的makefile(通常是`device.mk`或类似文件)中,需要包含这些模块。 - 另外,检查是否有条件编译语句(如`target: { vendor: true }`)可能导致模块未被包含。 6. **解决方案**: a. **如果模块存在**:确保模块所在的目录被包含在构建中。检查`Android.bp`文件所在的目录是否被添加到构建路径(例如,通过`SOONG_CONFIG_NAMESPACES`和`SOONG_CONFIG_*`变量)。 b. **如果模块不存在**: - 可能需要添加缺失的模块。检查Mediatek提供的文档或代码,确认这些模块是否应该存在。 - 或者,如果这些模块不是必需的,可以尝试移除对它们的依赖(但需要确保功能不受影响)。 c. **检查版本兼容性**:确保使用的代码库版本与设备配置匹配。有时,不同分支的代码可能有不同的模块定义。 7. **参考其他类似错误**:引用[4]中提到了另一个类似错误(`car-androidx-room-compiler`依赖未定义的模块),这通常也是由于模块缺失或路径问题引起。因此,解决方法类似。 8. **清理和重新构建**:在修复后,执行`make clean`或`m clean`,然后重新构建,以确保没有缓存问题。 具体到当前错误,我们关注的是`libged_sys`和`libged`。根据错误信息,它们应该在Mediatek专有的硬件目录中定义。因此,我们应检查: - `vendor/mediatek/proprietary/hardware/gralloc_extra`的同级目录或相关目录(如`vendor/mediatek/proprietary/hardware/libged`)是否存在,并且其中是否有定义这些模块的Android.bp文件。 如果发现这些模块确实定义在其他地方,但构建系统没有找到,则可能是构建配置没有包含该目录。我们可以尝试在设备配置中添加该目录,或者检查Mediatek的文档是否要求将某些路径添加到构建中。 如果问题仍然存在,可能需要联系Mediatek支持或查看是否有补丁未应用。 由于具体代码库可能有所不同,以上步骤需要根据实际环境调整。 相关问题: 1. 如何定位Soong构建系统中的未定义模块错误? 2. 在Android.bp文件中,如何正确声明模块依赖? 3. 如何将自定义的供应商模块集成到Android构建系统中?</think>### Soong构建系统中模块依赖错误的解决方法 您在构建Android系统时遇到的错误信息为: ```bash error: vendor/mediatek/proprietary/hardware/gralloc_extra/Android.bp:22:1: "libgralloc_extra_sys" depends on undefined module "libged_sys" error: vendor/mediatek/proprietary/hardware/gralloc_extra/Android.bp:59:1: "libgralloc_extra" depends on undefined module "libged" ``` 这表明Soong构建系统在解析`gralloc_extra`模块时,找不到其依赖的`libged_sys`和`libged`模块。以下是系统性的解决步骤: --- ### 步骤1:验证模块定义是否存在 1. **检查模块源码位置** 在代码库中搜索缺失模块的定义: ```bash find vendor/mediatek -name "libged_sys" -o -name "libged" ``` 正常情况下,这些模块应位于类似路径: `vendor/mediatek/proprietary/hardware/libged/` 2. **确认Android.bp文件存在** 进入对应目录检查是否有有效的构建描述文件: ```bash ls vendor/mediatek/proprietary/hardware/libged/Android.bp ``` 若文件缺失,需从原始代码仓库恢复或联系供应商提供[^3]。 --- ### 步骤2:修复依赖关系 1. **检查依赖声明** 打开报错文件`vendor/mediatek/proprietary/hardware/gralloc_extra/Android.bp`,定位到第22行和59行附近: ```python // 示例依赖声明 shared_libs: ["libged"], // 动态库依赖 static_libs: ["libged_sys"], // 静态库依赖 ``` 2. **修正依赖类型**(常见错误) - 若实际是源码级依赖,应使用`static_libs`而非`shared_libs` - 若需头文件依赖,改用`header_libs` --- ### 步骤3:检查构建配置 1. **查看设备配置** 确认设备Makefile(如`device/mediatek/mtXXXX/device.mk`)包含模块: ```makefile PRODUCT_PACKAGES += \ libged \ libged_sys ``` 2. **验证BoardConfig配置** 检查`BoardConfig.mk`是否启用相关硬件特性: ```makefile BOARD_USES_MTK_HARDWARE := true ``` --- ### 步骤4:同步代码与清理构建 1. **同步最新代码** ```bash repo sync -j8 vendor/mediatek/proprietary ``` 2. **清理构建缓存** ```bash make clean rm -rf out/soong/ ``` 3. **重新生成ninja文件** ```bash source build/envsetup.sh lunch <target> m nothing # 仅生成ninja文件 ``` --- ### 步骤5:高级调试 若仍失败,尝试: 1. **启用详细日志** ```bash export SOONG_LOGGING=verbose ./build/soong/soong_ui.bash --make-mode ``` 2. **检查模块可见性** 在`libged`的Android.bp中确保有: ```python visibility: ["//vendor/mediatek/proprietary/hardware/gralloc_extra"], ``` 3. **验证PLATFORM_SDK_VERSION** 某些模块要求特定SDK版本,在Android.bp中添加: ```python min_sdk_version: "29", ``` --- ### 根本原因分析 此类错误通常由以下原因导致: 1. **供应商代码不完整**:Mediatek专有代码未完全同步或版本不匹配 2. **依赖链断裂**:被依赖模块因编译条件被排除(如`target: { android: { enabled: false } }`) 3. **路径配置错误**:SOONG_CONFIG_*变量未正确指向供应商模块 --- ### 相关问题 1. 如何为Soong构建系统添加新的硬件模块? 2. Android.bp与Android.mk的依赖声明有何本质区别? 3. 当遇到`ninja: no work to do`但实际构建失败时,如何诊断?[^3] 4. 如何为MTK平台定制Soong构建配置? > 提示:若问题持续,检查`out/soong/build-error.log`获取详细错误上下文[^4]。 [^1]: Soong是Android新的构建系统,替代了旧的make系统,使用Android.bp文件描述模块。 [^2]: 构建命令`out/soong/.bootstrap/bin/soong_build`负责生成ninja文件。 [^3]: 当依赖模块未定义时,Soong构建过程会中断并报错`subcommand failed`。 [^4]: 构建失败时,详细错误日志通常位于`out/soong/`目录下。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值