Android NDK之 跨语言序列化Protocol Buffer 3 编译

Android NDK之 跨语言序列化Protocol Buffer 3 编译

关键词:Protobuf NDK CMake JNI

1、protobuf3介绍

Protocol Buffers简称protobuf,由Google开源,是一种跨语言传递对象的一种序列化框架,类似AIDL定义接口一样定义一个通用对象结构描述代码(.proto文件)来生成各语言端的序列化与反序列化的代码,目前在AOSP源码(Android 11)里面也能看到很多.proto文件,方便在Java和C++之前简化调用逻辑。

2、proto3 语法

protobuf3的使用需要先编写.proto文件来规定需要传递对象所包含的元素,具体语法结构、不同语言之间的数据定义参考: Language Guide (proto 3)

下面定义了一个Person对象来作为例子:

person.proto

proto 复制代码
syntax = "proto3";

message Person {
  string name = 1;
  int32 age = 2;
}

编写好proto后,需要为目标语言生成所需的代码了

3、proto3 编译工具

下载protoc程序,这里下载的是 protoc-v3.18.1-linux-x86_64 版本 protoc-v3.18.1

解压到工作目录,并创建src文件夹,放入上一步的protoc文件夹

ruby 复制代码
kryo@WSL1:/mnt/k/Android/NDK-Project/libprotobuf/misc/protoc-3.18.1-linux-x86_64$ tree -L 2
.      
├── bin
│   └── protoc
├── include
│   └── google
├── readme.txt
└── src
    ├── cpp
    ├── java
    └── person.proto

生成java代码

bash 复制代码
./protoc-3.18.1-linux-x86_64/bin/protoc  --cpp_out=src/cpp/ src/person.proto

生成C++代码

bash 复制代码
./protoc-3.18.1-linux-x86_64/bin/protoc  --java_out=src/java/ src/person.proto

src目录查看编译产物

css 复制代码
src/
├── cpp
│   ├── person.pb.cc
│   └── person.pb.h
├── java
│   └── PersonOuterClass.java
└── person.proto

已经成功生成了cpp和java的源文件,如果打开这些文件可以看到cpp文件还依赖一些protobuf的头文件,java也会依赖protbuf的包,需要进一步集成所需 依赖才能实例化person对象

4、Android集成

Android Studio 的集成可使用脚本自动把.proto文件转化成XXXOuterClass.java文件,这一步先开个坑,暂时利用手动生成

4.1 bulid.gradle增加proto依赖

gradle 复制代码
implementation 'com.google.protobuf:protobuf-java:3.18.1'

4.2 编写Java端序列化和反序列化代码

把生成的PersonOuterClass.java放到java源码目录

java 复制代码
//序列化
byte[] javaByteArray = PersonOuterClass.Person.newBuilder()
        .setAge(16)
        .setName("Kryo")
        .build()
        .toByteArray();

//反序列化
try {
    PersonOuterClass.Person person = PersonOuterClass.Person.parseFrom(javaByteArray);
} catch (InvalidProtocolBufferException e) {
    throw new RuntimeException(e);
}

5、NDK 编译 Protobuf3,完成一次C++与Java的对象传递

5.1、源码获取以及CMakeLists.txt编写

5.1.1 下载代码

下载链接

解压后找到protobuf-3.18.1/src/Makefile.am这个文件 Makefile.am文件找到以下两个变量的源码列表,其中lite版本可以单独编译,两个文件列表一起编译的到完整的libprotobuf

libprotobuf_lite_la_SOURCES

libprotobuf_la_SOURCES

5.1.2 创建cpp目录

demo目录存放protoc生成C++代码

google/protobuf/ 需要把protobuf-3.18.1/src/google目录完整拷贝过来

ruby 复制代码
kryo@WSL1:/mnt/k/Android/NDK-Project/libprotobuf/src/main/cpp/protobuf3$ tree -L 2
.
├── CMakeLists.txt
├── demo
│   ├── person.pb.cc
│   └── person.pb.h
├── google
│   └── protobuf
└── protobuf_jni.cpp

5.1.3 CMakeList编写

cmake 复制代码
cmake_minimum_required(VERSION 3.10.2)

project("protobuf3")

get_filename_component(PARENT_DIR ${CMAKE_CURRENT_SOURCE_DIR} DIRECTORY)
set(JNI_PATH ${PARENT_DIR})
message("JNI_PATH ${JNI_PATH}")

add_library(protobuf3 SHARED
        google/protobuf/any_lite.cc
        # ... ...
        # libprotobuf_lite_la_SOURCES 的源码列表
        # ... ...
        google/protobuf/stubs/time.h
        google/protobuf/wire_format_lite.cc
        ##################    lite src end  ##################
        google/protobuf/any.cc
        google/protobuf/any.pb.cc
        google/protobuf/api.pb.cc
        google/protobuf/compiler/importer.cc
        # ... ...
        # libprotobuf_la_SOURCES 的源码列表
        # ... ...
        google/protobuf/util/type_resolver_util.cc
        google/protobuf/wire_format.cc
        google/protobuf/wrappers.pb.cc
        demo/person.pb.cc
        demo-lite/person-lite.pb.cc
        protobuf_jni.cpp
        )

