news 2026/9/1 11:37:43

Linux下Android NDK r23b部署与编译实战:从zip到.so

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Linux下Android NDK r23b部署与编译实战:从zip到.so

简介:这是一份面向Linux平台的Android NDK r23b工具包,适合需要在Android应用中集成C/C++原生代码的开发者,常用于游戏渲染、图像处理、加密与高性能计算等对执行效率要求较高的场景。压缩包约691.53MB,内含编译器、链接器、静态/动态库以及构建调试工具链,可在Linux主机上直接完成面向Android设备的交叉编译。当前已有601人浏览学习,是NDK的常用版本之一。借助JNI,开发者可让Java层与本地代码高效互通,复用既有的C/C++库、压减APK体积并增强关键代码的安全性;同时也需留意原生层调试与内存管理的复杂度。该工具链还支持原生多线程,有助于在复杂任务中发挥多核优势,适合有一定C/C++基础、愿意深入底层优化的Android开发者使用。 如果你经常和 Android 原生开发打交道,android-ndk-r23b-linux.zip这个文件名大概率不陌生。它就是 Google 发布的 Android NDK r23b 针对 Linux 平台的官方压缩包,在 CI 构建机和本地开发机上都很常见。这篇文章不打算讲大而全的 NDK 入门,而是从部署、选型、编译验证到换版本时的坑,全部围绕这个 zip 展开。

很多人在 Linux 上装 NDK 时,习惯从 Android Studio 里点几下等下载,但真正到了服务器上搞持续集成、或者要给团队搭一套离线构建环境时,靠 IDE 图形界面是行不通的。手动下载这个 zip、解压、配环境变量、验证工具链,才是每个 Android/原生开发工程师都应该掌握的日常操作。下面按我自己的实际操作顺序来聊。

1. android-ndk-r23b-linux.zip:文件名里藏着的信息量

这个文件名乍看很长,但每个字段都很关键。android-ndk是 Google 官方 NDK(Native Development Kit)的标准前缀;r23b是版本号;linux表示宿主操作系统是 Linux x86_64;最后的.zip则是官方选择的打包格式, Windows 下的安装包是.exe,macOS 走的是.darwin-x86_64.zip,看到组合就知道该往哪里用了。

NDK 本身不是一个单一软件,而是一整套交叉编译工具链,包括编译器、链接器、系统库头文件、调试工具、构建脚本。Android 系统上跑的.so动态库,绝大多数是 NDK 编出来的,比如音视频解码、OpenGL 渲染、算法库、游戏引擎核心这类对性能敏感的原生代码。拿到这个压缩包后,Linux 机器就可以直接把 C/C++ 源码编译成 arm64-v8a、armeabi-v7a、x86、x86_64 这些 Android 设备架构可执行的二进制。

版本号r23b存在两套表达体系,这是最容易把人绕晕的地方。官方发布页和压缩包文件名里用的是r23b,但 Android Studio 的 SDK Manager 和很多构建工具里显示的是23.1.4579552。有人找半天找不到r23b,就是因为在 sdkmanager 里搜不到这个名字。只要记住r23b = 23.1.4579552,理解起来就顺了。顺带一提,r23c对应的是23.2.8568313,新人经常把 b 和 c 搞混。

从版本演进角度看,r23 系列在 NDK 历史上是一个标志性节点。r21 时代还保留着 GCC 过度期的影子,r22 开始全面转向 Clang,到 r23 则彻底移除了 GCC,同时移除了独立工具链部署脚本,默认链接器切换到 LLD。它不是一个测试版,而是一个“全面 Clang 化”之后相对稳定的大版本,所以被大量商业项目选作固定构建版本并不奇怪。

2. 为什么很多项目到现在还锁在 r23b

我在不少团队的项目配置里看到ndkVersion "23.1.4579552",包括一些对外发布的 SDK 也长期锁这个版本。最开始觉得不理解为啥不用最新的,后来自己参与的几个项目踩过新版本坑之后,反而理解了这种“保守”的合理性。

