Linux下Android NDK r28c下载配置与编译实战指南

发布时间:2026/9/1 18:29:12
Linux下Android NDK r28c下载配置与编译实战指南 简介Android NDK r28c 是面向Linux 64位x86平台的官方原生开发工具集专为Android平台C/C开发者设计用于构建高性能图形渲染、音视频处理、AI推理等对性能敏感的原生模块。本资源完整提供android-ndk-r28c-linux.zip压缩包含2000个文件主体为1977个头文件.h涵盖Camera元数据标签、Neural Networks API、OpenGL ES扩展、Unicode底层支持及V4L2控制等关键接口辅以11个Python脚本自动化构建与校验、10个Markdown文档API说明与迁移指南、1个PDF官方发行说明及基础文本文件结构规范、即下即用。资源包大小688.8MB目录层级清晰头文件组织严格遵循NDK标准路径便于集成至CMake或ndk-build工程。目前已有230人学习下载适合中高级Android原生开发工程师快速部署开发环境、查阅最新原生API定义并开展跨架构适配工作。 做 Android 开发尤其是开始碰 so 库、JNI 或者音视频底层的时候迟早会和 Android NDK 这个压缩包打照面。如果你和我一样在 Linux 环境工作那最常见的入口大概率就是那个名为 android-ndk-r28c-linux.zip 的文件。这篇文章不打算复读官方文档就聊聊我实际下载、解压、配置、编译这套 NDK 的完整过程包括目录里每个部件是干什么的ndk-build 和 CMake 两条路线怎么选以及几十次编译报错之后总结出来的排错经验。适合刚准备在 Linux 桌面或服务器上搭建 Android 原生编译环境的朋友也适合需要在 CI 机器上离线部署 NDK 的团队参考。1. Android NDK 是干嘛的我为什么选择单独下 zip1.1 先捋清楚 NDK 和 SDK 的分工很多刚接触 Android 原生开发的人会有一个误区觉得 NDK 好像包含了 SDK或者两者是替代关系。实际上 Android SDK 管的是 Java/Kotlin 层的编译打包提供的是 android.jar、aapt2、gradle 插件这些工具链而 Android NDK 是一套独立的原生开发套件专门用来把 C/C 代码交叉编译成各个 CPU 架构下的机器码。一个比较接地气的类比是SDK 像家用工具箱里的常用扳手NDK 是那套专门拧异形螺丝的批头平时用不上一遇到 JNI、游戏引擎、音视频编解码、OpenGL 渲染、加密算法这类场景就必须把它请出来。在 Linux 上做这件事有个天然优势交叉编译这种活本身就是服务端场景不需要图形界面只要一个可用的终端和一套正确的工具链就能给 Android 设备产出 .so 动态库。很多团队的 App 里那些底层能力都是在 Linux 构建机上统一编译出 arm64-v8a、armeabi-v7a 等 ABi 的产物再打进 APK 的。1.2 从 Android Studio 自动装 vs 手动下 zip 的区别大量新手是用 Android Studio 的 SDK Manager 一键勾选 NDK 完成的装完之后自动放到 $SDK/ndk/ 目录下。这种方式当然省事但我自己更倾向于手动下载 android-ndk-r28c-linux.zip原因有三个。第一脚本化部署时手动 zip 可以精确控制版本项目里别人用的 r28c 我不会不小心装成 r27。第二离线环境或 CI 服务器上AS 的图形界面根本不适用直接把 zip 拷过去解压就能用。第三手动解压能让你看清楚 NDK 到底由哪些组件组成后面出了问题也好排查而不是对着 AS 的报错一脸茫然。顺带解释一下 r28c 这个版本号。NDK 的命名规则大致是 r 后面跟大版本号再跟一个字母代表小修订。r28c 可以理解为 28 这个大版本下的第三次修订修复了一些上个小版本的编译问题。具体改动以官方 Release Notes 为准但对我们日常使用者来说它就是当前比较新的一版稳定工具链。1.3 为什么要用 linux 后缀的包文件名里的 linux 标识了宿主平台也就是说这套 NDK 只能在 Linux 系统上运行。别小看这一点我见过有人把 linux 版本的 zip 放到 Windows 上强行解压然后对着“无法识别的应用程序”发呆。NDK 的压缩包是针对不同宿主机分别发布的macOS 要用 darwin 版本Windows 要用 windows 版本Linux 服务器当然就选 linux 版本。如果你的构建机是 x86_64 架构的 Linux这个包就是正确的选择少数 ARM 架构的开发板要跑 Android NDK得另找对应构建不在本文讨论范围内。2. 安装前的准备JDK、SDK、构建工具一个都不能少2.1 先检查自己的 Linux 发行版和基础依赖拿到 zip 之后先别急着解压先确认系统环境。通常我们只要执行几条命令检查一下cat /etc/os-release java -version unzip -v cmake --version这里 java -version 要特别注意新版 Android Gradle PluginAGP 8.x要求 JDK 17如果你机器上还停留在 JDK 8 或 11后面用 Gradle 构建时会直接报错。Ubuntu、Debian 系系统可以这样装基础依赖sudo apt update sudo apt install -y unzip wget openjdk-17-jdk cmake build-essential有人会问既然 NDK 自带编译工具链为什么还要系统装 cmake这里要说清楚NDK 包里确实带了构建脚本和工具链但 Gradle 调用的 CMake 往往是独立安装的而且 AGP 对 CMake 版本还有要求。如果你完全不走 Gradle只手动跑 NDK 的 cmake 工具链那系统层面的 cmake 版本也不能太老否则会出现语法不兼容的问题。2.2 下载 android-ndk-r28c-linux.zip从官方仓库拿包下载我习惯用 wget主要有两个原因一是可以断点续传二是可以直接在脚本里自动执行不需要打开浏览器。官方下载地址是wget -c https://dl.google.com/android/repository/android-ndk-r28c-linux.zip-c 参数很重要这个包体积不小网络抖动是常有的事有了断点续传就能避免从头再下。下载完成之后建议顺手做一次完整性校验。NDK 官方的校验值在 Google 的 repository 列表里有你也可以先把文件下到本地然后用 sha256sum 计算哈希sha256sum android-ndk-r28c-linux.zip如果是在 Windows 上下载完再传到 Linux 上校验更不能省。文件传输过程中偶尔出现损坏解压时候不会立刻报错但编译到一半出现莫名其妙的链接错误会让你非常头疼。2.3 解压安装与目录规划NDK 的安装其实没有“安装”这一步它就是解压即用。我习惯把 NDK 统一放在 Android SDK 的 ndk 目录里这样 Android Studio 和 Gradle 都能自动识别mkdir -p ~/Android/Sdk/ndk unzip android-ndk-r28c-linux.zip -d ~/Android/Sdk/ndk/解压完成之后~/Android/Sdk/ndk/ 下面会多出一个 android-ndk-r28c 目录。这个目录名尽量不要改因为 Gradle 识别 NDK 版本号靠的就是目录名。我以前手贱把它改成 ndk28结果 Android Studio 里版本号对不上折腾了很久才反应过来。还有一点是关于权限的。如果非要用 sudo 解压到 /opt 这类系统目录解压后记得把目录所有者改回普通用户否则后续构建产生临时文件时会遇到 Permission denied。我的建议是别折腾直接放用户目录走完整个流程会发现省很多事。2.4 设置 ANDROID_NDK_HOME 和 PATH 环境变量解压完以后我建议立刻把环境变量配置好。那套工具链本身可以直接用绝对路径调用但每天都敲那么长一串绝对路径真的不现实配置环境变量相当于给工具链加了个别名。我在 ~/.bashrc 里追加了这几行export ANDROID_NDK_HOME$HOME/Android/Sdk/ndk/android-ndk-r28c export ANDROID_NDK_ROOT$ANDROID_NDK_HOME export PATH$PATH:$ANDROID_NDK_HOME有人会问 ANDROID_NDK_HOME 和 ANDROID_NDK_ROOT 到底该用哪个实话说不同脚本认的名字不一样Gradle 里通常看 local.properties而一些第三方构建脚本可能读 ANDROID_NDK_HOME老点的工具又认 ANDROID_NDK_ROOT。干脆两个都设置成同一个路径这是最省心的做法。保存之后执行 source ~/.bashrc然后验证一下ndk-build --version能看到版本信息就说明环境变量生效了。如果你用的是 zsh记得改 ~/.zshrc而不是改完 bashrc 再抱怨不生效这个坑我替大家踩过了。3. 核心配置与编译链路从目录结构到两条主流编译路线3.1 解压完先看懂 NDK 目录结构别急着写代码配置完环境变量我强烈建议先花几分钟浏览一下 NDK 的目录结构。要动手交叉编译你不必了解每个文件但至少要认识几个关键目录。我整理了一个表格按常用程度排序目录或文件作用toolchains/llvm/prebuilt/linux-x86_64核心工具链包含 clang/clang、ld、ar、sysroot 等platforms/android-XX各 API Level 对应的系统库和头文件sources/cxx-stlC 标准库实现及相关头文件build/cmake/android.toolchain.cmakeCMake 交叉编译工具链文件ndk-build基于 make 的传统构建脚本package.xmlNDK 组件元数据Android Studio 会读取它识别版本这里我想重点解释 toolchains/llvm/prebuilt/linux-x86_64 这个目录。从 NDK r23 开始GCC 就被移除了Clang 成为唯一官方编译器。你在这个目录的 bin 下能看到一堆带格式的编译器比如 aarch64-linux-android34-clang这种名字的含义是“目标 ABI Android API Level 编译器类型”。它们本质上就是 Clang 的封装内部自动指定了 target 和 sysroot。sysroot 是什么你可以理解成“拥有一整套头文件和库的虚拟根目录”编译器在里面能找到 android/log.h、liblog.so 这些 Android 特有的东西所以你在命令行编译时不用手动指定一大堆 -I 和 -L 参数。3.2 用 ndk-build 编译一个最简 JNI 库说完了目录来点实际操作。ndk-build 是 NDK 里老牌的构建系统它基于 Makefile适合维护历史悠久、已经写好了 Android.mk 的项目。虽然新项目用 CMake 更多但看懂 ndk-build 能帮你了解整个编译流程的底层逻辑。假设我有一个最简单的 JNI 项目目录结构如下hello-jni/ ├── jni/ │ ├── Android.mk │ ├── Application.mk │ └── hello-jni.c └── src/ └── com/ └── example/ ├── MainActivity.java └── NativeLib.javaNativeLib.java 里声明了 native 方法package com.example; public class NativeLib { static { System.loadLibrary(hello-jni); } public static native String stringFromJNI(); }对应的 C 文件 hello-jni.c#include jni.h JNIEXPORT jstring JNICALL Java_com_example_NativeLib_stringFromJNI(JNIEnv *env, jobject thiz) { return (*env)-NewStringUTF(env, Hello from NDK); }再看两个 mk 文件。Android.mk 是模块定义Application.mk 是全局配置# Android.mk LOCAL_PATH : $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE : hello-jni LOCAL_SRC_FILES : hello-jni.c include $(BUILD_SHARED_LIBRARY)# Application.mk APP_ABI : all APP_PLATFORM : android-23在项目根目录执行ndk-build正常的话几秒后能看到类似这样的输出[arm64-v8a] Compile : hello-jni hello-jni.c [arm64-v8a] SharedLibrary : libhello-jni.so [armeabi-v7a] Compile : hello-jni hello-jni.c [armeabi-v7a] SharedLibrary : libhello-jni.so [x86] Compile : hello-jni hello-jni.c [x86] SharedLibrary : libhello-jni.so [x86_64] Compile : hello-jni hello-jni.c [x86_64] SharedLibrary : libhello-jni.so生成的所有 .so 文件会按照 ABI 分别放在 libs/armeabi-v7a/、libs/arm64-v8a/ 这些目录下。APP_ABI : all 的意思是编译所有支持的 ABI这里要提醒一下r28 里的 all 已经不包括 armeabi 了那是 16 位时代的产物老项目搬到新版 NDK 时往往就在这里报第一个错。如果你的应用只面向真机其实没必要编 x86 和 x86_64后面我会讲到怎么用 abiFilters 控制。3.3 用 CMake 交叉编译这才是新项目的默认选择虽然 ndk-build 能跑但 Android Studio 里用 CMake 创建 C 工程已经是默认行为了。CMake 的好处是跨平台、生态强大、第三方库几乎都支持而且 AGP 对 CMake 有很好的集成。手动使用 NDK 的 CMake 工具链最核心的是指定 android.toolchain.cmake 文件。这个文件位置在 $ANDROID_NDK_HOME/build/cmake/android.toolchain.cmake。命令行方式大致是这样cmake -DCMAKE_TOOLCHAIN_FILE$ANDROID_NDK_HOME/build/cmake/android.toolchain.cmake \ -DANDROID_ABIarm64-v8a \ -DANDROID_PLATFORMandroid-34 \ -DCMAKE_BUILD_TYPERelease \ ..这三个参数的含义必须清楚。CMAKE_TOOLCHAIN_FILE 是交叉编译的钥匙它告诉 CMake 要使用 Android 的工具链ANDROID_ABI 决定编译出来的 so 给哪种 CPU 用arm64-v8a 对应 64 位 ARM 设备armeabi-v7a 对应 32 位 ARM 设备ANDROID_PLATFORM 对应 API Level也就是你的 so 最低要兼容哪个 Android 版本。有一点要注意ANDROID_PLATFORM 指定的 API Level 不能比工程里 minSdkVersion 还低否则运行时会出现符号找不到的问题。如果项目里有 CMakeLists.txt典型的最小配置如下cmake_minimum_required(VERSION 3.22.1) project(ndkdemo) add_library(native-lib SHARED native-lib.cpp) find_library(log-lib log) target_link_libraries(native-lib ${log-lib})这里 find_library 找到的是 Android 系统自带的 liblog.so它是 NDK 提供的系统库之一。如果你在 C 代码里调用了 __android_log_print却忘了链接 log 库链接阶段就会报 undefined reference这是新手最容易踩的坑。3.4 直接调用 clang 交叉编译最透明也最灵活前面两种方式都依赖构建系统但有些场景你就只想快速编一个 .so 出来给别的项目用没必要把 CMake 或 ndk-build 那套全拉起来。这时候可以直接调用 NDK 里的 Clang 编译器命令反而最直观。假设我要把 demo.cpp 编成 arm64-v8a 的动态库完整命令如下export TOOLCHAIN$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/linux-x86_64 export API34 $TOOLCHAIN/bin/aarch64-linux-android34-clang \ -fPIC \ -shared \ -o libdemo.so \ demo.cpp注意编译器名字里的 aarch64-linux-android34 不是随便拼的它把目标 CPU 架构和 API Level 都编码进了命令里编译器会自动带出对应的 sysroot 和平台库。如果你用 aarch64-linux-android-clang 不带数字的版本通常也能编过但链接时默认 API Level 可能不是你想要的。 -fPIC 参数必须加它表示生成位置无关代码这是动态库的基本要求不加的话链接阶段会报 relocation 相关的错误。这种直接调 clang 的方式也适合排查编译问题因为每一步做了什么非常透明不像 gradle 构建那样包了一层又一层。3.5 把 NDK 配进 Gradle本地工程和命令行都通用接下来的问题是如何让 Gradle 找到这套手动安装的 NDK。两个地方要配置。第一个是项目根目录下的 local.properties手动写入 SDK 和 NDK 路径sdk.dir/home/你的用户名/Android/Sdk ndk.dir/home/你的用户名/Android/Sdk/ndk/android-ndk-r28c第二个是 app/build.gradle 里的 android 块指定 ndkVersion 和 CMake 配置android { compileSdk 34 ndkVersion 28.2.x // 以实际目录名为准 defaultConfig { externalNativeBuild { cmake { cppFlags -stdc17 abiFilters arm64-v8a, armeabi-v7a } } } externalNativeBuild { cmake { path file(src/main/cpp/CMakeLists.txt) } } }这里有个细节很多人不知道ndkVersion 写什么取决于 SDK 的 ndk 目录下到底放了什么。你可以这样查ls ~/Android/Sdk/ndk/如果目录名是 android-ndk-r28c那 Gradle 内部会用这个目录名去匹配版本。有时候你会在 ndk 目录下看到像是“28.2.12479011”这样带数字的目录名那是 Android Studio 自动下载时的命名方式两者都能被识别。我的建议是在 build.gradle 里写 ndkVersion 就写你在 ls 命令里看到的那个全名保证一致。abiFilters 的作用是只编译你真正需要的 ABI。如果只做真机测试保留 arm64-v8a 就够了能省下不少编译时间。4. 常见问题与排查技巧实录4.1 高频报错速查表NDK 编译的报错信息五花八门但归纳下来大部分都集中在几个固定场景。我把这些年遇到的高频问题整理成了表格方便大家快速定位。报错信息可能原因解决办法NDK not configured / ndk.dir is not specified没配置 local.properties 或 ndkVersion检查 sdk.dir、ndk.dir 路径是否正确Unable to locate NDKGradle 识别不了手动解压的 NDK 目录目录名保持 android-ndk-r28c 原样不要随意改名clang: error: unknown targetABI 拼写错误或编译器前缀不对对比官方 ABI 名称arm64-v8a、armeabi-v7aundefined reference to __android_log_print调用了 log 接口但没有链接 liblogfind_library(log-lib log) target_link_librariesCMake was unable to find a build program系统 cmake 缺失或版本过旧apt install cmake 或通过 SDK Manager 安装 CMakeUnsupported option --no-as-needed项目用了过旧的编译参数检查 Android.mk 里是否手动指定了废弃的链接参数library libxxx.so not found第三方 so 没有放进正确的 ABI 目录检查 jniLibs 里 ABI 目录是否匹配4.2 Linux 环境下特有的几个坑调试完报错信息再聊聊 Linux 环境下特别容易踩的坑这些坑换到 macOS 或 Windows 上不会出现但在 Linux 上一个个都真实发生在我身上过。第一个是权限问题。把 NDK 解压到 /opt 这类系统目录或者用 sudo 解压后续你在普通用户下编译时写 build 目录可能会失败。解决办法是把整个 NDK 目录 chown 给当前用户或者干脆解压到用户目录。第二个是路径问题。Linux 对大小写敏感jni 目录写成 JNI 或 Jni 都可能让 ndk-build 找不到源码路径。另外项目的绝对路径里不要带中文和空格CMake 解析这类路径时经常出一些怪异问题。第三个是宿主系统的 glibc 版本。新版 NDK 里编译器的运行依赖宿主系统 glibc如果你还在用很老的发行版系统比如某些长期不更新的服务器版本可能会遇到“cannot execute binary file”或“version GLIBCXX not found”的问题这种时候要么升级系统要么退回到兼容的 NDK 版本。第四个大坑是 SSH 环境下的“假死”。在无图形界面的服务器上跑 Android Studio 是不现实的但命令行编译完全可以。很多人拿到服务器第一反应是找安装向导其实只要把 JDK、SDK、NDK 装好用 gradle 命令或手动 cmake 都能正常产出 so 库。不要在服务器上试图启动 Android Studio 或者用 vnc 去看图形界面完全没必要。4.3 一次典型链接失败的完整排查过程这里分享一个我最近遇到的真实案例处理思路比问题本身更有参考价值。有个同事在 Linux CI 机器上编译一个 C 模块一直报undefined reference to __android_log_print编译阶段是没有问题的说明头文件找到了但链接接阶段找不到符号实现。先检查 CMakeLists.txt发现他确实只 add_library 了完全没有链接 log。这种问题用 NDK 工具链自带的 nm 或 readelf 就能验证比如对生成的动态库$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-nm -D libdemo.so | grep log看到 U 标记的 __android_log_print就说明这个符号还是未定义的它需要从 liblog.so 里解析。解决办法就是两行find_library(log-lib log) target_link_libraries(native-lib ${log-lib})整个排查过程不到十分钟。我想说的是NDK 报错看起来很吓人但思路其实就是沿着编译链、链接链一层层往下查先用编译命令缩小范围再用工具看符号表问题基本都能定位。4.4 顺手聊聊 OpenSSL、FFmpeg 这类第三方库的交叉编译很多 App 使用 NDK 并不仅仅是为了编译自己写的 JNI 接口还要把 OpenSSL、FFmpeg 这类大型第三方库编成 Android 可用的 .so。这类库一般都自带 configure 脚本交叉编译时需要显式指定编译器和 sysroot。我通常这样配置export TOOLCHAIN$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/linux-x86_64 export SYSROOT$TOOLCHAIN/sysroot export CC$TOOLCHAIN/bin/aarch64-linux-android34-clang export CXX$TOOLCHAIN/bin/aarch64-linux-android34-clang ./configure --hostaarch64-linux-android --with-sysroot$SYSROOT --prefix$PWD/build/arm64关键点是 --host 参数要写目标平台三元组--with-sysroot 则告诉 configure 脚本去哪里找 Android 的头文件和库。每个库的 configure 参数不一样但核心思路一致。遇到编译失败时先看 config.log 里到底卡在哪一步而不是盲目改参数。5. 最后分享几个我长期保留的 NDK 使用习惯写到这里整套 NDK 在 Linux 上的下载、配置、编译流程基本讲完了。最后说几个我个人在实际工作里觉得收益很高的操作习惯供参考。我一直把常用的 NDK 环境变量和编译器路径集中写在一个脚本文件里比如 ndk-env.sh然后在 ~/.bashrc 里 source 它。这样换机器、重装系统之后只要把这个脚本复制过去所有路径就都恢复了不用每次重新敲 export。另外每下载一个 NDK 版本我都会把 sha256 哈希值记下来放在项目目录的 README 里。这样做最大的好处是当 CI 环境要复现构建时下载完可以立刻校验文件完整性排除了“文件损坏导致编译报错”这个干扰项。还有一个经验是尽量别让构建脚本去猜 NDK 路径。无论是 Gradle 还是手动 CMake我都建议把所有路径显式传进去要么写进 local.properties要么在 CMake 命令行里直接传参数。宁可多打几个字符也不要依赖系统环境变量环境变量一旦在某个 CI worker 上没配好排查起来真的很费劲。最后再补充一个踩过无数次坑的细节编译产物命名一定要盯着。Android 的 System.loadLibrary 加载的是不带 lib 前缀和不带 .so 后缀的名字比如 libhello-jni.so 要写成 System.loadLibrary(hello-jni)。很多人编译没问题一运行就报 UnsatisfiedLinkError十有八九是把名字写错了。这个坑虽然小但每次遇到都要花一两小时确认希望你能一次避开。本文还有配套的精品资源点击获取