news 2026/9/11 8:41:33

Linux 下 Android NDK r25b 解压配置与 Gradle 交叉编译实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Linux 下 Android NDK r25b 解压配置与 Gradle 交叉编译实战

简介:android-ndk-r25b-linux.zip 是 Google 面向 Linux 平台发布的 Android 原生开发工具包(NDK)r25b 稳定版,核心服务对象是需要在 Android 工程中使用 C/C++ 完成高性能计算、图形处理、物理模拟,或复用现有 C/C++ 代码库的开发者。通过 JNI 桥接,开发者可在 Java/Kotlin 层与原生代码之间传递数据、调用方法,从而兼顾开发效率与运行性能。整个压缩包约 506.38MB,包含 2000 个文件,其中以 1917 个 .h 头文件为主,覆盖 ARM、x86 等不同 CPU 架构的系统 API 接口;同时有 57 个 Python 脚本、10 个 Markdown 文档、9 个文本文件以及若干构建脚本,可辅助完成环境配置、交叉编译、源码阅读与问题排查,目录结构清晰,便于按需查阅。该版本为官方稳定版,自带 GCC/Clang 编译工具链与构建系统,资源已有 992 人学习浏览,适合正在入门 JNI、希望理解原生内存管理、集成音视频与图像处理库,或计划优化应用性能的中高级 Android 开发者。借助这份资源,读者可以一次性获得完整的头文件体系、自动化脚本和说明文档,省去琐碎的下载配置步骤,在 Linux 环境下顺畅地生成 .so 动态库并接入工程。

1. 拿到 android-ndk-r25b-linux.zip,先别急着解压

很多人在 Linux 上做 Android 构建时,拿到 android-ndk-r25b-linux.zip 的第一个动作就是 unzip 到 /opt 里然后改 PATH。这个包确实是绿色版,解压即用,但真正让构建失败的往往不是解压动作本身,而是解压之后的路径关系:Gradle 靠什么找到 NDK、CMake 工具链文件在哪、ANDROID_NDK_HOMEPATH分别被谁读取。r25b 是 NDK 的 LTS 修订版,自带 Clang 14,对老工程的 CMake 脚本兼容性比后续几年频繁改动的版本要稳定,所以现在还有很多 CI 流水线和五年以上的 Android 工程指定使用这个 zip 包。下面按解压、环境变量、Gradle 接入、交叉编译、验证这条线来一步步说明,同时也是给初次维护 Linux 构建镜像的人一份可复现的检查单。

2. 在 Linux 上解压 android-ndk-r25b-linux.zip 并配置环境

解压前要先明确一件事:这个 zip 里的顶层目录是android-ndk-r25b,不是散装文件。也就是说,unzip -d /opt/android-sdk/ndk之后,实际路径是/opt/android-sdk/ndk/android-ndk-r25b,很多人后面配环境变量少写一层,导致编译时直接报Unable to locate aapt2之类的非标准错误。另外,zip 包体积不小,解压后会占用数 GB 磁盘空间,所以先用df -h确认分区剩余空间,再用df -i看 inode 是否充足,这在 Docker 镜像构建场景里尤其重要。

2.1 先校验 SHA-256,避免解压到一半报错

在 Linux 上下载这类 zip 包,最常见的问题是下载被代理或限速中断,文件少了尾部字节。zip 格式的文件如果缺少 End Of Central Directory 记录,unzip 会直接报invalid zip archive: could not find eocd。这个报错不是系统缺少解压工具,而是文件没下全。因此在解压之前先做一步校验:

sha256sum android-ndk-r25b-linux.zip

sha256sum会输出一个 64 位十六进制哈希,把它和 NDK 官方下载页给出的 SHA-256 做比对。如果比对一致再继续,不一致就直接重新下载,不要抱着“也许能用”的心态继续操作,因为 NDK 工具链里任何一个小文件损坏,都会在后续链接时表现出完全无关的符号错误。下载大文件时建议用支持断点续传的wget -c,即wget -c https://dl.google.com/android/repository/android-ndk-r25b-linux.zip -O android-ndk-r25b-linux.zip,避免中途丢包后从头再来。

