【RK3576 安卓 JNI/NDK 系列 05】NDK 构建系统:CMakeLists.txt 从入门到精通

目录

前言

[一、先搞懂核心概念:CMake 到底是个啥?和 NDK 啥关系?](#一、先搞懂核心概念:CMake 到底是个啥?和 NDK 啥关系?)

[1. 什么是 CMake?](#1. 什么是 CMake?)

[2. CMake 和 NDK 的关系](#2. CMake 和 NDK 的关系)

[3. 为什么咱们 RK3576 开发必须学 CMake?](#3. 为什么咱们 RK3576 开发必须学 CMake?)

[二、CMakeLists.txt 基础结构:逐行拆解,新手一遍就懂](#二、CMakeLists.txt 基础结构:逐行拆解,新手一遍就懂)

[1. cmake_minimum_required:指定 CMake 最低版本](#1. cmake_minimum_required:指定 CMake 最低版本)

[2. project:声明项目名称](#2. project:声明项目名称)

[3. add_library:添加要编译的库文件(核心命令)](#3. add_library:添加要编译的库文件(核心命令))

逐参数拆解,新手必记:

[4. find_library:查找安卓系统库](#4. find_library:查找安卓系统库)

[5. target_link_libraries:链接库文件(核心命令)](#5. target_link_libraries:链接库文件(核心命令))

[三、进阶必学:RK3576 开发常用 CMake 配置](#三、进阶必学:RK3576 开发常用 CMake 配置)

[1. 指定 C++ 标准](#1. 指定 C++ 标准)

[2. 添加头文件搜索路径](#2. 添加头文件搜索路径)

[3. 批量添加源文件](#3. 批量添加源文件)

[4. 针对 RK3576 架构的编译优化](#4. 针对 RK3576 架构的编译优化)

[5. 宏定义开关](#5. 宏定义开关)

[6. 打印调试信息](#6. 打印调试信息)

[四、核心实战:链接 RK3576 官方原生库(以 librga 为例)](#四、核心实战:链接 RK3576 官方原生库(以 librga 为例))

[步骤 1:从 RK3576 官方 SDK 里提取库文件和头文件](#步骤 1:从 RK3576 官方 SDK 里提取库文件和头文件)

[步骤 2:把库文件和头文件复制到项目里](#步骤 2:把库文件和头文件复制到项目里)

[步骤 3:修改 CMakeLists.txt,导入并链接库](#步骤 3:修改 CMakeLists.txt,导入并链接库)

核心命令拆解:导入预编译库

[步骤 4:修改 build.gradle,确保只编译 arm64-v8a 架构](#步骤 4:修改 build.gradle,确保只编译 arm64-v8a 架构)

[步骤 5:验证链接是否成功,写测试代码](#步骤 5:验证链接是否成功,写测试代码)

[五、新手踩坑急救站:90% 的 CMake 编译报错都在这里解决](#五、新手踩坑急救站:90% 的 CMake 编译报错都在这里解决)

[报错 1:undefined reference to xxx(90% 的人都遇到过)](#报错 1:undefined reference to xxx(90% 的人都遇到过))

[报错 2:fatal error: xxx.h: No such file or directory](#报错 2:fatal error: xxx.h: No such file or directory)

[报错 3:library xxx: LOCATION path refers to non-existent path](#报错 3:library xxx: LOCATION path refers to non-existent path)

[报错 4:Cannot find source file: xxx.cpp](#报错 4:Cannot find source file: xxx.cpp)

[报错 5:INSTALL_FAILED_NO_MATCHING_ABIS](#报错 5:INSTALL_FAILED_NO_MATCHING_ABIS)

[报错 6:java.lang.UnsatisfiedLinkError: dlopen failed: library "libxxx.so" not found](#报错 6:java.lang.UnsatisfiedLinkError: dlopen failed: library "libxxx.so" not found)

[本章总结 + 下章预告](#本章总结 + 下章预告)

【本章总结】

【下章预告】


前言

哈喽各位兄弟们,我是你们的黒漂技术佬!

前面四章咱们把 JNI 的核心语法啃完了,后台一堆兄弟报喜:"佬哥,我终于能在 C++ 里处理传感器数据、调用 Java 回调了!" 但同时,90% 的兄弟都卡在了同一个坎上:"佬哥,我代码写得一点问题没有,一编译就报undefined reference to xxx,百度了三天都没解决,人都麻了!""我想把 RK3576 官方的 librga.so 导进项目里,结果要么找不到库,要么安装到开发板上就闪退,到底咋弄啊?""我项目里有十几个 C++ 源文件,一个个加到 CMake 里太麻烦了,有没有省事的办法?"

懂了懂了!这些问题,根本不是你代码写错了,99% 都是CMakeLists.txt 没写对。前面咱们的 demo 都是单文件、无第三方库,CMake 用自动生成的就行,但真到了 RK3576 的实际开发,要链接官方原生库、多文件编译、硬件相关的宏定义,不会写 CMake,你连编译都过不了,更别说跑程序了。

CMakeLists.txt 就是 NDK 开发的「项目总指挥」,它告诉编译器:哪些 C++ 文件要编译、头文件去哪里找、要链接哪些系统库 / 第三方库、生成什么架构的 so 库。不会写 CMake,就像开车不会握方向盘,根本走不动道。

今天这一章,佬哥我还是老规矩:大白话讲原理 + 保姆级逐行拆解 + RK3576 实战场景 + 踩坑急救指南,从最基础的命令,到链接 RK3576 官方库的完整实战,全给你讲透。所有配置全是咱们前面锁定的 NDK r21e + CMake 3.10.2 版本,100% 兼容 RK3576,新手直接抄作业,再也不会被编译报错搞崩心态!


一、先搞懂核心概念:CMake 到底是个啥?和 NDK 啥关系?

很多新手上来就对着 CMake 命令死记硬背,根本没搞懂它是干嘛的,越学越乱。先给大家用大白话讲透,建立整体认知。

1. 什么是 CMake?

CMake 是一个跨平台的构建工具,说白了就是「项目包工头」。你写好 CMakeLists.txt 配置文件,告诉它你的项目有哪些源文件、要依赖哪些库、要生成什么目标文件,它就会自动生成对应平台的编译脚本(比如 Windows 的 VS 工程、Linux 的 Makefile、安卓的 ninja 编译脚本),然后调用编译器完成编译。

在安卓 NDK 开发里,CMake 就是官方推荐的构建工具,替代了老掉牙的 ndk-build,现在 Android Studio 新建 JNI 项目,默认用的就是 CMake。

2. CMake 和 NDK 的关系

咱们上一章讲过,NDK 是安卓给咱们提供的 C/C++ 开发工具包,里面包含了交叉编译器、系统库、头文件等等。而 CMake,就是用来指挥 NDK 里的编译器,把你的 C/C++ 代码编译成安卓能识别的 so 库的工具

用咱们之前的翻译官类比延伸一下:

  • JNI 是 Java 和 C/C++ 的翻译官;
  • NDK 是翻译官的全套办公设备(词典、编译器、调试器);
  • CMake 就是翻译官的项目经理,安排工作流程、告诉翻译官要翻译哪些文件、要参考哪些资料、最终输出什么格式的文档。

3. 为什么咱们 RK3576 开发必须学 CMake?

  1. RK3576 官方原生库全靠 CMake 链接:瑞芯微官方给的 librga、librknn_api、mpp 等库,全是预编译的 so 文件,必须通过 CMake 配置才能正确链接到你的项目里,不会 CMake 根本用不了这些核心能力。
  2. 多文件项目管理必备:实际开发中,你的 C++ 代码会拆成多个文件(比如 GPIO 控制、I2C 读写、RGA 处理各一个文件),必须靠 CMake 来管理这些源文件,一个个手动编译根本不现实。
  3. 编译配置灵活可控:可以针对 RK3576 的 arm64-v8a 架构做专属优化、设置宏定义、区分 Debug/Release 版本,这些全靠 CMake 配置。
  4. 行业通用标准:现在安卓 JNI 开发、嵌入式 Linux 开发,全用 CMake,学会了一通百通。

二、CMakeLists.txt 基础结构:逐行拆解,新手一遍就懂

Android Studio 新建 Native C++ 项目时,会自动生成一个最简版的 CMakeLists.txt,咱们就以这个为基础,逐行拆解每一个命令的作用、参数、用法,连标点符号都给你讲明白,新手再也不会对着一堆命令一脸懵。

先看自动生成的完整最简模板:

cmake

复制代码
# 1. 指定CMake的最低版本要求
cmake_minimum_required(VERSION 3.10.2)

# 2. 声明项目名称
project("rk3576_jni_demo")

# 3. 添加库文件,告诉CMake要编译生成什么so库
add_library(
        native-lib
        SHARED
        native-lib.cpp)

# 4. 查找安卓系统提供的库
find_library(
        log-lib
        log)

# 5. 链接库文件,把找到的系统库链接到咱们自己的so库
target_link_libraries(
        native-lib
        ${log-lib})

接下来咱们逐行拆解,每一个命令都讲透,带新手避坑。

1. cmake_minimum_required:指定 CMake 最低版本

cmake

复制代码
cmake_minimum_required(VERSION 3.10.2)
  • 作用:告诉编译器,这个 CMakeLists.txt 需要的最低 CMake 版本,如果你当前的 CMake 版本低于这个值,就会直接报错停止编译。
  • 新手必锁版本:咱们整个系列都锁死 3.10.2,和 NDK r21e 完美兼容,也是 RK3576 官方 SDK 用的版本,别瞎换最新版,不然会出现各种兼容问题。
  • 避坑点 :这个命令必须放在 CMakeLists.txt 的第一行,前面不能有任何其他有效命令,不然会报警告。

2. project:声明项目名称

cmake

复制代码
project("rk3576_jni_demo")
  • 作用:给你的项目起个名字,这个名字只在 CMake 内部有用,不会影响最终生成的 so 库名,随便起就行,一般和你的项目名对应。
  • 新手注意:这个命令不是必须的,但建议加上,规范项目结构,后面多模块编译的时候会用到。

3. add_library:添加要编译的库文件(核心命令)

这是整个 CMakeLists.txt 最核心的命令,没有之一,咱们的 so 库就是靠这个命令生成的。

cmake

复制代码
add_library(
        # 第一个参数:生成的so库名称,划重点!
        native-lib
        # 第二个参数:库的类型,SHARED/STATIC
        SHARED
        # 第三个及以后的参数:所有要编译的C/C++源文件路径,相对路径
        native-lib.cpp)
逐参数拆解,新手必记:
  1. 第一个参数:库名称
    • 你写的名字是native-lib,最终编译生成的 so 库名就是libnative-lib.so,安卓会自动给你加lib前缀和.so后缀。
    • 巨坑预警 :Java 层加载 so 库的时候,必须写这里的名字,不能加lib.so!比如System.loadLibrary("native-lib"),写错一个字符都加载不到,新手 100% 会踩这个坑。
  2. 第二个参数:库的类型
    • SHARED:动态库,最终生成.so文件,安卓 JNI 开发只用这个类型。动态库是运行时加载,不会把代码打包进 APK 的 dex 里,体积小,更新方便。
    • STATIC:静态库,最终生成.a文件,编译时会把所有代码打包进你的 so 库里,适合把第三方的静态库整合到自己的项目里,一般不用来生成咱们自己的主库。
  3. 第三个及以后的参数:源文件路径
    • 所有要编译的 C/C++ 源文件,都要写在这里,路径是相对于当前 CMakeLists.txt 的相对路径。
    • 比如你的 cpp 文件在src/main/cpp/gpio/目录下,就要写成gpio/gpio_control.cpp
    • 避坑点 :头文件(.h/.hpp)不用写在这里!只需要写源文件(.c/.cpp),头文件靠后面的include_directories命令指定路径就行。

4. find_library:查找安卓系统库

cmake

复制代码
find_library(
        # 第一个参数:保存找到的库路径的变量名
        log-lib
        # 第二个参数:要查找的系统库的名称
        log)
  • 作用:安卓系统提供了很多预编译的系统库(比如日志库 log、图形库 GLESv3、媒体库 mediandk 等),这些库在 NDK 里已经有了,咱们不用自己编译,只需要用这个命令找到它,然后链接到咱们的库里就行。
  • 参数拆解
    • 第一个参数:自定义的变量名,找到库之后,库的路径就会存在这个变量里,后面链接的时候用${变量名}就能引用。
    • 第二个参数:系统库的名字,比如日志库的名字就是log,NDK 里的系统库名都是去掉lib前缀和.so后缀的。
  • 咱们最常用的系统库
    • log:安卓日志库,咱们之前用的__android_log_print就靠这个库,必须链接。
    • android:安卓 NDK 原生 API 库,比如操作 AssetManager、ANativeWindow(图像渲染)都靠它。
    • GLESv3:OpenGL ES 3.0 图形库,做图像渲染、摄像头预览会用到。
    • z:zip 压缩库,处理压缩包会用到。

这是第二核心的命令,90% 的undefined reference报错,都是因为这个命令没写对。

cmake

复制代码
target_link_libraries(
        # 第一个参数:要链接到哪个库,就是咱们add_library生成的库名
        native-lib
        # 第二个及以后的参数:要链接的所有库,变量名、库路径都可以
        ${log-lib})
  • 作用:把你找到的系统库、第三方库,链接到咱们自己生成的 so 库里,告诉编译器:这个 so 库要用到这些库的函数,编译的时候要把它们关联起来。
  • 新手必记规则
    1. 第一个参数必须是你已经用add_library声明过的库名,不能瞎写。
    2. 所有你用到的外部库,都必须在这里链接,哪怕是系统库,不链接就会报undefined reference
    3. 链接顺序有讲究 :被依赖的库要放在后面,比如 A 库依赖 B 库,就要写成target_link_libraries(A B),写反了也会报未定义引用。
  • 避坑点 :很多新手用了find_library找到了库,但忘了在target_link_libraries里链接,结果编译报错,找半天找不到问题,一定要记住:找到库≠链接库,必须在这里写进去!

三、进阶必学:RK3576 开发常用 CMake 配置

上面的基础模板只能跑单文件 demo,真到了 RK3576 的实际开发,必须掌握这些进阶配置,不然项目根本没法维护。所有配置全是佬哥我在 RK3576 开发里天天用的,新手直接抄就行。

1. 指定 C++ 标准

cmake

复制代码
# 指定C++标准为C++11,和RK3576官方库的编译标准保持一致
set(CMAKE_CXX_STANDARD 11)
# 强制要求编译器必须支持这个标准,不支持就报错
set(CMAKE_CXX_STANDARD_REQUIRED ON)
  • 作用:指定编译用的 C++ 标准,咱们整个系列锁死 C++11,因为 RK3576 官方的所有原生库都是用 C++11 编译的,用更高的标准会出现兼容问题。
  • 避坑点 :这里的配置要和 build.gradle 里的cppFlags "-std=c++11"保持一致,不然会报警告。

2. 添加头文件搜索路径

cmake

复制代码
# 添加头文件搜索路径,相对路径/绝对路径都可以
include_directories(
        # 当前目录下的include文件夹
        include
        # GPIO模块的头文件路径
        gpio
        # RK3576官方librga的头文件路径
        3rdparty/rga/include
)
  • 作用:告诉编译器,你的 #include 头文件要去哪些目录里找。
  • 解决的痛点 :很多新手写#include "rga.h"的时候,编译器报file not found,99% 都是因为没把 rga.h 所在的目录加到这里。
  • 新手技巧 :如果你的头文件分散在多个文件夹里,把所有文件夹的路径都加到这里,就可以直接用#include "xxx.h",不用写长长的相对路径了。

3. 批量添加源文件

如果你的项目里有十几个 C++ 源文件,一个个写到 add_library 里太麻烦了,用这个命令可以批量添加:

cmake

复制代码
# 批量搜索cpp目录下所有的.cpp/.c文件,保存到SOURCE_FILES变量里
file(GLOB_RECURSE SOURCE_FILES
        "*.cpp"
        "*.c"
        "gpio/*.cpp"
        "i2c/*.cpp"
        "rga/*.cpp"
)

# 把批量搜索到的源文件,直接加到add_library里
add_library(
        native-lib
        SHARED
        ${SOURCE_FILES})
  • 命令说明
    • GLOB_RECURSE:递归搜索,包括子文件夹里的文件。
    • 后面的参数是搜索规则,*.cpp就是匹配所有后缀为.cpp 的文件。
    • 搜索到的所有文件路径,都会保存到SOURCE_FILES变量里,后面用${SOURCE_FILES}引用就行。
  • 避坑点:如果你新增 / 删除了源文件,必须点击 AS 右上角的「Sync Project with Gradle Files」重新同步 CMake,不然编译器不会识别到文件变化。

4. 针对 RK3576 架构的编译优化

RK3576 是 arm64-v8a 架构,咱们可以专门针对这个架构做编译优化,提升代码运行效率:

cmake

复制代码
# 针对arm64-v8a架构的优化配置
if(${CMAKE_ANDROID_ARCH_ABI} STREQUAL "arm64-v8a")
    # 开启O3级别优化,最高优化等级
    set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -O3")
    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O3")
    # 针对Cortex-A55架构优化,RK3576的CPU就是Cortex-A55
    set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -mcpu=cortex-a55")
    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mcpu=cortex-a55")
    # 开启NEON指令集优化,ARM架构的SIMD指令,提升图像处理、算法运算速度
    set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -mfpu=neon")
    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mfpu=neon")
endif()
  • 作用:针对 RK3576 的 Cortex-A55 CPU 做专属优化,开启最高等级的代码优化,开启 NEON 指令集,能让你的图像处理、算法运算速度提升 30% 以上,做嵌入式开发必须开。
  • 避坑点:这个优化只针对 arm64-v8a 架构,所以要用 if 判断,只在对应架构下生效,别给 x86 模拟器也加,不然会编译报错。

5. 宏定义开关

咱们做开发的时候,经常需要用宏定义区分 Debug/Release 版本,或者开启 / 关闭某些功能,比如 Debug 版本打印详细日志,Release 版本关闭日志:

cmake

复制代码
# Debug版本开启日志宏定义
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
    add_definitions(-DENABLE_DEBUG_LOG=1)
else()
    add_definitions(-DENABLE_DEBUG_LOG=0)
endif()

# 开启RK3576硬件加速宏定义
add_definitions(-DRK3576_HARDWARE_ACCEL=1)
  • 作用 :在编译的时候给 C/C++ 代码定义宏,相当于在所有源文件开头加了#define ENABLE_DEBUG_LOG 1
  • C++ 代码里的用法

cpp

运行

复制代码
#if ENABLE_DEBUG_LOG
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
#else
#define LOGD(...) //  Release版本不打印日志
#endif

6. 打印调试信息

写 CMake 的时候,经常需要调试变量的值,比如看看源文件有没有搜索到、库路径对不对,用这个命令可以打印信息:

cmake

复制代码
# 打印变量信息,和C++的printf一样
message("当前CMake源码路径:${CMAKE_SOURCE_DIR}")
message("搜索到的源文件:${SOURCE_FILES}")
message("当前编译架构:${CMAKE_ANDROID_ARCH_ABI}")
  • 打印的信息会在 AS 的「Build」窗口里看到,调试 CMake 的时候巨好用。

四、核心实战:链接 RK3576 官方原生库(以 librga 为例)

这是本章的重中之重!咱们做 RK3576 开发,90% 的场景都要链接瑞芯微官方的原生库,比如 2D 硬件加速的 librga、NPU 推理的 librknn_api、视频编解码的 libmpp。很多新手就是卡在这里,导不进库、链接失败、运行闪退,今天佬哥我给你一步不跳的保姆级教程,新手跟着走 100% 成功

咱们以最常用的librga.so为例,这个库是瑞芯微官方的 2D 硬件加速库,用来做图像缩放、旋转、格式转换,比 CPU 快 10 倍以上,后面的章节会专门讲它的用法,今天咱们先把它成功链接到项目里。

步骤 1:从 RK3576 官方 SDK 里提取库文件和头文件

首先,你要从 RK3576 的官方安卓 SDK 里,找到对应的库文件和头文件,路径如下:

  1. 头文件SDK根目录/hardware/rockchip/librga/include/ 目录下的所有.h 文件,核心是im2d_version.hRgaApi.hRockchipRga.h
  2. 库文件SDK根目录/out/target/product/rk3576_sdk/system/lib64/librga.so,注意!必须是lib64目录下的 arm64-v8a 版本,32 位的用不了。

如果没有完整 SDK,也可以从你的 RK3576 开发板里提取:用 adb 连接开发板,执行adb pull /system/lib64/librga.so ./,就能把库文件拉到电脑上。

步骤 2:把库文件和头文件复制到项目里

在你的 Android 项目里,按以下目录结构存放文件,新手直接照着建文件夹,别瞎改路径:

plaintext

复制代码
app/src/main/cpp/
├── 3rdparty/                # 第三方库统一存放目录
│   └── rga/                 # librga库目录
│       ├── include/         # 头文件目录,把所有.h文件放这里
│       │   ├── RgaApi.h
│       │   ├── RockchipRga.h
│       │   └── im2d_version.h
│       └── libs/            # 库文件目录
│           └── arm64-v8a/  # 只留arm64-v8a架构,对应RK3576
│               └── librga.so # 官方库文件放这里
├── native-lib.cpp           # 你的源码
└── CMakeLists.txt           # CMake配置文件

步骤 3:修改 CMakeLists.txt,导入并链接库

这是最核心的一步,咱们要把预编译的 librga.so 导入到 CMake 里,然后链接到咱们的 native-lib 库。完整配置如下,新手直接复制粘贴,只需要改路径就行:

cmake

复制代码
cmake_minimum_required(VERSION 3.10.2)
project("rk3576_jni_demo")

# -------------------------- 1. 基础配置 --------------------------
# 指定C++标准
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

# 添加头文件搜索路径,必须把librga的include目录加进来
include_directories(
        3rdparty/rga/include
)

# -------------------------- 2. 批量添加源文件 --------------------------
file(GLOB_RECURSE SOURCE_FILES
        "*.cpp"
        "*.c"
)
add_library(
        native-lib
        SHARED
        ${SOURCE_FILES})

# -------------------------- 3. 导入预编译的librga库 --------------------------
# 声明一个导入的库,名字叫rga,类型是SHARED,IMPORTED表示是预编译好的,不用咱们编译
add_library(
        rga
        SHARED
        IMPORTED)

# 设置导入库的路径,${CMAKE_SOURCE_DIR}就是当前CMakeLists.txt所在的目录
set_target_properties(
        rga
        PROPERTIES IMPORTED_LOCATION
        ${CMAKE_SOURCE_DIR}/3rdparty/rga/libs/${CMAKE_ANDROID_ARCH_ABI}/librga.so)

# -------------------------- 4. 查找系统库 --------------------------
find_library(
        log-lib
        log)
find_library(
        android-lib
        android)

# -------------------------- 5. 链接所有库,重点!必须把rga加进来 --------------------------
target_link_libraries(
        native-lib
        rga          # 链接librga库
        ${log-lib}
        ${android-lib})
核心命令拆解:导入预编译库

cmake

复制代码
add_library(rga SHARED IMPORTED)
  • 这里的IMPORTED关键字是核心,它告诉 CMake:这个库是已经编译好的,不用你再编译了,只需要导入进来用就行。
  • 库名rga对应咱们的库文件librga.so,和之前的规则一致,去掉lib前缀和.so后缀。

cmake

复制代码
set_target_properties(rga PROPERTIES IMPORTED_LOCATION 库路径)
  • 这个命令用来给导入的库设置属性,核心是IMPORTED_LOCATION,也就是库文件的完整路径。
  • ${CMAKE_ANDROID_ARCH_ABI}是 CMake 内置变量,会自动获取当前编译的架构(arm64-v8a),不用硬编码。

步骤 4:修改 build.gradle,确保只编译 arm64-v8a 架构

打开app/build.gradle,确保 ndk 的 abiFilters 只留 arm64-v8a,因为咱们的 librga.so 只有 arm64-v8a 版本,编译其他架构会报错:

gradle

复制代码
android {
    // 省略其他配置...
    defaultConfig {
        // 省略其他配置...
        ndk {
            // 只编译arm64-v8a,完全适配RK3576
            abiFilters 'arm64-v8a'
        }
    }
    ndkVersion '21.4.7075529'
}

步骤 5:验证链接是否成功,写测试代码

咱们在 native-lib.cpp 里写一段简单的测试代码,调用 librga 的 API,只要编译不报错,就说明链接成功了!

cpp

运行

复制代码
#include <jni.h>
#include <string>
#include <android/log.h>
// 导入librga的头文件,能正常导入就说明头文件路径配置对了
#include "RgaApi.h"

#define LOG_TAG "Heipiao_RK3576_RGA"
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)

extern "C" JNIEXPORT jstring JNICALL
Java_com_heipiao_rk3576_jni_MainActivity_getRgaVersion(JNIEnv *env, jobject thiz) {
    // 调用librga的API,获取RGA版本号
    int rga_version = c_RkRgaGetVersion();
    LOGD("成功链接librga库,RGA版本号:%d", rga_version);

    char result[128];
    sprintf(result, "成功链接RK3576 librga库,版本号:%d", rga_version);
    return env->NewStringUTF(result);
}

Java 层调用这个方法,运行到 RK3576 开发板上,如果能正常输出 RGA 版本号,就说明咱们已经成功链接了官方库!恭喜你,以后所有 RK3576 的官方原生库,你都能用这个方法导入链接了。


五、新手踩坑急救站:90% 的 CMake 编译报错都在这里解决

佬哥我把 NDK 开发中,CMake 相关的 99% 的报错,全整理出来了,遇到问题直接来这里找解决方案,不用到处百度。

报错 1:undefined reference to xxx(90% 的人都遇到过)

99% 的原因

  1. 你调用的函数所在的源文件,没有加到 add_library 里,编译器没编译这个文件;
  2. 用到了第三方库的函数,但没有在 target_link_libraries 里链接对应的库;
  3. 链接顺序写反了,依赖的库放在了前面;
  4. 库的架构不对,比如你链接了 32 位的库,却编译 64 位的程序。

解决方案

  1. 检查所有源文件都加到了 add_library 里,或者用 file (GLOB_RECURSE) 批量添加了;
  2. 检查用到的第三方库,都在 target_link_libraries 里链接了;
  3. 调整链接顺序,被依赖的库放在后面;
  4. 确保库的架构是 arm64-v8a,和编译的架构一致。

报错 2:fatal error: xxx.h: No such file or directory

原因:头文件所在的目录,没有加到 include_directories 里,编译器找不到头文件。

解决方案:把头文件所在的文件夹路径,加到 include_directories 里,注意是文件夹路径,不是文件路径。

报错 3:library xxx: LOCATION path refers to non-existent path

原因:导入预编译库的时候,IMPORTED_LOCATION 的路径写错了,库文件不存在。

解决方案:检查库文件的路径,用 message 命令打印路径,看看和实际路径是否一致,确保路径里没有中文、空格。

报错 4:Cannot find source file: xxx.cpp

原因:add_library 里写的源文件路径写错了,文件不存在。

解决方案:检查源文件的相对路径,相对于 CMakeLists.txt 的路径是否正确,用 file (GLOB_RECURSE) 批量添加可以避免这个问题。

报错 5:INSTALL_FAILED_NO_MATCHING_ABIS

原因:编译的 so 库架构,和 RK3576 的 arm64-v8a 架构不匹配,比如你编译了 armeabi-v7a 的库,却装到 64 位的开发板上。

解决方案:build.gradle 里的 abiFilters 只留 'arm64-v8a',确保所有第三方库都有 arm64-v8a 的版本。

报错 6:java.lang.UnsatisfiedLinkError: dlopen failed: library "libxxx.so" not found

原因

  1. so 库没有打包进 APK 里;
  2. Java 层 System.loadLibrary 的名字写错了;
  3. 库的命名不对,没有以 lib 开头。

解决方案

  1. 检查 APK 里的 lib/arm64-v8a 目录下,有没有对应的 so 文件;
  2. 检查 System.loadLibrary 的名字,和 add_library 里的名字完全一致,不加 lib 和.so
  3. 确保库文件命名是 libxxx.so 的格式。

本章总结 + 下章预告

【本章总结】

今天这一章,咱们彻底搞懂了 NDK 的构建系统 CMake,核心就 4 件事:

  1. 搞懂了 CMake 的核心作用,和 NDK 的关系,建立了构建系统的整体认知;
  2. 逐行拆解了 CMakeLists.txt 的基础命令,掌握了 so 库生成、系统库查找、链接的核心流程;
  3. 学会了 RK3576 开发必备的进阶 CMake 配置,包括批量添加源文件、架构优化、宏定义、头文件路径配置;
  4. 掌握了预编译库的导入方法,成功链接了 RK3576 官方的 librga 库,以后所有官方原生库都能轻松导入。

【下章预告】

下一章,咱们正式进入 RK3576 硬件开发的核心环节:安卓下的驱动访问基础:设备节点与 ioctl。我会给你讲透 Linux 驱动的核心原理、设备节点的操作方法、ioctl 的使用规则,教你怎么通过 JNI 操作底层驱动,为后面的 GPIO 点灯、I2C 读传感器打下最核心的基础,真正实现安卓 APP 操控硬件!


我是黒漂技术佬,专注给小白搞懂 RK3576 安卓底层、JNI/NDK、嵌入式开发的保姆级教程,跟着我,保证你不迷路、不踩坑!

兄弟们,跟着本章成功链接 librga 库的,麻烦评论区扣个「CMake 搞定 + RK3576 官方库链接成功」!有啥问题、踩了啥坑,评论区直接留言,佬哥我挨个回复!点赞收藏关注不迷路,咱们下一章见!

相关推荐
阿拉斯攀登6 小时前
【RK3576 安卓 JNI/NDK 系列 10】综合实战:RK3576 智能环境监测系统全实现 + 系列总结
rk3568·瑞芯微·rk安卓驱动·ndk构建系统·嵌入式智能终端
茉莉玫瑰花茶8 小时前
CMake 工程指南 - 工程场景(4)
服务器·c++·cmake
茉莉玫瑰花茶8 小时前
CMake 工程指南 - 工程场景(5)
开发语言·c++·cmake
阿拉斯攀登9 小时前
【RK3576 安卓 JNI/NDK 系列 06】安卓驱动访问基础:设备节点与 ioctl 全解析
ndk·嵌入式安卓·安卓jni·瑞芯微rk3576·底层驱动·linux设备节点
阿拉斯攀登11 小时前
【RK3576 安卓 JNI/NDK 系列 09】RK3576 实战(三):JNI 调用 librga 实现 2D 硬件加速图像处理
android·驱动开发·rk3568·瑞芯微·rk安卓驱动·rk3576 rga加速
阿拉斯攀登1 天前
第 19 篇 驱动性能优化与功耗优化实战
android·驱动开发·瑞芯微·嵌入式驱动·安卓驱动
阿拉斯攀登1 天前
第 20 篇 RK 平台 NPU / 硬件编解码驱动适配与安卓调用
android·驱动开发·瑞芯微·rk安卓驱动
阿拉斯攀登1 天前
第 12 篇 RK 平台安卓驱动实战 5:SPI 设备驱动开发,以 SPI 屏 / Flash 为例
android·驱动开发·rk3568·瑞芯微·嵌入式驱动·安卓驱动·spi 设备驱动
阿拉斯攀登1 天前
【RK3576 安卓 JNI/NDK 系列 01】概念扫盲 + RK3576 安卓开发架构全解析
ndk·嵌入式安卓·安卓jni·瑞芯微rk3576·底层驱动