Android NDK r28c在Linux下的完整配置实践

发布时间:2026/8/31 3:29:44
Android NDK r28c在Linux下的完整配置实践 简介Android NDK r28c 是面向Linux 64位平台x86架构的官方原生开发工具包专为Android平台C/C开发者设计用于构建高性能图形渲染、音视频处理、AI推理等对底层控制有强需求的应用模块。资源包含2000个文件主体为1977个头文件.h涵盖Camera元数据标签、Neural Networks API、OpenGL ES扩展、Unicode与V4L2控制等核心接口定义辅以11个Python脚本自动化构建与校验、10个Markdown文档版本说明与迁移指南、1个PDF官方快速入门及基础文本说明结构完整、即下即用。压缩包大小688.8MB目录组织符合NDK标准布局便于集成至Android Studio或命令行构建流程。目前已有230人学习下载开发者可直接获取完整、纯净、未经修改的r28c正式发行版规避网络下载源不稳定或版本混淆风险是搭建稳定JNI开发环境的关键基础设施。Android NDK r28c 在 Linux 下的完整配置实践如果你准备在 Linux 环境里做 Android 原生开发或者要把一些高性能 C/C 模块编译成 .so 库集成到 App 里那迟早要和 NDK 打交道。最近我在 Ubuntu 24.04 上把项目的 NDK 升级到了 android-ndk-r28c-linux整个过程中踩了不少坑也整理出了一套完整的配置和编译流程。这篇文章就把这个过程从头到尾拆开讲从下载解压、环境变量、AS 集成、CMake 构建到常见的编译报错排查都覆盖到希望能帮你少走一些弯路。这篇文章不是纯粹的命令堆砌每一处我都会解释这么配的理由和底层原理这样你遇到问题时不至于只会照着抄还能自己定位。无论你是刚开始接触 NDK 的新人还是从 Windows/macOS 迁移到 Linux 的老手都可以参考这套方案。1. NDK r28c 选型分析与环境设计1.1 r28c 这个版本解决了什么问题先说结论android-ndk-r28c-linux.zip是 Android NDK r28 系列的第三个补丁版本面向 Linux x86_64 平台。NDK 的版本迭代非常快r28 相比 r26、r27 有几个关键变化值得我们在选型时留意。首先是API level 的最低门槛。r28 系列对minSdkVersion的要求进一步收紧如果你的 App 还在支持 Android 5.0API 21以前的系统这个版本会直接编不过。从实际开发角度来看这个限制其实影响不大目前市场上还在跑的设备基本都高于这个门槛但如果你维护的是老项目升级前一定要确认。其次是工具链的更新。r28 把 LLVM/Clang 的版本又往前推了一大截默认的 C 标准库也从 libc 全面接管了。这意味着你编出来的 .so 在体积和性能上都有优化空间但对老代码里一些依赖 GCC 特定行为的写法就不太友好了。我在升级过程中遇到过几处因为-fno-exceptions位置不对导致的编译失败后面会详细讲。再一个就是CMake 版本的适配。NDK r28 对内部自带 cmake 的版本有要求如果你用的是 Android Studio 自带的 CMake 或系统级 CMake版本太老会出现各种莫名其妙的配置解析错误。我的建议是直接使用 NDK 自带的 CMake或者单独装一个 3.22.1 以上版本的 CMake后面我会把具体配置贴出来。1.2 为什么在 Linux 下做 NDK 开发比 Windows 顺手很多人会问Android Studio 在 Windows 上也能跑为什么非要折腾 Linux我的实际体验是Linux 下做 NDK 开发绕开了很多 Windows 特有的环境摩擦。首先是符号链接和路径。Windows 下 NDK 里的sysroot和工具链路径中有大量的符号链接用git或压缩工具解压时经常会把这些链接弄丢导致编译时找不到头文件。Linux 下原生支持符号链接解压即用这个问题几乎不存在。其次是编译脚本的可移植性。NDK 本质上就是一套交叉编译工具链在 Linux 的 shell 环境里各种export、make、cmake脚本可以直接跑不需要像 Windows 那样配bash.exe或者专门去配置 WSL 的路径映射。如果你还在 Windows 上做 NDK 开发我强烈建议在 WSL 里搭一套环境试试体验完全不一样。还有一个比较隐蔽的点是.so的链接行为。Linux 系统本身对 ELF 文件的处理比 Windows 更“宽容”遇到链接问题时报错信息也更符合直觉排查起来效率高很多。1.3 版本兼容性检查清单在动手下载之前先把兼容性确认好不然很可能装到一半才发现白忙活一场。这里给出一份我在实际项目中验证过的配置清单组件推荐版本备注操作系统Ubuntu 22.04 / 24.04x86_64其他 Linux 发行版类似但依赖包名可能略有差异JDKJDK 17低于 17 可能无法运行新版 Android StudioAndroid StudioLadybug 或更高版本AGP 版本建议 8.5 以上Android SDKPlatform 34 / 35需要包含最新 build-toolsCMake3.22.1建议用 NDK 自带的或单独安装新版ninja1.10系统自带或随 SDK 一起装NDKr28candroid-ndk-r28c-linux.zip本文的主角注意如果你的项目里还有其他依赖库比如 OpenCV、FFmpeg 的预编译包还需要确认这些库是用哪个 NDK 版本编出来的。NDK 的 ABI 兼容性虽然是向前兼容的但不同工具链版本编出的 .so 在链接时可能因为__android_log_print等符号的版本差异产生警告虽然一般不影响运行但洁癖患者很难受。2. 下载解压与环境变量配置详解2.1 下载渠道与 MD5 校验NDK 的下载渠道主要有两个一个是 Android Studio 的 SDK Manager 图形界面另一个是谷歌官方的命令行工具。由于我们这里拿到的是独立发布的android-ndk-r28c-linux.zip压缩包我直接推荐用命令行方式下载这样也方便在服务器或 CI 环境里复用。下载地址可以到 Android 开发者官网的 NDK 下载页面找到这里不直接贴长链接。下载完成后强烈建议先做一遍 SHA-256 校验避免传输过程损坏导致后续编译出现各种诡异问题。校验命令如下echo 期望的哈希值 android-ndk-r28c-linux.zip | sha256sum -c -如果校验失败重新下载别嫌麻烦。我见过太多人省了这一步结果编译时老是报undefined reference查了半天发现是 NDK 文件本身有问题。2.2 解压与目录规划拿到压缩包后不建议直接解压到 Android Studio 默认的 SDK 目录里而是放到一个你有完整读写权限的路径。我个人的习惯是把所有 SDK 相关的东西统一放在/opt/android-sdk/下NDK 单独一个子目录方便后续版本切换。export ANDROID_SDK_ROOT/opt/android-sdk sudo mkdir -p $ANDROID_SDK_ROOT/ndk sudo chown -R $USER:$USER $ANDROID_SDK_ROOT unzip android-ndk-r28c-linux.zip -d $ANDROID_SDK_ROOT/ndk/解压完成后目录结构大概是这样的/opt/android-sdk/ndk/android-ndk-r28c/ ├── build/ ├── meta/ ├── prebuilt/ ├── simples/ ├── sources/ ├── sysroot/ ├── toolchains/ ├── CHANGELOG.md ├── NOTICE ├── source.properties ├── ndk-build └── ndk-gdb这里要特别注意source.properties文件它是 AS 识别 NDK 版本的关键里面记录了Pkg.Revision 28.2.0之类的版本号。如果你自己手动改目录名或者移动位置只要这个文件存在且内容正确AS 就能正常识别。2.3 环境变量配置三种场景NDK 的使用场景大致分三种每种的环境变量配置方式都不一样场景一纯命令行交叉编译如果你只是想在终端里直接把 C/C 代码编译成 Android 可用的动态库不经过 Android Studio那只需要配置ANDROID_NDK_HOME和PATH。编辑~/.bashrc或~/.zshrcexport ANDROID_NDK_HOME/opt/android-sdk/ndk/android-ndk-r28c export PATH$ANDROID_NDK_HOME:$PATH然后source ~/.bashrc生效。配置好后直接在命令行敲ndk-build --version就能看到版本信息。场景二Android Studio 集成如果你用 Android Studio 构建项目其实不用在系统环境变量里设置 NDK 路径直接在 AS 的local.properties里声明即可。这个文件在项目根目录如果没有就新建一个内容如下sdk.dir/opt/android-sdk ndk.dir/opt/android-sdk/ndk/android-ndk-r28c或者更推荐的方式在gradle.properties里加上android.ndkPath/opt/android-sdk/ndk/android-ndk-r28c这种方式的好处是配置文件跟着项目走换机器或者多人协作时不容易出错。场景三CMake 独立构建有些大型 C 项目会直接调用 NDK 里的 CMake 工具链文件做构建这时候需要设置的是CMAKE_TOOLCHAIN_FILEexport CMAKE_TOOLCHAIN_FILE$ANDROID_NDK_HOME/build/cmake/android.toolchain.cmake cmake -DCMAKE_TOOLCHAIN_FILE$CMAKE_TOOLCHAIN_FILE -DANDROID_ABIarm64-v8a ..这个场景在 3.3 节会详细展开。提示如果你的 shell 是 fish 或别的非 bash 系环境变量语法可能不太一样自己去查对应 shell 的写法。我这里统一按 bash/zsh 来讲。3. 从零写一个 NDK 动态库并集成到 App3.1 工程结构设计讲完环境准备接下来进入实战环节。这里我创建一个最简单的 Native 库示例从一个纯 C 函数add(int a, int b)开始通过 JNI 暴露给 Java 层调用。整个过程你会看到 JNI 接口怎么定义、CMake 怎么配置、最终 .so 怎么被打进 APK。先规划一下工程结构MyNDKApp/ ├── app/ │ ├── src/main/ │ │ ├── cpp/ │ │ │ ├── native-lib.cpp │ │ │ └── CMakeLists.txt │ │ ├── java/ │ │ │ └── com/example/myndkapp/MainActivity.kt │ │ └── AndroidManifest.xml │ ├── build.gradle.kts │ └── CMakeLists.txt可选根级配置 ├── build.gradle.kts └── settings.gradle.kts这个结构是 AS 创建 Native C 工程时的默认结构核心部分就是cpp/目录下的 C 源码和 CMake 脚本。3.2 JNI 接口的底层机制在写 C 代码之前有必要先搞清楚 JNI 的调用链路。Java/Kotlin 层声明一个native方法例如external fun sum(a: Int, b: Int): Int编译时Java 编译器会生成一个符号引用运行时虚拟机会通过这个符号在当前加载的 .so 里寻找对应的 C/C 导出函数。这个函数的命名规则是Java_包名_类名_方法名其中包名里的点全部换成下划线。所以对应上面的 Kotlin 方法C 里应该是extern C JNIEXPORT jint JNICALL Java_com_example_myndkapp_MainActivity_sum( JNIEnv* env, jobject thiz, jint a, jint b) { return a b; }extern C是必须的告诉链接器不要对函数名做 C 名字修饰否则 Java 层找不到符号。JNIEXPORT和JNICALL是平台相关的宏在 Android 上展开为__attribute__((visibility(default)))和空作用是控制符号可见性和调用约定。JNIEnv* env是指向 JNI 函数表的指针几乎所有的 JNI 调用比如创建对象、访问数组、抛出异常都通过它完成。jobject thiz是调用该方法的 Java 对象引用。如果是静态 native 方法这里会是jclass。注意JNI 函数的前两个参数是固定的一定要写对位置我见过有人把 JNIEnv 和 jobject 顺序写反编译能过运行时必崩而且崩之前没有任何警告。3.3 CMake 配置的核心指令解析app/src/main/cpp/CMakeLists.txt是 NDK 构建的核心文件它负责告诉 CMake 如何把源码编成目标库。一个最小可用的配置如下cmake_minimum_required(VERSION 3.22.1) project(myndkapp) add_library( native-lib SHARED native-lib.cpp ) find_library(log-lib log) target_link_libraries( native-lib ${log-lib} )这里几个关键点add_library的第一个参数是库的名字最终生成的产物是libnative-lib.so。注意 CMake 会自动加上lib前缀和.so后缀你在 Java 层加载时要用System.loadLibrary(native-lib)不带前缀和后缀。find_library(log-lib log)会找到 Android 系统的liblog.so用于在 C 层打日志。这一步是对的因为我们经常需要__android_log_print输出调试信息。target_link_libraries把 log 库链接进我们的动态库中。在实际项目中你还需要设置 ABI 和 C 标准。这些可以放在模块级的build.gradle.kts里android { defaultConfig { externalNativeBuild { cmake { cppFlags -stdc17 arguments listOf(-DANDROID_STLc_shared) } } ndk { abiFilters listOf(arm64-v8a, x86_64, armeabi-v7a) } } externalNativeBuild { cmake { path file(src/main/cpp/CMakeLists.txt) version 3.22.1 } } }cppFlags -stdc17启用 C17如果你用到了std::optional之类的特性必须要加。-DANDROID_STLc_shared指定共享 STL 库。这里有两种选择c_shared和c_static。前者生成的 .so 体积更小但安装包需要额外打包libc_shared.so后者把这些代码直接编进你的 .so 里体积变大但省去了分发依赖的麻烦。如果你的项目只有一个 .so 文件用c_shared没问题如果有很多个 .so 且互相有依赖建议统一用c_shared避免多个库里各含一份 STL 导致符号冲突。abiFilters只保留你需要的 ABI。这里为什么推荐只编 arm64-v8a 和 x86_64因为目前 Play 商店和国内主流应用市场已经对 32 位包做了限制armeabi-v7a 的设备存量越来越少除非你有明确需求否则编进去只会增加 APK 体积。3.4 完整构建与 APK 集成代码和配置都写好之后构建就很直接了./gradlew assembleDebug首次构建会触发 CMake 配置并编译原生代码这个过程取决于机器性能一般 1-3 分钟。如果一切顺利你会看到类似这样的输出 Task :app:buildCMakeDebug [1/2] Building CXX object CMakeFiles/native-lib.dir/native-lib.cpp.o [2/2] Linking CXX shared library libnative-lib.so生成的 .so 文件在app/build/intermediates/cxx/Debug/目录下按 ABI 分了子目录每个里面有一个libnative-lib.so。在 Kotlin 侧加载和调用class MainActivity : AppCompatActivity() { init { System.loadLibrary(native-lib) } external fun sum(a: Int, b: Int): Int override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding ActivityMainBinding.inflate(layoutInflater) setContentView(binding.root) binding.resultText.text 1 2 ${sum(1, 2)} } }System.loadLibrary会从 APK 里的lib/abi/目录查找对应的 .so 并加载。如果找不到会抛UnsatisfiedLinkError这个错误下面会专门讲。4. 高频编译报错与排查技巧实录4.1 UnsatisfiedLinkError链接不到 JNI 函数这是 NDK 开发中最常见也最让人头疼的错误。现象是 App 启动时崩溃日志长这样java.lang.UnsatisfiedLinkError: No implementation found for void com.example.MyApp.nativeInit()可能的原因有三个按概率排序函数名拼写不一致。Java 里的包名、类名、方法名和 C 里的导出函数名没有完全对上。检查包名里的下划线替换是否正确多层包名每层都要替换。比如com.example.my_app这类带下划线的包名JNI 命名还有额外的转义规则要多查一下资料。少写了extern C。C 编译器默认会对函数名做修饰导致导出符号变成Java_com_example_MainActivity_sum_b54fa2这种样子Java 层当然找不到。把extern C加上或者在函数声明处用#ifdef __cplusplus extern C {包起来。ABI 不匹配。设备是 arm64 架构你只编了 x86_64 的 .so。检查abiFilters和设备的Build.SUPPORTED_ABIS。排查方法用readelf直接看 .so 的导出符号一条命令就能确认readelf -Ws libnative-lib.so | grep Java_com_example正常能看到类似LOCAL DEFAULT 13 Java_com_example_myndkapp_MainActivity_sum的行。如果什么都查不到说明符号没导出来回到上面三个原因里找。4.2 CMake 报错找不到 toolchain 文件如果你是在纯命令行下手动跑 CMake不是用 AS很常见的一个报错是CMake Error: Unable to find toolchain file: /opt/android-sdk/ndk/android-ndk-r28c/build/cmake/android.toolchain.cmake这个 95% 是环境变量ANDROID_NDK_HOME没设置或设置错了。注意 CMake 的 toolchain 文件路径是build/cmake/android.toolchain.cmake不是随便一个路径。用echo $ANDROID_NDK_HOME确认一下再ls那个路径是否存在。还有一个隐蔽的场景如果你从 AS 的 SDK Manager 里下载的 NDK默认会在sdk/ndk/版本号/下面但是 CMake 工具链文件在sdk/ndk/版本号/build/cmake/这两个层级别搞混。4.3 链接时报 undefined reference编译能过但链接时报undefined reference to std::__1::basic_stringchar, ...::find(...)这种情况要么是 STL 库没链接进来要么是链接顺序不对。在 CMake 里target_link_libraries的库顺序很重要被依赖的库要放在后面。另一个可能性是ANDROID_STL设置不当。如果你在命令行里用 c_static但 CMake 脚本里没有把 STL 加进链接就会出现这种问题。在build.gradle.kts里加一行arguments listOf(-DANDROID_STLc_static)或者直接在 CMake 里写set(ANDROID_STL c_static)就行。4.4 与 WSL、镜像站相关的坑非技术项的排查最后一个要强调的是环境差异问题。如果你是照着网上的教程在 WSL 或某些国产 Linux 发行版里配置很可能会遇到路径映射、权限、包管理器源等各种小问题。我自己的经验是尽量先排除环境差异再排查代码问题WSL 1 和 WSL 2 的文件系统性能差异巨大NDK 这种大量小文件编译的场景WSL 1 几乎不可用建议升级到 WSL 2 并把项目放在 Linux 文件系统下。如果是阿里云、腾讯云上的 Linux 服务器下载 NDK 时可能因为网络原因很慢可以考虑用镜像站或者干脆在本地下载。这里顺带提一句content://、file:///这类路径在 Android 上访问时需要注意权限和文件提供器配置和 NDK 本身无关但如果你的 App 要解析本地文件路径后传给 native 层值得注意。4.5 问题排查速查表报错/现象大概率原因解决方案UnsatisfiedLinkErrorJNI 函数名不匹配 / 缺 extern C检查命名规则用 readelf 查看导出符号Unable to find toolchain fileANDROID_NDK_HOME 路径错误检查环境变量确认 toolchain 文件路径undefined reference to std::__1::...STL 链接配置缺失设置 ANDROID_STL调整链接顺序Cannot find -llog缺少 log 库在 CMake 里用 find_library(log-lib) 并链接编译极慢使用了 WSL 1 或跨文件系统访问升级 WSL 2项目放到 Linux 目录ABI filtering警告abiFilters 配置不完整按需配置 abiFilters不编 ABI 不匹配的 .so5. 构建产物分析与性能优化建议5.1 检查 .so 的依赖与符号表编译完成后建议养成检查产物依赖的习惯。很多时候编译不报错跑到设备上才发现启动闪退罪魁祸首就是库里链接了不存在的系统符号。用 NDK 自带的工具链检查依赖llvm-readelf -d libnative-lib.so | grep NEEDED输出里应该只包含libc.so、libm.so、liblog.so、libdl.so这类 Android 系统库以及你明确链接的其他自定义库。如果出现一堆libstdc.so说明你的 STL 配置有问题Android 系统并不自带这个库。检查符号表llvm-nm -D libnative-lib.so | grep T 正常情况下应该能看到 JNI 导出的几个函数以及一些内部符号。如果导出符号过多说明你的-fvisibilityhidden没有生效会增大 .so 体积也容易被其他库符号污染。5.2 控制 .so 体积的几个技巧NDK r28 使用的 LLVM 工具链自带了-Oz优化选项可以针对体积做优化。在 CMake 里添加target_compile_options(native-lib PRIVATE -Oz -ffunction-sections -fdata-sections) target_link_options(native-lib PRIVATE -Wl,--gc-sections)-Oz是 LLVM 特有的GCC 没有。相比-Os它对体积的优化更激进代价是生成的代码可能稍慢一些。如果是发行版能省下 10%-20% 的体积。还有一个技巧是-Wl,--hash-styleboth。Android 系统对 ELF 的 hash 表要求比较严格both会同时生成 SysV 和 GNU hash兼容性更好虽然体积会略增但从稳定性角度考虑值得。5.3 构建缓存的合理利用NDK 编译的时间瓶颈在于 C 源码的编译特别是大型项目。第一次全量编译后后续的增量编译速度就取决于构建系统的缓存策略。CMake 的 Ninja 构建系统天然支持增量编译build/intermediates/cxx/下的产物会按 ABI 和构建类型分开改一个文件通常几十秒就能出结果。如果想进一步加速可以在 NDK 里启用 ccacheexport CCACHE_DIR/opt/ccache export USE_CCACHE1然后在 CMake 配置里加一行set(CMAKE_CXX_COMPILER_LAUNCHER ccache)ccache 的命中率对 C 项目通常能到 60%-80%特别是反复切换分支或改配置时效果非常明显。我在一个 5 万行 C 的项目里测过全量编译从 20 分钟降到 8 分钟这体验差距还是很值得投入的。5.4 多 ABI 构建的策略前面提到 abiFilters 时我说只用 arm64-v8a 和 x86_64。这里补充一个判断依据国内应用市场在 2023 年后针对 64 位包的清理力度很大如果你上传的 APK 里还有 armeabi-v7a 的 .so审核阶段可能被提醒甚至被拒。但是如果项目里有动态下发的 .so比如从服务器拉取 so 后加载就要注意了——系统不会为动态加载的 .so 做 ABI 兼容判断你必须自己根据Build.SUPPORTED_ABIS来选合适的 so 下载这个逻辑要提前写好。不要只判断 32/64 位要精确到具体的 ABI 字符串否则在部分大屏设备上会出现奇葩的 crash。6. 从 NDK 升级到 r28c 的完整迁移笔记6.1 升级前需要备份什么如果你是从老版本比如 r25 或更早升级上来先别急着替换把这几样东西备份好老的android-ndk-rXX整个目录。建议保留至少一个老版本方便遇到新工具链问题时临时切换回来对比验证。项目里的local.properties和gradle.properties记录一下当前使用的是哪个 NDK 版本。所有使用到 NDK 的模块的build.gradle.kts特别是externalNativeBuild和ndk下的配置。6.2 具体升级步骤我建议的升级路径很保守每一步都留了回滚余地下载新版本 NDK 并解压到独立目录不覆盖旧的。在local.properties里把ndk.dir指向新版本目录先不用ndkPath环境变量。执行一次./gradlew clean assembleDebug观察报错。如果编译通过跑一遍全量测试特别是涉及 JNI 的用例。如果编译失败根据错误信息逐个击破必要时临时切回旧版本对比。6.3 从 r26 移植到 r28 常见的坑从 r26 升到 r28我遇到的坑主要集中在两个地方。第一个是AIDL 相关的编译选项。新版 NDK 对-Wl,--no-undefined的默认值做了调整如果你的 C 代码里有未定义的符号比如调试用的空实现链接阶段会直接失败而不是像以前那样编出一个在运行时才崩的 .so。这其实是件好事但如果你习惯了旧的宽松行为第一感觉是“怎么突然就编不过了”。第二个是Kotlin 1.8 以下的兼容性。r28 的 NDK 工具链编出的 .so 对进程的初始化方式有变化老版本的 Kotlin 协程在 C 层创建的线程上启动时可能出现奇怪的art::Thread挂死问题。这不是 NDK 的锅但如果你遇到类似现象可以先升一下 Kotlin 版本再继续排查。6.4 回滚与灰度方案即使你做好了万全准备也难保有一些只在特定真机上才暴露的问题。我的建议是在使用新 NDK 的构建上保留老的 .so 的备份并且在代码里加一个 native 层版本号上报到日志系统方便线上出问题时快速定位是哪个版本编出来的。如果出问题回滚动作要快git revert掉 NDK 相关的改动切回旧目录重新构建发布。这是最稳妥的操作。7. 还有几个值得一试的进阶场景7.1 自定义 CMake 工具链文件NDK 自带的android.toolchain.cmake是所有 Android CMake 构建的基础但如果你想做一些自定义操作比如修改默认的 sysroot、指定额外的 include 路径、强制使用某个 flags可以在项目里放一个自己的 toolchain 包装文件然后 include NDK 自带的那个set(ANDROID_ABI arm64-v8a) set(ANDROID_PLATFORM android-28) include(/opt/android-sdk/ndk/android-ndk-r28c/build/cmake/android.toolchain.cmake)这样所有额外的配置都集中在一个文件里团队协作时易读性更好。7.2 在 CI 里使用 Docker 镜像做 NDK 构建很多团队已经用 Docker 统一构建环境了。Android NDK 在 Docker 里的配置和本机没有本质区别但有几个细节要注意基础镜像选择ubuntu:22.04或ubuntu:24.04别用 alpineglibc 的兼容性会省去很多麻烦。镜像里需要预装openjdk-17-jdk-headless、unzip、wget、git等基础工具。如果构建过程中要下载依赖库比如ContentProvider相关的文件、content://路径的资源注意 Docker 网络代理配置别让 CI 机器因为超时白跑一轮。这里顺带说一句content://com.baidu.searchbox.fileprovider这类 Android URI 在 CI 环境里是不存在的如果代码里有对file:///storage/emulated/0/的路径依赖多半是模拟了真机上从文件选择器选取文件后传给 JNI 层的场景这类逻辑在 CI 里没法完整覆盖建议单独写 instrumented test 跑真机。7.3 调试 Native 代码的实用技巧NDK 开发最痛苦的就是 debug。虽然 AS 现在对 LLDB 的支持已经很好了但在 Linux 下还有一种更轻量的方式在 C 里输出关键变量到 logcat用 shell 脚本批量解析。在 native 代码里打日志#define LOG_TAG NativeLib #include android/log.h #define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)然后adb logcat -s NativeLib:D这比打断点快很多尤其在真机上排查问题时日志几乎是唯一手段。别嫌土好用才是硬道理。7.4 与其他原生生态打通NDK 不只服务于 App 内部的 C 模块很多跨平台方案的底层也要靠它Flutter 的 engine、React Native 的 reanimated、OpenCV Android SDK全都是走 NDK 编译成 .so 的。理解了 NDK 的基本工作流后面你去分析这些三方的源码或二次开发会顺畅非常多。最后说点实在的NDK 的配置和排错说难也难说简单也简单。难在错误信息往往不直观特别是遇到链接阶段的问题一上来就是几千行输出容易让人懵。简单在有了一套固定的排查路径之后大部分问题都能在十分钟内定位。我个人实际使用下来的最大体会是环境配置这东西一次性做对比反复调试省下的时间要多得多。下载完压缩包先校验、解压完先看目录结构、配置完先跑一个最小示例验证这些步骤每一步看起来多余但能帮你挡住 90% 的后续麻烦。如果你在升级 NDK 版本或配置 Linux 环境时遇到本文没覆盖的问题建议先看 NDK 自带的CHANGELOG.md里面记录了每个版本的破坏性变更。其次是官方 issue 列表很多坑早就有人踩过并给出了 workaround。最后再考虑搜索引擎碰运气但记得带上具体的错误码和 NDK 版本号否则搜出来的答案十有八九是老版本的经验白耽误时间。希望这篇笔记能帮到你动手配置祝编译顺利链接一次过。本文还有配套的精品资源点击获取