2.2 规划安装目录和顶层目录结构

校验通过后,解压前可以先用unzip -l预览包内文件列表:

unzip -l android-ndk-r25b-linux.zip | head -20

输出第一行通常就是android-ndk-r25b/,后面跟着source.propertiesbuild/toolchains/。这一步不只是看目录名,还能确认这个 zip 包没有用-j参数压平,避免解压后一堆文件直接撒在目标目录里。确认后再正式解压:

mkdir -p /opt/android-sdk/ndk unzip -q android-ndk-r25b-linux.zip -d /opt/android-sdk/ndk

-q让解压静默输出,只保留错误信息;-d指定根目录。安装路径里不要有空格和中文,NDK 的 CMake 工具链文件对路径中的空格处理并不完善,Gradle 的 externalNativeBuild 遇到带空格路径时会生成奇怪的转义参数。解压完成后检查一下权限:

ls -ld /opt/android-sdk/ndk/android-ndk-r25b find /opt/android-sdk/ndk/android-ndk-r25b -type f -perm -111 | wc -l

第二行统计可执行文件数量,如果数量为 0,说明压缩包在传输过程中丢失了执行权限或文件模式,常见于从 Windows 共享目录复制出来的文件;这时用chmod -R +x补上全局执行位,但更建议返回到原始 zip 包重新解压,因为 zip 格式本身是会保存 Unix 权限位的。

2.3 配置 ANDROID_NDK_HOME 与 PATH 的先后顺序

解压完成后,配置环境变量。ANDROID_NDK_HOME是给 NDK 自带的ndk-build脚本和很多第三方构建系统读取的,PATH则是让终端直接能调用 clang 工具链。两者指向的位置不同,不要混写:

export ANDROID_NDK_HOME=/opt/android-sdk/ndk/android-ndk-r25b export PATH=$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/linux-x86_64/bin:$PATH

PATH没有直接指向$ANDROID_NDK_HOME,而是指向toolchains/llvm/prebuilt/linux-x86_64/bin,因为 NDK 在 Linux 下的编译器、链接器都打包在这个 prebuilt 目录里。后面手动交叉编译时,使用的 clang 包装器也全部在这里。把这两行写入~/.bashrc后执行source ~/.bashrc,再用一条命令确认:

环境变量推荐值被谁读取
ANDROID_NDK_HOME/opt/android-sdk/ndk/android-ndk-r25bndk-build、CMake 脚本
PATH/opt/android-sdk/ndk/android-ndk-r25b/toolchains/llvm/prebuilt/linux-x86_64/binshell、Gradle 外部进程
ANDROID_HOME/opt/android-sdkAGP、sdkmanager

接着执行$ANDROID_NDK_HOME/ndk-build --version。如果能看到GNU Make 3.81或类似输出,说明 NDK 主脚本没有缺少依赖;如果主要报错是/bin/bash: ...: Permission denied,回到上一节看权限。这里的常见误用是把变量名写成NDK_HOME,很多老脚本确实识别NDK_HOME,但 Android Gradle Plugin 认识的是ANDROID_NDK_HOMEANDROID_HOME,所以两个都设置也不冲突,只是不要依赖其中一个而不设置另一个。

3. 让 Gradle 和 AGP 识别 NDK r25b:local.properties 与符号链接

把环境变量配置好只是第一步,实际项目里通常不会手动跑 NDK 命令,而是通过 Android Gradle Plugin(AGP)在构建时自动调用 NDK。AGP 识别 NDK 有两套线索:一是local.properties里的sdk.dir,二是 AGP 自己记录的 SDK 目录下ndk子文件夹。如果只设置了ANDROID_NDK_HOME,没有把 NDK 放到 AGP 预期的位置,Gradle 会报NDK not configured或者提示下载某个特定版本。下面用 r25b 这个 zip 包来演示最稳妥的接入方式。

3.1 local.properties 里的 sdk.dir 决定查找起点

local.properties是 Gradle 工程根目录下的键值文件,不需要提交到 Git。CI 环境通常由脚本在构建前生成。第一行这样写:

sdk.dir=/opt/android-sdk