对比 r21 和 r23b,最直观的变化是 GCC 正式退出历史舞台。NDK r23 之后,arm-linux-androideabi-gcc这类命令彻底消失,所有编译统一走 Clang/LLVM 体系。对新工程来说这是好事,编译参数更统一,二进制体积也有优化;但对老项目来说却可能是一次“破坏性升级”,所以很多团队宁可停在熟悉的老版本链路上。r23b 正好卡在“旧时代尾声”和“新时代开始”之间,既能用上 Clang 默认链接器 LLD,也没有后续版本里那些更激进的行为变更。

再往后看 r25、r26 虽然能在新 API level 上提供更好的支持,但伴随的是 C++ 标准库行为更严格、编译器默认告警变成错误、不同线程模型下优化策略变化等隐性差异。一个维护期项目为了一个用不到的 API 特性去承受这些不确定性,性价比不高。特别是对外提供.aar.so给第三方集成的团队,NDK 版本一旦升级,编译出的二进制可能要在大量未知宿主 App 里运行,保持一个社区验证充分、反馈成熟的版本,比追新更稳妥。

还有一个值得提的点是 AGP(Android Gradle Plugin)的兼容性。每个 AGP 版本都会带一个默认 NDK 版本,但允许通过ndkVersion覆盖指定。AGP 7.x 以及后面多个主要版本与 r23b 的搭配都是经过大量项目验证过的。即使是在新版 Android Studio 里,只要 SDK 目录装了这个版本的 NDK,构建时指定ndkVersion "23.1.4579552"就能正常编译。当然,如果是全新项目,没有历史包袱,我建议直接用较新的稳定版本;但如果是进来维护现有工程,看到 r23b 锁着就别轻易动。

3. Linux 部署实操:从下载、解压到环境变量生效

拿到这个 zip 之后,好多人的第一反应是双击解压到随便哪个目录就开始用,但命令行环境里不养成固定习惯,后面配 CMake 和 Gradle 会非常闹心。我推荐按下面这个流程走一遍。

3.1 下载与校验

最简单的方式是直接用直链下载。NDK r23b 官方文件名就是android-ndk-r23b-linux.zip,在构建机上可以用wgetcurl拉取。如果内网有代理或镜像,优先走内部渠道,速度快也稳定。下载完一定不要跳过 SHA-256 校验,官方发布页提供了对应的 checksum,这能避免镜像污染或传输损坏导致的“编译时莫名其妙报错”。

wget https://dl.google.com/android/repository/android-ndk-r23b-linux.zip sha256sum android-ndk-r23b-linux.zip

把输出的哈希值和官方给出的值对一下,一致再继续。这一步在团队服务器上尤其重要,别嫌麻烦,我见过因为压缩包传输问题导致整个工具链不可用、排查半天最后发现是包损坏的情况。

3.2 解压目录规划

解压路径建议放在固定且有辨识度的位置,比如/opt/android-ndk-r23b或者~/Android/Sdk/ndk/23.1.4579552。如果是给单一用户用,放在用户目录下就行;如果是多构建机统一管理,建议统一放/opt下通过软链指过去。

这里有个细节:路径中不要出现空格和中文。NDK 里的脚本对路径比较敏感,空格会导致某些老式 Makefile 或 CMake 配置解析出问题。用unzip解压后,确认目录结构是android-ndk-r23b/,里面有toolchains/platforms/sources/build/等子目录。

3.3 环境变量配置

接下来设置环境变量。手动在命令行里跑 NDK 需要把工具链目录加进PATH,但更关键的是让 Gradle、脚本、IDE 能识别 NDK 的安装位置,所以要定义ANDROID_NDK_HOMEANDROID_NDK_ROOT

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

为什么两个变量都要设?因为不同工具读取的环境变量名不统一。有些老脚本、第三方构建插件读ANDROID_NDK_HOME,有些工具认ANDROID_NDK_ROOT,干脆都配上,省得后面报“NDK not found”。为了让这些变量永久生效,把它们写进~/.bashrc~/.zshrc,然后source一下。

