简介:Android NDK r28c 是面向 Android 原生开发者的官方工具集,专为 Windows 64 位平台构建,适用于需通过 C/C++ 编写高性能模块(如图像处理、音视频编解码、AI 推理引擎)的中高级开发者。本资源完整封装了 NDK 核心头文件、类型定义与平台扩展接口,涵盖 Camera 元数据标签、Neural Networks API、OpenGL ES 扩展、Unicode 字符处理及 Linux V4L2 控制等关键能力,可直接集成至 Android Studio 或自定义构建流程。压缩包含 2000 个文件,主体为 1977 个 .h 头文件(提供跨平台原生接口声明),辅以 11 个 Python 脚本(用于构建自动化与工具链管理)、10 个 Markdown 文档(含 API 说明与迁移指南)、1 个 PDF 官方参考手册及基础文本配置文件,总大小 713.46MB,目录结构规范,便于按功能模块快速定位。已有 176 人下载学习,是搭建 Android 原生开发环境、理解底层系统交互机制及开展跨架构移植工作的可靠基础组件。 最近要给一个老项目适配新机器,我从头下载了android-ndk-r28c-windows.zip这个 Windows 版 NDK 压缩包。说实话,很多人拿到这个 zip 后的第一反应是:解压完往那一放,结果 Android Studio 要么报“NDK not configured”,要么就是ndkVersion对不上,然后开始莫名其妙地联网重下。这篇就把我从下载、解压、配置、跑通命令行编译,再到踩了一堆 Windows 特有坑的完整过程写出来。适合第一次接触 NDK 的移动端开发者,也适合正在从 r26、r27 往上升级的同学做参考。
1. 版本号里的门道:r28c 到底是哪个版本、和 r27、r26 差在哪
1.1 从文件名拆出来的有效信息
大部分 Android 开发者看到android-ndk-r28c-windows.zip,只把它当成一个“下载链接里的尾巴”,其实这个文件名已经把最关键的信息写清楚了。
拆开看就是四段:
android-ndk:这是 Android Native Development Kit 的标准前缀。r28c:主版本号是 28,c 是 r28 的第三次修订包。windows:运行平台,对应 Windows x86_64。zip:免安装压缩包,解压即用。
这个命名规则和 Linux、macOS 上的 NDK 包是一致的,比如 Linux 版本会叫android-ndk-r28c-linux.zip,macOS 会叫android-ndk-r28c-darwin.dmg或者darwin.zip。所以你在不同平台的 CI 机器上拉取同一套工具链时,只要把文件名里的平台字段换掉就行,版本号里面的r28c完全不用动。
r28c这里的c是“revision c”的意思。Google 对 NDK 的版本管理基本是:大版本号加小写字母修订号。比如r27a、r27b、r28c。字母越往后,表示在这个大版本里修复了越多重要问题。换句话说,同一大版本下,能选c就不要选a,因为后面带的都是实打实的 bugfix,特别是 Windows 平台上的路径、长文件名、符号链接问题,往往都是在小版本修订里才被磨平的。
1.2 r28 的工具链变化,直接影响老项目迁移
r28 这个版本对老项目的直接冲击,主要集中在工具链和最低 API 级别上。
从工具链角度看,NDK 早就全面转到 Clang/LLVM 了,GCC 相关的交叉工具在 r18 之后就逐渐退出,r28 里面你根本找不到arm-linux-androideabi-gcc这类文件。如果你的项目还在用ndk-build,并且依赖一些老旧的 GCC 参数,那升级之后大概率会直接报“unknown argument”或者“unable to execute command”之类的错误。
从 API 级别看,新版 NDK 对minSdkVersion的要求也在往上抬。老项目里常见的APP_PLATFORM := android-16或-DANDROID_PLATFORM=android-16这种配置,在较新的 NDK 上可能已经直接不被支持,编译时会被工具链拒绝。所以 r28 里你需要重新检查Application.mk和 CMake 里的ANDROID_PLATFORM,把它提升到一个合理的水平。具体每个大版本支持的最低 API 是多少,以source.properties或官方发布说明为准,千万别想当然用老版本的老参数。
1.3 为什么我坚持用 zip 而不是安装器
Windows 上 NDK 官方给过两种发布形态:zip 压缩包和安装器 exe。安装器会把 NDK 塞进你指定的 SDK 目录,或者默认放到C:\Android\android-sdk\ndk\...下,好处是省事,坏处是它帮你做了一些目录管理和版本绑定的决定,一旦你后面想换版本或者迁移到其他机器,反而没那么透明。
zip 包的优势在于:
- 不写注册表,不污染系统。
- 解压后可以放在任意干净路径,想留几个版本就留几个版本。
- CI 环境里只要把 zip 下载、解压、设置环境变量,三步完成。
- 打包到本地缓存方便拷贝,不用每次都跑联网下载。
所以我个人更推荐直接把 zip 当作一个绿色工具链来管理。接下来要讲的就是这种“绿色部署”的完整流程。
2. Windows 部署的完整姿势:解压位置、环境变量与验证命令
2.1 解压前先想清楚三件事
我吃过不少亏,现在每次解压 NDK 之前都会先确认三件事:
第一,路径里不要有空格。很多人喜欢解压到C:\Program Files\Android\...,这个位置在 Android Studio 里一般没问题,但当你打开命令行手动敲 CMake 命令或者 ndk-build 时,空格和引号问题会非常折磨人。我的建议是直接放在C:\Android\NDK\android-ndk-r28c这种纯英文、无空格的路径下。
第二,路径里不要有中文和特殊符号。这不是玄学,而是 Windows 下不同的终端编码环境对中文路径的处理方式不一致。如果你用 PowerShell 配置环境变量,用 CMD 跑编译脚本,很可能同一个中文路径在一种环境里正常,在另一种环境里就出现字节错乱。为了省事,从源头避开。
第三,整个路径长度不要拉太长。Windows 的经典路径长度限制是 260 个字符,NDK 工具链内部嵌套很深,比如toolchains\llvm\prebuilt\windows-x86_64\bin\clang.exe,如果你把 NDK 解压到一层套一层很深的目录里,文件路径很容易逼近限制,到时候编译报莫名其妙的 “File name too long” 你就知道后悔药不够用了。
2.2 环境变量配置:ANDROID_NDK_HOME 和 PATH 到底怎么设
解压完成之后,环境变量是让命令行工具能快速找到 NDK 的关键。虽然 Android Studio 有自己的一套 SDK 位置管理逻辑,但命令行场景下,没有正确设置环境变量会经常出问题。
我一般会设置一个用户级别的环境变量ANDROID_NDK_HOME,指向 NDK 解压后的根目录:
[Environment]::SetEnvironmentVariable("ANDROID_NDK_HOME", "C:\Android\NDK\android-ndk-r28c", "User")这个变量的作用是给ndk-build.cmd、CMake 工具链脚本,以及一些第三方构建工具提供统一的 NDK 入口。
另外一个常见变量是ANDROID_NDK_ROOT。在 CMake 的安卓工具链里,有些脚本仍会尝试读取ANDROID_NDK_ROOT。如果你的构建工具比较新,基本都认ANDROID_NDK_HOME,但为了兼容起见,我建议两个变量都设成同一个目录:
[Environment]::SetEnvironmentVariable("ANDROID_NDK_ROOT", "C:\Android\NDK\android-ndk-r28c", "User")至于PATH,我不建议把 NDK 根目录加进去,因为 NDK 根目录下的可执行文件其实不多,而且可能会和你系统里其他同名工具冲突。真正有用的目录是编译工具链所在的:
C:\Android\NDK\android-ndk-r28c\toolchains\llvm\prebuilt\windows-x86_64\bin把这个路径加到PATH里,你就能直接在终端里敲clang、clang++、llvm-ar这些命令来验证工具链是否可用。
用 PowerShell 加 PATH 的话,要稍微绕一下,因为 PATH 本身是一个分号分隔的字符串:
$oldPath = [Environment]::GetEnvironmentVariable("Path", "User") $newPath = "C:\Android\NDK\android-ndk-r28c\toolchains\llvm\prebuilt\windows-x86_64\bin;" + $oldPath [Environment]::SetEnvironmentVariable("Path", $newPath, "User")注意,修改完环境变量后,最好新开一个终端窗口再验证,因为已经打开的窗口不会自动刷新环境变量。
2.3 验证部署是否成功
配置完成后,第一时间做验证。打开一个新的 PowerShell 窗口,执行:
& "$env:ANDROID_NDK_HOME\toolchains\llvm\prebuilt\windows-x86_64\bin\clang.exe" --version如果能看到类似clang version 18.x.x的输出,说明工具链已经可以正常运行了。
同时也检查一下source.properties,这个文件在 NDK 根目录下,记录了当前包的完整版本信息:
Get-Content "$env:ANDROID_NDK_HOME\source.properties"你应该能看到Pkg.Revision = 28.x.x.x这样的一串数字,这个数字非常关键,后面 Android Studio 识别 NDK 版本时用的就是它。
3. Android Studio 里的正确接法:Gradle 的 ndkVersion 与 SDK 目录结构
3.1 Android Studio 为什么总说“找不到 NDK”
很多人的疼点在于:明明我从官网下载了 zip,解压了,环境变量也设了,可 Android Studio 的 Gradle 构建还是报错:
NDK not configured或者:
NDK did not have a source.properties file这种情况十有八九是因为你把 zip 解压到了任意的目录,但 Android Studio 并不会去全局搜索你的ANDROID_NDK_HOME。它自己有固定的 SDK 扫描逻辑:Android SDK 目录下的ndk文件夹里,每一个子文件夹代表一个 NDK 版本,文件夹的名称必须和该版本的版本号对应。
也就是说,Android Studio 期望目录长这样:
<你的SDK目录>\ndk\28.x.x.x\如果你直接解压得到android-ndk-r28c文件夹,还把它放到了 SDK 的ndk目录下,但不重命名成28.x.x.x,AS 就会认为这个 NDK 没有正确的source.properties,从而拒认。
正确做法是:
- 打开 NDK 根目录的
source.properties。 - 记下
Pkg.Revision字段,比如它可能是28.0.12916984。 - 把整个文件夹改名为
28.0.12916984,并放到<SDK>\ndk\下。
完成之后重新打开项目,Gradle 就能识别到这个 NDK 了。
3.2 build.gradle 里的 ndkVersion 和 externalNativeBuild
现在标准的做法是在模块的build.gradle里显式声明ndkVersion,这样即使同一个 SDK 里装了多套 NDK,构建系统也知道该用哪一套。
android { compileSdk 34 ndkVersion "28.0.12916984" // 这里必须和 source.properties 里的 Pkg.Revision 一致 }我见过不少朋友在这个字段上踩坑:网上复制了一个ndkVersion "25.0.xxxx",但本地其实没有那个版本,Gradle 就会去下载,下载失败后就报错。正确思路永远是打开你本机<SDK>\ndk\目录,看一眼里面实际有哪些版本,再决定ndkVersion写什么。
如果你的项目涉及 C/C++ 代码,还会同时看到externalNativeBuild配置:
android { defaultConfig { externalNativeBuild { cmake { cppFlags "-std=c++17" arguments "-DANDROID_STL=c++_shared" } } } externalNativeBuild { cmake { path "src/main/cpp/CMakeLists.txt" } } }这里面的ANDROID_STL=c++_shared值得多提一句。老项目可能习惯了gnustl_shared或stlport,但这些早就被移除了。r28 上你只能选择c++_shared或c++_static,前者把 C++ 运行库打成独立的.so,包会变大,但多个 so 之间不会重复包含同一份运行时;后者把运行库静态编进你的 so 里,包小,但多个 so 同时使用时容易撞符号。默认选c++_shared在多数场景下比较稳妥。
3.3 通过 SDK Manager 装过的 NDK 和这个 zip 有什么区别
SDK Manager 安装 NDK 本质上做的事也是解压,只是它帮你把目录放对了,并且可以在项目构建时自动下载缺的版本。
如果你已经下载了android-ndk-r28c-windows.zip,就没必要再让 SDK Manager 下载一遍同样的东西。你可以用前文提到的重命名方法,把这个 zip 放进 SDK 的ndk目录,然后把项目里ndkVersion写成对应的版本号。这样 Android Studio 检测到本地已经有这个 NDK 时,就不会再执行联网下载了。
离线环境下这种“带包部署”的方式尤其好用。比如内网开发机上没法直接访问 Google 的 SDK 仓库,那我就会提前在其他机器上下载好对应版本的android-ndk-r*.zip,拷贝到内网开发机,手动解压到 SDK 的ndk目录,自动构建就能跑起来。
4. 不打开 IDE 的命令行编译:最小 JNI 示例跑通 NDK
4.1 用 ndk-build.cmd 构建一个 libhello.so
配置好环境变量后,我们直接从命令行验证整个工具链。这里用一个最朴素的 JNI 示例,连 Android Studio 都不用打开。
先创建一个hello.c,代码就做一件事:返回一个字符串给 Java 层。
#include <jni.h> JNIEXPORT jstring JNICALL Java_com_example_ndktest_MainActivity_stringFromJNI(JNIEnv *env, jobject thiz) { return (*env)->NewStringUTF(env, "Hello from NDK r28c"); }接着创建Android.mk:
LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE := hello LOCAL_SRC_FILES := hello.c include $(BUILD_SHARED_LIBRARY)以及Application.mk:
APP_ABI := arm64-v8a x86_64 APP_PLATFORM := android-21然后在项目根目录执行:
C:\Android\NDK\android-ndk-r28c\ndk-build.cmd NDK_PROJECT_PATH=. APP_BUILD_SCRIPT=jni/Android.mk NDK_APPLICATION_MK=jni/Application.mk注意这里要把目录结构摆成标准 NDK 工程形态:hello.c和Android.mk、Application.mk放在同级的jni子目录下,否则ndk-build脚本的默认目录扫描会跟你捉迷藏。
编译成功后,你会在libs/arm64-v8a/libhello.so和libs/x86_64/libhello.so下看到产物。这个产物就是可以打包进 APK 的原生库。
4.2 用 CMake 工具链文件构建
除了ndk-build,现在大多数新项目用的是 CMake。NDK 自带的 CMake 工具链文件位于:
C:\Android\NDK\android-ndk-r28c\build\cmake\android.toolchain.cmake写一个CMakeLists.txt:
cmake_minimum_required(VERSION 3.22.1) project(hello C) add_library(hello SHARED hello.c)然后执行:
cmake -S . -B build -G Ninja ^ -DCMAKE_TOOLCHAIN_FILE=C:\Android\NDK\android-ndk-r28c\build\cmake\android.toolchain.cmake ^ -DANDROID_ABI=arm64-v8a ^ -DANDROID_PLATFORM=android-21 ^ -DANDROID_STL=c++_shared这里有几个参数值得解释:
ANDROID_ABI:指定目标 ABI,默认是arm64-v8a。你可以用armeabi-v7a、x86、x86_64,但要确认当前 NDK 版本对这些 ABI 的支持情况。ANDROID_PLATFORM:指定android-XX的 API 级别,这决定了最低支持的 Android 版本。ANDROID_STL:指定 C++ 标准库的链接方式。
CMake 构建完成后,产物在build\arm64-v8a\libhello.so目录下。
4.3 验证产物的 ABI 和链接情况
生成的.so是否真的可以放进 APK,有一个快速检查方法:用 NDK 自带的llvm-readelf或者llvm-objdump查看 ELF 头。
比如:
C:\Android\NDK\android-ndk-r28c\toolchains\llvm\prebuilt\windows-x86_64\bin\llvm-readelf.exe -h libs\arm64-v8a\libhello.so输出里会明确显示Machine: AArch64,说明这个 so 是给 arm64 用的。如果发现 Machine 是x86_64,那说明你构建参数里的 ABI 选错了,这个包打进 APK 后会在某些设备上出现Library not found的运行时崩溃。
这里的核心思路是:不要只看文件名和后缀,要去看 ELF 的真实架构,尤其是当你同时用命令行和 Android Studio 混合构建时,很容易构建出和预期 ABI 不一致的产物。
5. Windows 上几个高频坑的完整排查链路
5.1 路径含空格或中文,导致 clang 在编译中段诡异退出
我有一台工作机,用户目录是中文名,项目也放在D:\My Projects\...路径下。第一次跑 NDK 编译时,报错信息五花八门,最开始是:
clang++.exe: error: unable to execute command: Program not executable这个报错说得不清不楚。我第一个反应是 clang 没有执行权限,但 Windows 哪来的执行权限问题?后来我单独在命令行里运行:
"C:\Android\NDK\android-ndk-r28c\toolchains\llvm\prebuilt\windows-x86_64\bin\clang.exe" --version这个能正常运行,说明工具链本身没问题。问题一定出在项目路径或 NDK 路径的解析上。
接着我把构建命令中的路径用引号包住,还是失败。最后把整个项目复制到C:\tmp\ndktest,并且保证 NDK 也放在无空格无中文的路径下,再跑一次,编译通过。
排查结论:NDK 的 Make 系统和 CMake 脚本在 Windows 下对路径中的空格处理仍然不完美。C:\Program Files这种短路径往往没问题,但一旦嵌套很深,再加上中文用户名,极容易触发编码或路径截断问题。所以在这类问题上别想着修代码,直接挪位置最省事。
5.2 ndkVersion 和本地实际版本对不上,Gradle 反复联网下载
另一个高频坑是:Android Studio 项目里写的ndkVersion是别人的版本号,比如网上教程里是25.1.8937393,但你本地只有 r28c,Gradle 就会在构建时自动去找25.1.8937393并且尝试下载。如果网络环境不理想,下载失败两次后,构建就失败了。
排查链路是这样的:
- 先看构建输出里
NDK相关日志,是不是在下载ndkVersion。 - 打开项目根目录的
local.properties,看sdk.dir指向哪个目录。 - 在这个 SDK 目录下的
ndk文件夹里,列出所有已安装的 NDK 版本。 - 把
build.gradle里的ndkVersion改成你在该目录里看到的那一长串数字。
我当时写完这个版本号后,再同步检查source.properties里的Pkg.Revision,发现两者一致,构建立刻就不下载了。
注意:ndkVersion必须写类似于28.0.12916984这样的长数字,不能写r28c,也不要写28。这个版本号的格式是 Gradle 用来区分小版本的,少一段或多一段都会导致匹配失败。
5.3 老项目 API level 太低,r28 直接拒绝编译
从 r26 往上升级的项目,最容易碰到这个问题。我在验证一个旧模块时,Application.mk里写的是:
APP_PLATFORM := android-19编译时直接报:
error: Invalid platform 'android-19' in NDK这个报错不是随口说说的,而是新 NDK 工具链里的默认平台检查机制变严格了。解决方案是把android-19改成支持范围内的值,比如android-21或android-24。具体支持到几,以ndk-build或 CMake 的提示为准。
升级 API level 后要连带检查代码里的旧 API 调用。比如__android_log_print这些老接口没动,但要留意一些已经废弃的 C 库函数,在新 NDK 里可能不再导出。这种问题一般会有编译链接时的 undefined reference 提示,排查起来比运行时崩溃要友好得多。
5.4 编译日志乱码和终端编码问题
Windows 下 NDK 输出乱码是一个很顽固的问题,尤其是在中文 Windows 系统上。CMD 默认的代码页可能是 936,而 NDK 工具链输出的 UTF-8 日志会被错误解码,出现一堆问号或方块字。
排查起来不复杂,但很烦。解决方案是在执行编译前,先把终端切换成 UTF-8:
chcp 65001PowerShell 用户可以在当前会话设置输出编码:
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8这一步主要解决“日志乱码”的显示问题,不会影响编译产物本身。但如果你发现编译和文件路径中涉及中文文件名,那就不是切换代码页能救回来的,只能回到最初的建议:所有路径全部用英文。
6. 配置完之后的检查清单,以及我的一些习惯
6.1 一分钟快速判断 NDK 状态是否正常
我每次在新机器上配置完 NDK,不会急着打开 Android Studio,而是先执行下面这组命令:
# 查看 NDK 环境变量 echo $env:ANDROID_NDK_HOME # 查看核心版本信息 Get-Content "$env:ANDROID_NDK_HOME\source.properties" | Select-String "Pkg.Revision" # 查看编译器版本 & "$env:ANDROID_NDK_HOME\toolchains\llvm\prebuilt\windows-x86_64\bin\clang.exe" --version三条命令都正常,基本可以放心交给 Android Studio。如果哪条没输出,就顺着对应章节排查。
6.2 多版本 NDK 共存管理心得
实际项目里,不同的历史分支可能锁定不同 NDK 版本,比如老分支用 r23,新分支用 r28。我现在的习惯是在 SDK 的ndk目录下保留多个版本目录:
C:\Android\sdk\ndk\23.2.8568313 C:\Android\sdk\ndk\28.0.12916984然后在每个项目里用ndkVersion锁定自己需要的版本。这样切分支时不会因为只有单一 NDK 版本而被迫改代码。
手动下载的 zip 包解压后,我会先把android-ndk-r28c这类目录重命名成版本号,再放进ndk目录。这个习惯帮我避免了很多“AS 不认目录”的尴尬。
6.3 给新项目的一点建议
如果你是从零开始搭建,不要直接把“最新 NDK”当成默认配置。先确认项目要支持的最低 Android 系统版本,再对照 NDK 发行说明选一个合适的版本。稳定优先,没必要追求永远追新。
另外,C/C++ 源码在 Windows 上使用 NDK 编译时,换行符和编码也会引发一些奇怪问题。建议 Git 仓库统一使用 LF 换行,.gitattributes里把*.c、*.h、*.cpp都声明成text eol=lf。这能避免代码在 Windows 和 Linux CI 之间来回切换时出现大量无意义的警告。
最后再分享一个我在多次踩坑后养成的习惯:下载完android-ndk-r28c-windows.zip后,立刻校验压缩包哈希,比如用 PowerShell 里的Get-FileHash,确认和官方页面给出的 SHA-256 一致再解压。Windows 环境里第三方下载源多,文件损坏、被篡改、下载不完整的情况远比想象中常见。等你编译到一半发现 clang 崩溃,那时候再去排查是 zip 的问题,成本就高多了。
本文还有配套的精品资源,点击获取