AGP 会在$sdk.dir/ndk目录下寻找 NDK,并且要求子目录名等于source.properties里的Pkg.Revision。比如 r25b 的Pkg.Revision25.1.8937393,那么 AGP 期望的目录就是/opt/android-sdk/ndk/25.1.8937393。这里和老版本习惯不一样,老项目里常见的ndk.dir=/opt/android-ndk-r25b写法在 AGP 7.0 之后逐渐失效,Android Studio 新版也不再生成ndk.dir字段,所以不建议继续依赖它。

除了local.properties,还可以在gradle.properties里用android.ndkDirectory指定绝对路径。这个属性对 AGP 仍然有效,适合不想动符号链接的场景。但缺点是它影响的是单个工程配置,多个工程共用时容易遗忘。下面推荐的方式是目录伪装,一次配置,所有工程都能用。

3.2 用符号链接把 NDK 伪装进 SDK 的 ndk 目录

在 Linux 上,最简单的做法是把解压出来的android-ndk-r25b目录软链成 AGP 期望的版本号目录名:

ln -s /opt/android-sdk/ndk/android-ndk-r25b /opt/android-sdk/ndk/25.1.8937393

执行前先查看source.properties确认Pkg.Revision

cat /opt/android-sdk/ndk/android-ndk-r25b/source.properties

输出里有Pkg.Revision=25.1.8937393,这个值必须和符号链接名严格一致。AGP 在构建时读取source.propertiesbuild.gradle里的ndkVersion,两个值不一致时不会去尝试模糊匹配,而是直接报版本不存在。所以 build.gradle 里这样写:

android { ndkVersion "25.1.8937393" compileSdk 33 }

如果项目里没有显式写ndkVersion,AGP 会使用它自己默认绑定的 NDK 版本,那可能就不是 r25b 了。因此,只要项目依赖的是这个 zip 包,就一定要显式声明ndkVersion。符号链接名和ndkVersion都对齐后,Gradle 的配置阶段就会认为 NDK 已经安装在 SDK 内部,不再走网络下载流程。

3.3 AGP 版本与 NDK r25b 的兼容边界

NDK r25b 对应 Clang 14,AGP 8.x 和 AGP 7.4 都支持它,但要注意 AGP 8.0 之后对ndk.dirandroid.ndkDirectory的兼容级别。下面这张表是实际维护中容易踩到的对应关系:

配置项AGP 7.4AGP 8.0+建议
ndkVersion支持支持必须写
local.properties 的 sdk.dir支持支持必须写
local.properties 的 ndk.dir支持忽略不要写
ANDROID_NDK_HOME部分场景部分场景当作补充
符号链接到 ndk/版本号支持支持推荐

这说明在 AGP 8.0+ 的环境里,ndk.dir会被静默忽略。如果你的 CI 脚本还在往local.properties里写ndk.dir=/opt/ndk/r25b,构建日志里看不到报错,但最终编译时会突然出现找不到libc++_shared.so或直接使用错误 NDK 的诡异现象。正确姿势是只维护sdk.dirndkVersion两个值,再配合符号链接,这样无论 Jenkins 还是 GitLab Runner,镜像重建后都能立刻跑起来。

4. NDK r25b 交叉编译实战:CMake 还是直接 clang

Gradle 能接入 NDK,最终执行交叉编译的还是 Clang 和 LLVM 工具链。NDK r25b 不再提供单独的arm-linux-androideabi-gcc,所有编译都走toolchains/llvm/prebuilt/linux-x86_64/bin下的 clang 包装器。这些包装器的命名规则是目标三元组 + API level + 编译器类型,其中一个常见误解是“NDK 里没有 gcc 所以不能做 C++ 编译”,实际上 clang 完全兼容 GNU 命令行参数,下面的命令在 r25b 上都可以直接跑。

4.1 用 CMake 工具链文件搭建交叉编译

CMake 是 NDK 官方推荐的构建方式,r25b 自带的工具链文件位于build/cmake/android.toolchain.cmake。创建一个小型 C++ 工程,源文件如下:

extern "C" int native_add(int a, int b) { return a + b; }

CMakeLists.txt 这样写:

cmake_minimum_required(VERSION 3.18.1) project(native_demo LANGUAGES C CXX) add_library(native SHARED native.cpp) target_link_libraries(native log android)

CMakeLists.txt 里LANGUAGES C CXX必须写,否则 NDK 工具链文件在project()阶段看不到 C++ 编译器,会退回用宿主的 g++。然后执行:

cmake -S . -B build \ -DCMAKE_TOOLCHAIN_FILE=$ANDROID_NDK_HOME/build/cmake/android.toolchain.cmake \ -DANDROID_ABI=arm64-v8a \ -DANDROID_PLATFORM=android-21 \ -DANDROID_STL=c++_shared cmake --build build

这里没有手动设置CMAKE_C_COMPILER,因为工具链文件会自己填好。ANDROID_ABI=arm64-v8a编译 64 位 ARM 指令集,ANDROID_PLATFORM=android-21通知编译器使用 API 21 的 sysroot。ANDROID_STL=c++_shared会链接libc++_shared.so,APK 里需要单独打入这个 so;改成c++_static则直接把 C++ 运行时静态编进产物,APK 体积会更大,但省去带 so 的麻烦。在 Android Studio 工程中,这段 CMake 由 Gradle 的externalNativeBuild触发,最简配置是:

android { defaultConfig { externalNativeBuild { cmake { cppFlags "-std=c++17" arguments "-DANDROID_STL=c++_shared" } } } externalNativeBuild { cmake { path "src/main/cpp/CMakeLists.txt" } } }

这样 Gradle 会使用 NDK 目录下的 CMake 工具链,不需要在build.gradle里重复声明ANDROID_NDK_HOME

4.2 直接调用 clang 编译一个 .so 的命令

不想引入 CMake 时,也可以直接使用 NDK 提供的 clang 包装器。比如编译上面那个函数,最简命令是:

ARM64_CLANG=$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android21-clang++ $ARM64_CLANG -shared -fPIC -std=c++17 \ native.cpp -o libnative.so -landroid -llog

aarch64-linux-android21-clang++这个包装器的名字里已经隐含了--target=aarch64-linux-android--sysroot,不需要再额外跟一长串参数。-shared告诉链接器产出动态库,-fPIC是 Android 平台上的硬性要求,不写的话后面链接到 app 进程时会报relocation R_AARCH64_ADR_PREL_PG_HI21一类错误。-landroid链接 Android 脚手架库,-llog提供__android_log_print,如果代码里没有用到日志可以不加。

4.3 ABI 与 API level 参数对应表

NDK r25b 支持四种 ABI,包装器前缀差别只在目标三元组。在 Linux 命令行里,API level 要写在三元组后面,并且 arm64 和 x86_64 必须使用android-21或更高,因为 64 位 Android 系统本身就是从 API 21 开始的。

ABIclang 包装器前缀最低 API常见 Gradle ABI 过滤
armeabi-v7aarmv7a-linux-androideabi21-clang++android-19armeabi-v7a
arm64-v8aaarch64-linux-android21-clang++android-21arm64-v8a
x86i686-linux-android21-clang++android-19x86
x86_64x86_64-linux-android21-clang++android-21x86_64

这里的21是示例 API level,如果工程 minSdkVersion 是 23,就把前缀里的21换成23。目录下确实存在多个 API 版本的包装器,直接用aarch64-linux-android23-clang++也可以。需要小心的是,armeabi-v7a的包装器不是arm-linux-androideabi,而是armv7a-linux-androideabi,写错的话终端会提示命令不存在。另外,libc++_shared.so 在sysroot/usr/lib/下的 ABI 子目录里,适配时不要搞混 arm64-v8a 和 armeabi-v7a 的库文件。

5. 验证与排错:从 zip 解压到 so 链接的自检清单

拿到 r25b 这个 zip 包,最后要确认的不只是编译通过,而是编译产物和 NDK 安装本身没被旧的缓存污染。这一套自检在 CI 里尤其值得写成一个脚本,每次换镜像后跑一遍。先检查 NDK 是否完整,再检查产物动态依赖,最后看 ELF 头。

5.1 解压层验证与 eocd 报错处理