验证是否生效:

ls $ANDROID_NDK_HOME/toolchains/llvm/prebuilt/linux-x86_64/bin/

能看到clangaarch64-linux-android21-clangllvm-stripllvm-readelf这些文件,就说明核心工具链已经就位。

3.4 与 Android Studio 的关系

如果你的开发机装的是 Android Studio,通常不需要手动下载这个 zip。打开 SDK Manager,在 SDK Tools 标签里勾选 NDK(Side by side),选择23.1.4579552版本即可。它会自动安装到 SDK 目录下的ndk/23.1.4579552子目录。这种情况下ANDROID_NDK_HOME不是必须的,因为 Gradle 会通过local.propertiessdk.dir推导出 NDK 位置。

不过我处理过的不少案例,都是在命令行和 IDE 混用的环境下出的问题。比如手动解压了一个 NDK,又同时在 IDE 里装了一个,两个版本不一致,构建时就被 Gradle 的ndkVersion指定搞崩溃。这时候建议以 SDK Manager 安装的版本为准,命令行单独下载的只给离线 CI 用,不要两边同时参与一个工程的构建。

4. 用 r23b 里的 clang 编译出第一个 Android 库

环境配好了,很多人会卡在“怎么证明这个 NDK 真的能用”。最直接的办法是写一段 JNI 代码,手动调 clang 编出一个.so

4.1 写一个最小的 JNI 源文件

新建一个add.c,内容如下:

#include <jni.h> JNIEXPORT jint JNICALL Java_com_example_add_AddHelper_add(JNIEnv *env, jobject thiz, jint a, jint b) { return a + b; }

这个函数名里的包名com.example.add和类名AddHelper要和你后续 Java/Kotlin 里System.loadLibrary的预期对应起来。

4.2 使用 NDK 里的 clang 编译

在 NDK r23b 中,编译 Android 目标平台的惯用命令是直接调用带 target 前缀的 clang wrapper:

$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android21-clang \ -shared -fPIC -o libadd.so add.c

这条命令做了几件事:aarch64-linux-android21-clang内部的 wrapper 自动设置了--target=aarch64-linux-android21,让编译器知道目标是 Android 的 64 位 ARM 架构,API level 是 21;-shared表示生成动态库;-fPIC生成位置无关代码,这是.so的基本要求。

如果你编的是 32 位 ARM 平台,把命令前缀换成armv7a-linux-androideabi21-clang即可。这里面的数字 21 是 minSdkVersion,可以按需调整;如果你的 App 最低只支持 Android 8.0(API 26),就改成 26。

4.3 检查生成的库

编完之后不要直接拿去用,先确认产物:

file libadd.so readelf -d libadd.so | head -20 nm -D libadd.so

file会显示这是一个 ELF 64-bit LSB shared object,ARM aarch64 架构;readelf能看 SONAME 和依赖库;nm -D用于确认 JNI 导出符号是否正确,能看到Java_com_example_add_AddHelper_add就说明导出没问题。

这个流程走通,说明 NDK 的编译器、sysroot、链接器、头文件路径都正常了。下面再扩散到真实工程,通常会在CMakeLists.txt里写好构建脚本,由 Gradle 调用 CMake 完成编译。手动 clang 命令主要是帮助你理解原理,排查问题时用得上。

5. 从 r21 换到 r23b 时,我踩过的几个坑

工具链升级最难受的不是安装,而是老工程在新编译器下出现的各种诡异问题。我把这几年给项目迁移 NDK 版本时遇到的经典坑列出来,按“现象 -> 原因 -> 解决办法”的顺序说,希望对你有用。

5.1 找不到 make-standalone-toolchain.sh

这是从老版本升到 r23b 时最容易撞上的第一个坑。r23 正式移除了独立工具链部署脚本,如果你以前的构建脚本是这么写的:

$NDK/build/tools/make-standalone-toolchain.sh --arch=arm64 --install-dir=/opt/my-toolchain

