ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

NDK r28c 在 Linux 上的安装、编译与踩坑指南

NDK r28c 在 Linux 上的安装、编译与踩坑指南 简介Android NDK r28c 是面向Linux 64位平台x86架构的官方原生开发工具集专为Android平台C/C开发者设计用于构建高性能图形渲染、音视频处理、AI推理等对底层控制有强需求的应用模块。本资源完整包含NDK核心头文件如NdkCameraMetadataTags.h、NeuralNetworks.h、gl32.h等、Python构建脚本、Markdown文档说明及少量PDF/文本格式的官方指南支撑JNI开发、HAL对接与硬件加速能力调用。压缩包共2000个文件其中1977个头文件.h构成API主体11个Python脚本用于自动化构建与测试10个Markdown文档提供版本说明与使用指引整体体积达688.8MB结构规范、即下即用。已有230人下载学习适用于中高级Android原生开发者快速搭建编译环境、查阅最新NDK接口定义、调试Native层逻辑或集成Camera/NPU/OpenGL等系统级能力。 上周一个同事在 Ubuntu 22.04 上遇到一个典型问题他从官网下载了 android-ndk-r28c-linux.zip按说明解压完运行ndk-build --version直接报 GLIBC_2.34 找不到。他第一反应是文件损坏重新下两遍还是一样最后才定位到是系统 glibc 版本太旧而 r28c 的预编译工具链已经悄悄提高了对运行库的要求。这件事让我想系统写一篇 NDK r28c 在 Linux 上从安装到编译的实操记录。它不复杂但细节很容易踩尤其是从旧版 NDK 直接跳到 r28 系列的人很多差异官方文档根本没说全。这篇内容适合刚接触 NDK 的初学者也适合正在升级 NDK 版本、需要在 Linux 服务器或本机交叉编译.so的开发者。1. 先搞清楚 r28c 和旧版差在哪1.1 工具链彻底转向 ClangNDK 从 r23 开始就正式移除了 GCC 相关的独立工具链脚本r28 系列里已经看不到gcc、g这类可执行文件了。你在toolchains/llvm/prebuilt/linux-x86_64/bin目录下能看到的全是clang、clang以及带目标架构前缀的编译驱动比如aarch64-linux-android21-clang、armv7a-linux-androideabi21-clang。这对老项目影响很大。不少网上教程还停留在“先跑 make-standalone-toolchain.py 生成独立工具链”的阶段那套流程在 r28c 里根本没有对应入口照搬旧脚本直接报错。实际上 NDK 官方现在的态度就是要么用 ndk-build要么用 CMake要么直接用 clang 交叉编译不再提供中间层包装。1.2 API 级别策略收紧r28 系列在构建时对minSdkVersion的关注度更高了。以前 NDK 默认的APP_PLATFORM可能比较宽松新版工具链则倾向于按较高的最低 API 级别来链接。如果你沿用旧的android-16这类低版本目标大概率会碰到平台头文件缺失或者__ANDROID_API__宏触发的条件编译分支不生效。我个人的建议是新项目直接按系统当前能接受的最低版本定比如android-21老项目如果要升级到 r28c优先把minSdkVersion一起提上来别一边更新 NDK 一边守着远古 target。1.3 r28c 这类小版本更新为何值得关注NDK 的发布策略和大版本同步r28b、r28c 都是针对构建工具链的补丁版。小版本修复的东西不会出现在大新闻里但往往很实际比如修复特定架构下编译某些第三方库时的内联汇编问题、修正链接器对 LLD 的默认行为等。生产环境里我会优先选一个已经发布一段时间的稳定小版本而不是直接追最新 RC 或 Canary。r28c 就属于比较稳的位置既能享受到 r28 的新工具链特性又不至于像 Dev Preview 那样频繁变动。2. 下载解压前把 Linux 环境检查一轮2.1 网络下载、校验、解压的完整命令链先说下载一般从 Android 开发者官网的 NDK 下载页拿压缩包。文件名是android-ndk-r28c-linux.zip对应的是 Linux 64 位平台。下载命令可以用wget或curl但我更建议顺手做一次 SHA-256 校验因为二进制文件太大下载中途断掉又没报错的情况也不少wget https://dl.google.com/android/repository/android-ndk-r28c-linux.zip sha256sum android-ndk-r28c-linux.zip把输出结果跟下载页展示的官方哈希值比对一致再解压。这一步在公网下载大文件时尤其值得做省得后面编译报出一堆莫名其妙的链接错误。2.2 系统依赖库核对清单r28c 解压后的工具链大部分是动态链接的对系统底座有一定要求。我建议在解压前先跑一轮检查省得解压完才报环境问题64 位 Linux 系统32 位系统跑不了 r28c。glibc版本不能太旧r28 系列的预编译工具链通常要求 GLIBC_2.17 以上部分较新组件会要到 GLIBC_2.34。系统要有python3NDK 的构建脚本内部会调用 Python。make、patch、unzip这类基础命令要存在建议用sudo apt install make python3 unzip zlib1g-dev之类的命令补齐。很多容器镜像或精简版 Linux 发行版会把patch、make这类工具裁掉结果 NDK 跑起来就报make: command not found或patch: command not found。看着是 NDK 的问题其实就是宿主环境缺依赖。2.3 解压目录和权限选择解压我习惯统一放到/opt或用户目录下关键是路径里不能有空格不要有中文比如别解到/mnt/windows 下载/NDK这种路径。NDK 的构建脚本对路径很敏感带空格的路径会让 CMake 和 make 在传参时直接断掉。sudo unzip android-ndk-r28c-linux.zip -d /opt/解压完成后还要确认权限没问题。有时候通过某些图形界面工具解压文件权限会丢后续执行ndk-build就报 permission denied。稳妥起见可以补一次递归加执行权限sudo chmod -R x /opt/android-ndk-r28c3. 目录结构拆解r28c 解压后哪些目录在干活3.1 顶层目录速览NDK 解压完大概有 6 到 8 个顶层目录第一次打开容易看花眼。我把主要目录的作用列出来目录作用build/构建系统支持文件比如 CMake 工具链文件android.toolchain.cmake就在这里meta/NDK 构建系统的元数据一般不用管platforms/各 API 级别对应的 Java 层 Android.jar主要用于 SDK 层面构建prebuilt/NDK 自带的预编译工具比如 make、awk 等sources/C STL 的源码、第三方库源码等sysroot/交叉编译要用的头文件和库文件集合最终链接时非常重要toolchains/核心编译器位置LLVM/Clang 就在这里对大多数人来说真正要接触的只有toolchains/llvm、platforms和sysroot。3.2 toolchains/llvm 里藏着的编译器toolchains/llvm/prebuilt/linux-x86_64/bin是核心中的核心。这个目录下的可执行文件数量很多但命名有规律aarch64-linux-android21-clang目标架构为 arm64-v8a目标 API 级别为 21 的 C 编译器aarch64-linux-android21-clang目标架构为 arm64-v8a目标 API 级别为 21 的 C 编译器armv7a-linux-androideabi21-clang目标架构为 armeabi-v7a 的 C 编译器x86_64-linux-android21-clang目标架构为 x86_64 的 C 编译器不带 API 数字的aarch64-linux-android-clang也存在它会自动选择 NDK 支持的默认 API 级别。在 r28c 里直接使用带 API 级别的编译器更可控因为默认值不一定是你项目的minSdkVersion。3.3 sysroot 和 platforms 的区别有的新手会把platforms和sysroot弄混其实两者分工不同。sysroot是 NDK 交叉编译的“根”里面装着 Android 系统 API 对应的头文件长期演进版本和用于链接的.so/.a库。当你用clang交叉编译时-sysroot参数会指向这里。platforms里装的是android.jar等 Java 层接口主要是给 Android SDK 构建 APK 时用的。如果你只是单独编译 C/C 代码成.so基本上不需要碰platforms。4. 环境变量与 Android Studio 接入的两种姿势4.1 命令行环境变量配置如果只想在终端里用ndk-build或 CMake比较简单的方式是设置两个环境变量export ANDROID_NDK_HOME/opt/android-ndk-r28c export PATH$ANDROID_NDK_HOME:$PATH我建议把这两行写进~/.bashrc或~/.zshrc重新登录后自动生效。要注意的是ANDROID_NDK_HOME这个变量名在 Android Gradle Plugin 里也认很多 CI 机器上配置 NDK 就是靠它。验证是否配置成功$ANDROID_NDK_HOME/ndk-build --version如果能看到构建版本信息说明 NDK 主程序能跑。再验证编译器$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android21-clang --version能输出 clang 版本号说明工具链可执行文件具备运行条件。4.2 Android Studio 中手动指定 NDK 路径Android Studio 优先去 SDK 目录下的ndk/版本号里找 NDK。如果你不喜欢默认位置最省事的方法是把压缩包解压到 SDK 的ndk目录下并改名为 Gradle 能识别的版本号格式。但我不建议这样做。因为 NDK 完整版本号是一长串比如28.x.xxxxxx手动改名很容易和ndkVersion配置对不上。更推荐的做法是在项目根目录的local.properties里显式指定sdk.dir/home/youruser/Android/Sdk ndk.dir/opt/android-ndk-r28c或者不写ndk.dir直接设置环境变量ANDROID_NDK_HOME新版 AGP 会优先读取这个变量。如果项目里的ndkVersion配置和实际安装版本不一致Gradle 会尝试下载对应的版本这也是很多人卡住的原因。一个比较稳的组合是local.properties配sdk.dir模块build.gradle里写android { ndkVersion 28.2.1356572 }版本号要以你解压出来的实际目录名或 NDK 包元信息为准不要照抄网上任何例子。r28c 对应的具体 build 号会在解压目录里体现你打开/opt/android-ndk-r28c/source.properties就能看到Pkg.Revision字段。5. ndk-build 和 CMake 各有各的脾气两套构建实战5.1 ndk-build 方式ndk-build 是 NDK 自带的传统构建方式核心是Android.mk和Application.mk一组 Make 文件。项目结构大致是jni/ Android.mk Application.mk native-lib.cppAndroid.mk的写法LOCAL_PATH : $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE : native-lib LOCAL_SRC_FILES : native-lib.cpp LOCAL_LDLIBS : -llog include $(BUILD_SHARED_LIBRARY)Application.mk里指定目标 ABI 和 C 运行时APP_ABI : arm64-v8a armeabi-v7a APP_PLATFORM : android-21 APP_STL : c_shared然后在项目根目录执行$ANDROID_NDK_HOME/ndk-build默认输出会放在libs/abi/目录下。如果APP_ABI里写了多个架构NDK 会逐个编译。这种方式的优点是简单直接适合传统项目、纯 C/C 库、不依赖 Gradle 的构建场景。5.2 CMake 方式CMake 是 Android Studio 默认推荐的构建方式本质上是 NDK 提供了一个工具链文件让 CMake 知道怎么调用 clang、怎么设置 sysroot、怎么处理 ABI。一个最小的CMakeLists.txtcmake_minimum_required(VERSION 3.22.1) project(native-lib) add_library(native-lib SHARED native-lib.cpp) find_library(log-lib log) target_link_libraries(native-lib ${log-lib})命令行编译时最关键的是指定工具链文件cmake -S . -B build \ -DCMAKE_TOOLCHAIN_FILE$ANDROID_NDK_HOME/build/cmake/android.toolchain.cmake \ -DANDROID_ABIarm64-v8a \ -DANDROID_PLATFORMandroid-21 cmake --build build这里ANDROID_ABI可以是armeabi-v7a、arm64-v8a、x86、x86_64中的任意一个。ANDROID_PLATFORM建议和项目minSdkVersion保持一致。CMake 相比之下更灵活适合大型项目和依赖很多第三方库的工程。Android Studio 里新建 Native C 项目默认就会生成 CMake 版本的项目骨架。5.3 两种方式的输出差异ndk-build 的输出通常集中在libs/abi/CMake 的输出则取决于你指定的CMAKE_LIBRARY_OUTPUT_DIRECTORY默认在build/下的子目录里。要看清楚最终生成的是.so还是.a取决于你在构建文件里写的是BUILD_SHARED_LIBRARY还是BUILD_STATIC_LIBRARY或 CMake 里的SHARED/STATIC。实际项目里我习惯先把第三方库编成静态库.a再链接进最终.so这样交付产物单一也避免在多个模块间重复暴露符号。6. 从零编译一个 JNI 动态库并跑进 App6.1 准备 JNI 源文件先写一个最简单的 JNI 函数验证整个工具链链路是否通畅#include jni.h #include string extern C JNIEXPORT jstring JNICALL Java_com_example_app_MainActivity_stringFromJNI( JNIEnv* env, jobject /* this */) { std::string hello Hello from NDK r28c; return env-NewStringUTF(hello.c_str()); }注意函数名里的包名和类名com_example_app_MainActivity对应 Java 层的com.example.app.MainActivity。如果包名或类名不一致运行时会报UnsatisfiedLinkError。6.2 用 ndk-build 编译验证按第 5.1 节的Application.mk和Android.mk配置在jni目录外执行$ANDROID_NDK_HOME/ndk-build看到类似输出[arm64-v8a] Compile : native-lib native-lib.cpp [arm64-v8a] SharedLibrary : libnative-lib.so [arm64-v8a] Install : libnative-lib.so libs/arm64-v8a/libnative-lib.so说明 NDK 工具链已经正常工作了。这是在 Linux 上验证 r28c 最简单的测试能跑通这一步就说明大部分环境问题已经排除。6.3 用 CMake 编译验证同样一份CMakeLists.txt执行cmake -S . -B build \ -DCMAKE_TOOLCHAIN_FILE$ANDROID_NDK_HOME/build/cmake/android.toolchain.cmake \ -DANDROID_ABIarm64-v8a \ -DANDROID_PLATFORMandroid-21 cmake --build build最后在build/目录下找到libnative-lib.so。这一步跑通说明 CMake 工具链文件没有被破坏sysroot和链接器都工作正常。6.4 把 .so 集成进 Android App如果只是想在 App 里用这个库把编译好的libnative-lib.so放到app/src/main/jniLibs/arm64-v8a/下。Java 层加载package com.example.app; public class MainActivity extends Activity { static { System.loadLibrary(native-lib); } public native String stringFromJNI(); }这样绕开 Android Studio 的自动构建流程适合快速验证“手动编译的库能不能被 App 加载”。实际开发中我更多用这个方式测试第三方预编译库比如 OpenSSL、FFmpeg 之类。7. 解压后第一波报错的完整排查记录7.1 GLIBC 版本不兼容是最高频问题GLIBC_2.34 not found这类错误在检查系统时其实很直观。r28 系列预编译的 clang 和 ndk-build 在运行时依赖的 glibc 符号已经高于 Ubuntu 18.04 这类老版本。排查命令ldd --version看到版本低于要求后两个方向一是系统升级到 Ubuntu 20.04 以上或换一个更新版本的系统二是放弃 r28c换用更老的 NDK 版本。旧系统补 glibc 的风险比较大不建议手动替换系统库我试过容易把系统搞到崩溃重装比修补更省时间。7.2 zlib 缺失导致的链接异常另一个常见问题是解压工具能跑但 clang 在链接阶段报找不到libz.so.1。这种报错很容易误判为代码问题实际是系统没有安装 32 位兼容库或压缩库。处理方式就是装依赖sudo apt install zlib1g-dev装完重新跑一次编译一般就能过。类似这种问题我的排查习惯是看到error while loading shared libraries或cannot open shared object file第一反应查系统库而不是翻代码。7.3 Android Studio 报 NDK not configured在 Android Studio 里改完 NDK 路径后Gradle 仍可能报NDK not configured。这种情况通常不是路径写错而是ndkVersion和实际 NDK 版本不匹配Gradle 找不到对应目录就当成没配置。检查local.properties里的ndk.dir是否指向正确目录。检查build.gradle里的ndkVersion是否和source.properties里的Pkg.Revision一致。如果还不行清理一下.gradle缓存的配置。7.4 解压后没有执行权限导致 permission denied这个问题在服务器上经常遇到特别是用 root 用户下载再解压给普通用户用。当 NDK 工具链在普通用户下执行时部分二进制文件可能没有x权限。处理方式chmod -R x /opt/android-ndk-r28c如果是多用户使用同一套 NDK建议放到/opt下并由所有开发者统一使用。服务器上尽量不要解压到/tmp因为重启可能被清理而且权限管理也更混乱。7.5 路径有空格导致 build 脚本炸裂/home/user/My Downloads/android-ndk-r28c这类路径在命令行下容易出问题。CMake 和 ndk-build 内部会拼接大量的绝对路径带空格时引号处理一旦出漏子就报No such file or directory而路径明明存在。最省心的做法是解压到没有空格、没有中文、没有特殊符号的目录例如/opt/android-ndk-r28c。这个看似小问题实际能消掉一半的玄学报错。8. 把这些经验固化成之后的 NDK 使用习惯8.1 固定 NDK 版本别混着用同一个项目里 A 模块用 r28c、B 模块用旧版 NDK短期内可能不炸但遇到 C 标准库 ABI 不兼容时会非常痛苦。NDK 的版本升级经常伴随着 libc 实现调整混用版本后通过System.loadLibrary加载多个.so很容易在类加载阶段或运行时出现符号找不到。我现在的固定做法是所有模块统一一个 NDK 版本升级时先看 release notes 里关于 ABI、最小 API 级别、libc 的变更说明再决定是否整体升级。8.2 交叉编译第三方库时的通用套路很多人在 Linux 上拿 NDK 不是为了跑 Android Studio 项目而是为了交叉编译 OpenSSL、FFmpeg、cURL 这类第三方库。r28c 下我建议直接写一个带--target的 clang 包装脚本或者用 CMake 工具链文件统一管理。以直接调用 clang 为例编译 OpenSSL 时通常要设置export CC$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android21-clang export CXX$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android21-clang然后进到 OpenSSL 源码目录里按 Android 目标平台配置。这种玩法本质上就是把 NDK 当通用交叉编译工具链用不局限于 Android 应用开发。8.3 C 标准库的选择c_shared 与 c_static如果项目代码用了 STLAPP_STL或 CMake 的ANDROID_STL设置就很重要。c_shared会链接系统里的 libc_shared.so生成的.so体积更小但设备上需要同时提供 libc_shared.soc_static把库静态编进目标.so部署简单但每个模块都静态链接会导致体积膨胀和符号冲突。我的经验是一个 App 有多个.so的时候统一用c_shared只在主模块里带一份 libc_shared.so单库项目随意但c_static在调试时符号重复的坑更少。8.4 别把 NDK 当“绿色软件”随便拷NDK 体积大、文件多、内部路径依赖强我不建议直接把它放进 Git 仓库也不建议在项目里用相对路径引用它。正确做法是在 CI 机器上安装固定版本环境变量全局统一项目里只存版本号配置。另外现在很多团队已经用 Docker 镜像做 Android 构建里面预先装好 JDK、SDK 和 NDK。这种做法很值得推广因为只要镜像升级了 NDK所有引用的流水线都能保持一致不会出现开发机器能编译、服务器上不能编译的尴尬。我自己在实测 r28c 时最大的感受是新版 NDK 对构建前置条件的要求更高了但一旦环境就绪编译效率和产物稳定性都明显提升。如果你正准备升级别把第一波报错想得太可怕多半是环境问题按上面第 2 节和第 7 节的清单过一遍比乱翻 Stack Overflow 高效得多。本文还有配套的精品资源点击获取
返回列表