目录
[一、先搞懂核心概念: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?
- RK3576 官方原生库全靠 CMake 链接:瑞芯微官方给的 librga、librknn_api、mpp 等库,全是预编译的 so 文件,必须通过 CMake 配置才能正确链接到你的项目里,不会 CMake 根本用不了这些核心能力。
- 多文件项目管理必备:实际开发中,你的 C++ 代码会拆成多个文件(比如 GPIO 控制、I2C 读写、RGA 处理各一个文件),必须靠 CMake 来管理这些源文件,一个个手动编译根本不现实。
- 编译配置灵活可控:可以针对 RK3576 的 arm64-v8a 架构做专属优化、设置宏定义、区分 Debug/Release 版本,这些全靠 CMake 配置。
- 行业通用标准:现在安卓 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)
逐参数拆解,新手必记:
- 第一个参数:库名称
- 你写的名字是
native-lib,最终编译生成的 so 库名就是libnative-lib.so,安卓会自动给你加lib前缀和.so后缀。 - 巨坑预警 :Java 层加载 so 库的时候,必须写这里的名字,不能加
lib和.so!比如System.loadLibrary("native-lib"),写错一个字符都加载不到,新手 100% 会踩这个坑。
- 你写的名字是
- 第二个参数:库的类型
SHARED:动态库,最终生成.so文件,安卓 JNI 开发只用这个类型。动态库是运行时加载,不会把代码打包进 APK 的 dex 里,体积小,更新方便。STATIC:静态库,最终生成.a文件,编译时会把所有代码打包进你的 so 库里,适合把第三方的静态库整合到自己的项目里,一般不用来生成咱们自己的主库。
- 第三个及以后的参数:源文件路径
- 所有要编译的 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 压缩库,处理压缩包会用到。
5. target_link_libraries:链接库文件(核心命令)
这是第二核心的命令,90% 的undefined reference报错,都是因为这个命令没写对。
cmake
target_link_libraries(
# 第一个参数:要链接到哪个库,就是咱们add_library生成的库名
native-lib
# 第二个及以后的参数:要链接的所有库,变量名、库路径都可以
${log-lib})
- 作用:把你找到的系统库、第三方库,链接到咱们自己生成的 so 库里,告诉编译器:这个 so 库要用到这些库的函数,编译的时候要把它们关联起来。
- 新手必记规则 :
- 第一个参数必须是你已经用
add_library声明过的库名,不能瞎写。 - 所有你用到的外部库,都必须在这里链接,哪怕是系统库,不链接就会报
undefined reference。 - 链接顺序有讲究 :被依赖的库要放在后面,比如 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 里,找到对应的库文件和头文件,路径如下:
- 头文件 :
SDK根目录/hardware/rockchip/librga/include/目录下的所有.h 文件,核心是im2d_version.h、RgaApi.h、RockchipRga.h。 - 库文件 :
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% 的原因:
- 你调用的函数所在的源文件,没有加到 add_library 里,编译器没编译这个文件;
- 用到了第三方库的函数,但没有在 target_link_libraries 里链接对应的库;
- 链接顺序写反了,依赖的库放在了前面;
- 库的架构不对,比如你链接了 32 位的库,却编译 64 位的程序。
解决方案:
- 检查所有源文件都加到了 add_library 里,或者用 file (GLOB_RECURSE) 批量添加了;
- 检查用到的第三方库,都在 target_link_libraries 里链接了;
- 调整链接顺序,被依赖的库放在后面;
- 确保库的架构是 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
原因:
- so 库没有打包进 APK 里;
- Java 层 System.loadLibrary 的名字写错了;
- 库的命名不对,没有以 lib 开头。
解决方案:
- 检查 APK 里的 lib/arm64-v8a 目录下,有没有对应的 so 文件;
- 检查 System.loadLibrary 的名字,和 add_library 里的名字完全一致,不加 lib 和.so;
- 确保库文件命名是 libxxx.so 的格式。
本章总结 + 下章预告
【本章总结】
今天这一章,咱们彻底搞懂了 NDK 的构建系统 CMake,核心就 4 件事:
- 搞懂了 CMake 的核心作用,和 NDK 的关系,建立了构建系统的整体认知;
- 逐行拆解了 CMakeLists.txt 的基础命令,掌握了 so 库生成、系统库查找、链接的核心流程;
- 学会了 RK3576 开发必备的进阶 CMake 配置,包括批量添加源文件、架构优化、宏定义、头文件路径配置;
- 掌握了预编译库的导入方法,成功链接了 RK3576 官方的 librga 库,以后所有官方原生库都能轻松导入。
【下章预告】
下一章,咱们正式进入 RK3576 硬件开发的核心环节:安卓下的驱动访问基础:设备节点与 ioctl。我会给你讲透 Linux 驱动的核心原理、设备节点的操作方法、ioctl 的使用规则,教你怎么通过 JNI 操作底层驱动,为后面的 GPIO 点灯、I2C 读传感器打下最核心的基础,真正实现安卓 APP 操控硬件!
我是黒漂技术佬,专注给小白搞懂 RK3576 安卓底层、JNI/NDK、嵌入式开发的保姆级教程,跟着我,保证你不迷路、不踩坑!
兄弟们,跟着本章成功链接 librga 库的,麻烦评论区扣个「CMake 搞定 + RK3576 官方库链接成功」!有啥问题、踩了啥坑,评论区直接留言,佬哥我挨个回复!点赞收藏关注不迷路,咱们下一章见!