在新版本里会直接提示找不到文件或命令不存在。原因在于 NDK 官方认为直接使用 clang wrapper 就能完成同样的交叉编译,不再需要生成独立工具链目录。解决办法是改用前文提到的aarch64-linux-android21-clang这种调用方式,或者让项目里的 CMake/ndk-build 自动处理,别再手动做独立工具链。

5.2 老代码里的 GCC 专属编译参数直接报错

有些老项目从 GCC 时代留下来的 Makefile 或 CMake 配置里写了-fno-builtin-Wno-unused-but-set-variable-fno-strict-aliasing这类参数,在新版 Clang 里部分参数会变成“unsupported”直接报错。我之前遇到的一个项目编译时报clang: error: unknown argument: '-fno-expensive-optimizations',排查半天才发现是开发者从旧项目抄来的 CFLAGS。这种问题没有通用解法,只能顺着报错把不兼容的参数逐个删掉或替换成 Clang 支持的等价项。建议在迁移前先做一次参数清单审计。

5.3 APP_STL 不一致导致运行时找不到 libc++_shared.so

r23b 里默认 C++ 标准库是 libc++,而且为了支持多 ABI 共存,库在链接时可以选择静态或动态方式。如果你在Application.mk里设置了APP_STL := c++_shared,最终 APK 必须带上对应的libc++_shared.so,否则 App 运行到 JNI 调用时会直接崩溃,报dlopen failed: library "libc++_shared.so" not found

解决方法是两种:要么在 Gradle 里配置packagingOptions { jniLibs { useLegacyPackaging = true } }并在打包时把libc++_shared.so一起带进去;要么把多个 Native 库统一改成c++_static,把标准库静态链接进各自的.so,不过这样体积会变大。这里最怕的是部分模块用c++_shared、另一部分用c++_static,同一个进程里混用两套运行时,轻则告警,重则内存问题,属于比较隐蔽的坑。

5.4 ndkVersion 与 SDK 目录版本不匹配

在 Gradle 构建时出现过最“莫名其妙”的报错是:

NDK did not have a source.properties file

原因是命令行环境变量里的ANDROID_NDK_HOME指向了一个手动解压的 NDK,而 Gradle 项目里ndkVersion "23.1.4579552"又在 SDK 目录里找版本,两边不一致导致构建工具逻辑错乱。其实 NDK 包在source.properties中记录了Pkg.Revision = 23.1.4579552,如果文件缺失,说明解压包不完整或被手动改过。遇到这类问题,优先清理环境变量,或者统一把 NDK 放进 SDK 的ndk/目录下管理,别让两套路径并存。

5.5 老项目的 mips 架构编译失败

r23b 已经移除了 mips 和 mips64 架构支持。如果你的Application.mk里写了APP_ABI := all,构建脚本会自动枚举剩下支持的 ABI,一般不会报错;但如果是某个老构建流程硬编码了APP_ABI := armeabi armeabi-v7a mips,在新版本里必然找不到 mips 工具链。现在主流设备早就没有 mips 架构了,直接删掉这个 ABI 即可,不用有心理负担。

6. 多版本 NDK 共存与构建环境管理

接下来的内容可能超出“一个 zip”的范围,但实际工作中你大概率会碰上:一台机器上不同项目需要的 NDK 版本不一样。有的项目锁r21e,新的组件要求r25c,CI 上又不能每次重新解压替换,这时候怎么办?

比较好的做法是用 sdkmanager 把多个版本都装进 SDK 的ndk/目录。在终端执行:

sdkmanager --install "ndk;23.1.4579552" "ndk;25.2.9519653"

然后在项目级build.gradlebuild.gradle.kts里指定:

android { ndkVersion "23.1.4579552" }

Gradle 在构建时会自动从 SDK 目录的ndk子目录里找对应版本,不需要你手动切换环境变量。这样做的好处是整个工具链由 SDK Manager 统一管理,.source.properties、符号链接、版本记录都不会乱。团队协作时,只要每个人都安装了对应版本,构建环境就是可复现的。