add_definitions(-DHAVE_PTHREAD)

target_include_directories(protobuf3 PUBLIC
        ${JNI_PATH}/protobuf3/
        )

target_link_libraries(protobuf3
        # List libraries link to the target library
        android
        log
        )

5.2 JNI函数编写

目的:把java序列化的byte数组通过JNI传递到C++ Native层,在Native层反序列化生成对象,处理后再序列化传递到Java层,Java再反序列化生成对 象,完成一次完整的对象传递。

NativeLib.java

java 复制代码
package com.kryo.libprotobuf;

public class NativeLib {

    // Used to load the 'libprotobuf' library on application startup.
    static {
        System.loadLibrary("protobuf3");
    }

    public native byte[] process(byte[] data);
}

protobuf_jni.cpp

cpp 复制代码
//
// Created by X86-TIAN on 2023-11-18.
//

#include <jni.h>
#include <android/log.h>
#include "demo/person.pb.h"
#include "demo-lite/person-lite.pb.h"

#define TAG "Protobuf3-Native"

#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, TAG, __VA_ARGS__)
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, TAG, __VA_ARGS__)

#ifdef __cplusplus
extern "C"
#endif
JNIEXPORT jbyteArray JNICALL
Java_com_kryo_libprotobuf_NativeLib_process(JNIEnv *env, jobject thiz, jbyteArray inputArray) {

    // 获取输入字节数组长度
    jsize inputLength = env->GetArrayLength(inputArray);

    // 将输入字节数组复制到本地缓冲区
    jbyte *inputBuffer = env->GetByteArrayElements(inputArray, nullptr);

    Person person;
    person.ParseFromArray(inputBuffer, inputLength);
    person.set_age(person.age() + 2);

    LOGI("Process Java Person name %s", person.name().c_str());

    // 创建输出字节数组
    jbyteArray outputArray = env->NewByteArray(inputLength);

    // 获取输出字节数组指针
    jbyte *outputBuffer = env->GetByteArrayElements(outputArray, nullptr);

    person.SerializeToArray(outputBuffer, (int) person.ByteSizeLong());

    // 释放本地缓冲区
    env->ReleaseByteArrayElements(inputArray, inputBuffer, 0);
    env->ReleaseByteArrayElements(outputArray, outputBuffer, 0);

    // 返回输出字节数组
    return outputArray;
}

java端调用

java 复制代码
byte[] javaByteArray = PersonOuterClass.Person.newBuilder()
        .setAge(16)
        .setName("Kryo")
        .build()
        .toByteArray();

Log.d(TAG, "Person Java HEX: 0x" + ByteUtils.bytesToHex(javaByteArray));

NativeLib lib = new NativeLib();

byte[] cppByteArray = lib.process(javaByteArray);

try {
    PersonOuterClass.Person person = PersonOuterClass.Person.parseFrom(cppByteArray);
    Log.d(TAG, "Process Native Person age " + person.getAge());
} catch (InvalidProtocolBufferException e) {
    throw new RuntimeException(e);
}
Log.d(TAG, "Person Native HEX: 0x" + ByteUtils.bytesToHex(cppByteArray));
yaml 复制代码
2024-04-06 01:59:06.132 25679-25679 JNI_Activity      com.kryo.demo   D  Person Java HEX: 0x0a044b72796f1010
2024-04-06 01:59:06.146 25679-25679 Protobuf3-Native  com.kryo.demo   I  Process Java Person name Kryo
2024-04-06 01:59:06.147 25679-25679 JNI_Activity      com.kryo.demo   D  Process Native Person age 18
2024-04-06 01:59:06.147 25679-25679 JNI_Activity      com.kryo.demo   D  Person Native HEX: 0x0a044b72796f1012

总结

  • 本文记录了如何使用ndk编译protobuf库,移植其他开源C++库时也可以参考此文。
  • protobuf本质上也是一种TLV编码类型,对于不太复杂的数据交互,自定义一种TLV协议即可,Java用Bytebuffer类封装一下也很方便。
  • protobuf的java编译Android Studio可以对其完好支持,C++端等其他端代码也可以使用gradle执行脚本自动化编译,但如果和其他方合作,要求对方适配可能导致对方代码库污染,如果对性能要求没那么苛刻,json够用且方便。
相关推荐
安卓理事人5 小时前
安卓LinkedBlockingQueue消息队列
android
万能的小裴同学6 小时前
Android M3U8视频播放器
android·音视频
q***57747 小时前
MySql的慢查询(慢日志)
android·mysql·adb
JavaNoober7 小时前
Android 前台服务 "Bad Notification" 崩溃机制分析文档
android
城东米粉儿8 小时前
关于ObjectAnimator
android
zhangphil9 小时前
Android渲染线程Render Thread的RenderNode与DisplayList,引用Bitmap及Open GL纹理上传GPU
android
火柴就是我9 小时前
从头写一个自己的app
android·前端·flutter
lichong95111 小时前
XLog debug 开启打印日志,release 关闭打印日志
android·java·前端
用户693717500138411 小时前
14.Kotlin 类:类的形态(一):抽象类 (Abstract Class)
android·后端·kotlin
火柴就是我11 小时前
NekoBoxForAndroid 编译libcore.aar
android