如果解压时遇到invalid zip archive: could not find eocd,先别急着换 unzip 版本。这个报错的核心是 zip 包末尾没有End Of Central Directory记录,原因多数是文件没下载完。处理方法是重新下载,同时建议下载后补一个zip -T android-ndk-r25b-linux.zip测试完整性。zip -T会对包内每个文件做 CRC 检查,比 sha256 更快,适合解压前的二次确认。重新下载后如果仍然报错,再检查 zip 包所在分区是否被挂载为 noexec,虽然这不影响解压,但会让后续 clang 执行时直接回应Permission denied

5.2 检查 sysroot 和 C++ 运行时是否匹配

r25b 的 sysroot 在toolchains/llvm/prebuilt/linux-x86_64/sysroot,检查是否能找到目标 ABI 的头文件和运行时:

test -d $ANDROID_NDK_HOME/toolchains/llvm/prebuilt/linux-x86_64/sysroot && echo ok find $ANDROID_NDK_HOME/toolchains/llvm/prebuilt/linux-x86_64/sysroot -name "libc++_shared.so" | head -5

如果libc++_shared.so没有按 ABI 子目录出现,说明 zip 包解压不完整。这时候不要只重新解压一次,先确认磁盘空间是否足够,再删除旧目录。如果删除了旧目录后df -h显示空间没释放,用lsof +L1查看是否有进程仍占用旧 NDK 路径,常见于持续集成的 Gradle daemon 没有重启,杀掉 daemon 后空间才会回来。

5.3 用 readelf 判断编译产物是否真的跨平台

最后验证编译出来的 so:

readelf -d libnative.so | grep NEEDED readelf -h libnative.so | grep Machine

NEEDED行会列出动态依赖,期望看到liblog.solibc++_shared.so,如果出现libstdc++.so或 host 上的libgcc_s.so.1,说明 clang 包装器路径没设置对,编译器错误地用了宿主的 sysroot。Machine字段在 arm64-v8a 产物里应该是AArch64,在 armeabi-v7a 产物里应该是ARM,出现x86-64就说明 ABI 参数写错。这个检查比编译成功更有说服力,也是 NDK r25b 在 Linux 上从 zip 包到动态库最后一道容易忽略的关卡。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/11 8:40:57

大模型上下文模式实战:告别多轮对话失忆与token浪费

1. 上下文模式到底解决什么问题 先给你说个我自己的经历。去年有个同事拿着一个内部工具来找我,说AI老是在多轮对话里"失忆",前面聊得好好的,第三轮就开始答非所问。我问他用的什么模型、上下文怎么传的,他一脸茫然&…

作者头像 李华
网站建设 2026/9/11 8:37:44

AD9914 DDS配置全解析:从MCU到FPGA的移植实践

简介:基于亚德诺半导体 AD9914 直接数字频率合成器,这份 FPGA 驱动工程示例面向通信、测试测量与雷达系统开发人员,适合需要快速实现高精度频率和相位控制的场景。该芯片支持 3.5 GSPS 采样率与 12 位 DAC,驱动实现需兼顾高速接口…

作者头像 李华
网站建设 2026/9/11 8:36:24

Cal.diy 怎么配置 Twilio 发送短信验证码与通知

Cal.diy 怎么配置 Twilio 发送短信验证码与通知 【免费下载链接】cal.diy Scheduling infrastructure for absolutely everyone. 项目地址: https://gitcode.com/GitHub_Trending/ca/cal.diy 如果你部署了 Cal.diy(Scheduling infrastructure for absolutely…

作者头像 李华
网站建设 2026/9/11 8:34:22

8G显存离线数字人:Duix.Avatar免费部署完整指南

8G显存离线数字人:Duix.Avatar免费部署完整指南 【免费下载链接】Duix-Avatar 🚀 Truly open-source AI avatar(digital human) toolkit for offline video generation and digital human cloning. 项目地址: https://gitcode.com/GitHub_Trending/he/…

作者头像 李华
网站建设 2026/9/11 8:34:11

树莓派Pico USB-CDC与select实现非阻塞虚拟串口通信控制舵机

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华