对于 CI 服务器,我的建议是直接在初始化的 shell 脚本里固定写上sdkmanager --install "ndk;23.1.4579552",不要用“手动解压到某个目录再软链”的方式。一方面 sdkmanager 会处理目录细节,另一方面后续脚本可以统一判断“版本是否已安装”,而不是盯着一个裸目录发呆。配合 Docker 镜像把 NDK 装好再跑构建,也是同一套思路,只是把安装步骤固化到镜像层里。

我见过不少团队用“把 NDK 整个目录打进 Git”或“用 rsync 推到构建机根目录”这种方案,短期能用,但只要版本一多,维护成本立刻上来了。版本目录化管理,虽然是多花了几分钟配置环境变量,后面省下的排查时间远不止这几分钟。

7. 最后说点实际操作中的心得

我在 Linux 下用 NDK 也有几年时间,从当初在 Android Studio 里点按钮,到自己写脚本跑 CI 构建,最大的感受是:NDK 版本管理这件事,越早规范化,后面的麻烦越少。android-ndk-r23b-linux.zip这个包本身没什么特别的,但围绕它展开的部署方式、环境变量设置、版本锁定策略,才是真正影响团队效率的地方。

如果你刚开始在 Linux 上配置 NDK,我建议先把第 3 节的操作完整做一遍,手动编译一次 JNI 库验证链路,再接入 Gradle 构建。这个验证步骤看起来简单,但能把“环境问题”和“代码问题”明确隔离开。等手里有几个项目同时维护时,再按照第 6 节的方式用 sdkmanager 管多个版本,基本上就能应付绝大多数情况了。

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

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

超薄嵌入式十字四门冰箱选购避坑:尺寸、风道、温控全解析

最近帮家里物色新冰箱&#xff0c;才意识到志高&#xff08;CHIGO&#xff09;这类超薄嵌入式十字四门冰箱的门道&#xff0c;比想象中多得多。表面看是“容量够大、厚度够薄、风冷无霜、一级能效”几个关键词&#xff0c;但真正决定它好不好用的&#xff0c;反而是那些写在说明…

作者头像 李华
网站建设 2026/9/1 11:35:29

技术选型两难:从沉没成本到止损成本的决策框架

最近看到一场很有意思的辩论赛&#xff0c;辩题是“爱到深处&#xff0c;步步是苦&#xff0c;更应该‘一往而深’还是‘回头是岸’”。乍一看这是情感话题&#xff0c;但做技术的人读到这个题目&#xff0c;很容易产生一种强烈的既视感——这不就是每次技术选型走到十字路口时…

作者头像 李华
网站建设 2026/9/1 11:32:20

2026 私有云 ITSM 行业趋势与 FAQ 实战问答

解读私有云分布式 ITSM 三大行业趋势&#xff0c;结合国内厂商 ServiceHot 的落地实践&#xff0c;整理高频实操问题&#xff0c;帮助企业落地前理清认知误区。2026 私有云 ITSM 三大趋势1. 数据主权驱动&#xff0c;远离纯公有 SaaS&#xff1a;越来越多企业出于合规要求&…

作者头像 李华
网站建设 2026/9/1 11:31:19

Excel筛选功能全解析:从简单筛选到高级多条件查询

在日常数据处理中&#xff0c;Excel的筛选功能是使用频率最高的工具之一。无论是整理销售数据、分析客户信息&#xff0c;还是筛选简历、核对库存&#xff0c;我们都需要从海量数据中快速找到目标记录。然而&#xff0c;很多朋友对筛选功能的认知还停留在“点一下筛选箭头&…

作者头像 李华
网站建设 2026/9/1 11:25:52

Rails Pulse:可定位到方法级的请求性能监控与追踪工具

先交代一个常见场景&#xff1a;Rails 应用上线之后&#xff0c;页面偶尔变慢&#xff0c;接口时不时超时&#xff0c;生产环境日志翻了几页&#xff0c;MySQL 慢查询也开了&#xff0c;Redis 也盯了&#xff0c;但问题还是若隐若现。你大概知道瓶颈不在某一条 SQL 上&#xff…

作者头像 李华