
示例工程移动开发【免费下载链接】ndk-samplesAndroid NDK samples with Android Studio项目地址https://gitcode.com/gh_mirrors/nd/ndk-samples点击查看免费下载导读本篇文章围绕 Android NDK Samples 仓库中的 hello-libs 示例展开讲解如何在 Android 应用中通过 CMake 导入预编译pre-compiled的第三方 C/C 静态库.a与动态库.so并打通从库的重新编译生成到应用链接调用的完整链路。读完本文你将掌握add_library(IMPORTED)、set_target_properties、target_include_directories的组合用法理解${ANDROID_ABI}在多 ABI 分发中的关键作用并能独立管理自己的预编译库分发目录。示例要解决的核心问题在真实的 Android 开发中团队或第三方供应商经常以只给二进制、不给源码的方式交付 C/C 库。此时你无法直接把这些库加入add_subdirectory参与源码编译而是需要告诉 CMake这些是已经编译好的外部库请按指定 ABI、指定路径链接进我的应用。hello-libs 示例 就是为此而生的最小可运行参考实现。它基于 CMake 的外部库支持external library support演示了如何把预先构建好的静态库gmath与动态库gperf引入应用并调用其中的函数。该示例明确声明本示例的主要目标是展示如何使用预编译库而不是如何构建它们见 hello-libs/README.md。因此预编译产物被直接放置于distribution目录下随仓库分发你无需任何额外步骤即可直接构建并运行 app 模块。示例的模块划分示例由两个 Gradle 模块组成见 hello-libs/README.mdapp消费方。从distribution目录导入共享库libgperf.so和静态库libgmath.a并调用其中的gpower()与GetTicks()函数gen-libs生产方。包含 gmath、gperf 两个库的源码与 CMake 构建脚本构建产物会被复制到distribution目录。gen-libs模块默认是禁用的在 settings.gradle 中include(:hello-libs:gen-libs)被注释在 app/build.gradle 中api project(:gen-libs)同样被注释因此它不会出现在 Android Studio 的 IDE 中避免干扰日常开发。只有当你需要重新生成库二进制时才按后文步骤临时启用它。预编译库的分发目录结构distribution目录是库分发的规范示例其布局遵循库名 → include / lib → ABI的层级hello-libs/distribution/ ├── gmath/ │ ├── include/gmath.h │ └── lib/ │ ├── arm64-v8a/libgmath.a │ ├── armeabi-v7a/libgmath.a │ ├── x86/libgmath.a │ └── x86_64/libgmath.a └── gperf/ ├── include/gperf.h └── lib/ ├── arm64-v8a/libgperf.so ├── armeabi-v7a/libgperf.so ├── x86/libgperf.so └── x86_64/libgperf.so两个库都覆盖了 Android 的四大主流 ABIarm64-v8a、armeabi-v7a、x86、x86_64。这种按 ABI 分子目录的组织方式与 Android Gradle Plugin 原生支持的多 ABI 打包机制一一对应是导入预编译库时应当遵循的标准做法。在 app 中导入预编译库核心实操导入预编译库需要在 app 的CMakeLists.txt中按固定顺序完成三件事见 hello-libs/README.md用add_library把库声明为静态或共享类型的IMPORTED用set_target_properties结合${ANDROID_ABI}配置每个库二进制的具体位置用target_include_directories配置每个库的头文件搜索路径。下面是 app/src/main/cpp/CMakeLists.txt 的完整实现cmake_minimum_required(VERSION 3.22.1) # configure import libs set(distribution_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../../../../distribution) add_library(lib_gmath STATIC IMPORTED) set_target_properties(lib_gmath PROPERTIES IMPORTED_LOCATION ${distribution_DIR}/gmath/lib/${ANDROID_ABI}/libgmath.a) add_library(lib_gperf SHARED IMPORTED) set_target_properties(lib_gperf PROPERTIES IMPORTED_LOCATION ${distribution_DIR}/gperf/lib/${ANDROID_ABI}/libgperf.so) # build applications shared lib set(CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS}) add_library(hello-libs SHARED hello-libs.cpp) target_include_directories(hello-libs PRIVATE ${distribution_DIR}/gmath/include ${distribution_DIR}/gperf/include) target_link_libraries(hello-libs android lib_gmath lib_gperf log)关键点解析add_library(... IMPORTED)IMPORTED关键字告诉 CMake 该 target 不是一个需要编译的目标而是指向一个已存在的二进制文件。STATIC对应.a归档文件SHARED对应.so共享库。IMPORTED_LOCATION与${ANDROID_ABI}IMPORTED_LOCATION属性指定库文件的完整路径。示例源码中特别注释道${ANDROID_ABI}对这个用途来说非常方便它可能是这个示例中最有价值的部分其余都是相当普通的 CMake 写法见 app/src/main/cpp/CMakeLists.txt。在 NDK 构建时CMake 会自动把ANDROID_ABI展开为当前正在构建的 ABI 名称如arm64-v8a从而自动定位到distribution目录下对应 ABI 的二进制。这意味着同一个 CMakeLists.txt 无需任何改动即可为多个 ABI 各取所需。target_include_directories为hello-libs目标添加头文件搜索路径。这里把 gmath、gperf 的include目录都加入源码中才能直接#include gmath.h、#include gperf.h。target_link_libraries把两个导入库链接进最终生成的libhello-libs.so。同时链接了android和log系统库——后者用于支持__android_log_print日志输出。导入库的命名约定注意一个细节分发目录中的二进制叫libgmath.a/libgperf.so而 CMake target 名被定义为lib_gmath/lib_gperf。CMake 的IMPORTED_LOCATION允许 target 名与真实文件名解耦你可以为库 target 起任意清晰的名字只要IMPORTED_LOCATION指向真实文件即可。这种命名解耦在多库、易混淆场景下非常实用。从 C 侧调用预编译库app 的 JNI 入口 hello-libs.cpp 展示了实际调用方式#include android/log.h #include gmath.h #include gperf.h #include jni.h extern C JNIEXPORT jlong JNICALL Java_com_example_hellolibs_MainActivity_measureTicks(JNIEnv *env, jobject thiz) { auto ticks GetTicks(); for (auto exp 0; exp 1000; exp) { volatile unsigned val gpower(exp); (void)val; // to silence compiler warning } ticks GetTicks() - ticks; LOGI(calculation time: % PRIu64, ticks); return ticks; }GetTicks()来自 gperf 库动态库返回当前系统时钟 tick 数gpower(n)来自 gmath 库静态库返回 2 的 n 次幂见 gmath.h。函数通过GetTicks()在循环前后各取一次时间戳测量执行 1000 次gpower调用所消耗的 tick 数并通过__android_log_print输出日志tag 为hello-libs::。两个库的头文件都使用了extern C包裹见 gmath.h 与 gperf.h确保在 C 编译环境下函数名不被名字修饰name mangling从而能被 JNI 层正确链接——这也是第三方 C 库在 C 工程中使用的常见必备手法。Java 侧触发MainActivity.java 与 hello-jni 示例的 Java 代码完全一致在静态代码块中System.loadLibrary(hello-libs)加载原生库然后通过native long measureTicks()声明并调用 JNI 函数把返回的 tick 数显示在界面上public class MainActivity extends AppCompatActivity { Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); TextView tv new TextView(this); tv.setText( The calculation took measureTicks() ticks ); setContentView(tv); } public native long measureTicks(); static { System.loadLibrary(hello-libs); } }这里演示了完整的调用链路Java → JNI →libhello-libs.so→ 链接自libgperf.so的GetTicks与静态链接自libgmath.a的gpower。静态库的代码在链接期直接并入libhello-libs.so动态库则在运行时由 Android 动态链接器按需加载。重新生成re-compile这两个库distribution目录里的二进制由gen-libs模块产出。虽然日常使用无需重新生成但如果你想了解这些库是如何构建并发布的或需要修改库源码可以按 hello-libs/README.md 的步骤操作在 settings.gradle 中启用include(:hello-libs:gen-libs)这一行在 app/build.gradle 中启用api project(:gen-libs)这一行构建项目在 Android Studio 中或命令行执行 Gradle 构建撤销前两步的修改清理临时构建文件rm -fr app/build app/.cxx。说明上述第 5 步中的rm命令来自原文档用于清理 Gradle/CMake 的临时构建产物。执行前请确认你确实处于该示例目录且路径正确。gen-libs 是如何产出分发产物的gen-libs的构建逻辑位于 gen-libs/src/main/cpp/CMakeLists.txt它把 gmath、gperf 作为子目录加入构建并将中间产物输出到用户主目录下的临时路径$ENV{HOME}/tmp/${ANDROID_ABI}避免污染项目目录。每个库的 CMake 脚本负责把产物复制到distributiongmath/CMakeLists.txt 通过ARCHIVE_OUTPUT_DIRECTORY把静态库libgmath.a输出到${distribution_DIR}/gmath/lib/${ANDROID_ABI}再通过add_custom_command(... POST_BUILD)把头文件复制到${distribution_DIR}/gmath/include/gmath.hgperf/CMakeLists.txt 通过LIBRARY_OUTPUT_DIRECTORY把共享库libgperf.so输出到${distribution_DIR}/gperf/lib/${ANDROID_ABI}同样以 POST_BUILD 命令复制头文件。ARCHIVE_OUTPUT_DIRECTORY与LIBRARY_OUTPUT_DIRECTORY分别控制静态库archive和共享库library的输出目录二者按库类型各取所需。构建完成后distribution目录便成为其他独立应用可直接导入的货架。源码注释还透露了一个实践细节见 gmath/CMakeLists.txt不能直接删除构建输出目录因为 Android Studio 检查 target 是否存在时会报错因此只保留distribution作为 app 的导入来源。与 Android Gradle 构建的衔接app 模块的构建配置集中在 app/build.gradleexternalNativeBuild.cmake.path指向src/main/cpp/CMakeLists.txt使 AGP 在构建时自动调用 CMakeexternalNativeBuild.cmake.arguments传入-DANDROID_STLc_static指定使用静态链接的 C 标准库避免动态 STL 带来的兼容性问题namespace与applicationId均为com.example.hellolibs与 JNI 函数Java_com_example_hellolibs_MainActivity_measureTicks的命名严格对应。整个构建链路可以概括为AGP 按defaultConfig声明的 ABI 依次调用 CMake → CMake 依据${ANDROID_ABI}从distribution选取对应架构的预编译库 → 链接进libhello-libs.so→ 打包进 APK。这也解释了为什么distribution必须为每个 ABI 都准备产物否则在对应架构设备上会因找不到库而构建失败或运行时崩溃。可移植到自有项目的清单基于本示例将任意预编译第三方库接入你自己的 Android NDK 项目时可按如下清单操作规划分发目录按库名/include与库名/lib/${ANDROID_ABI}组织头文件与二进制声明导入库静态库用add_library(name STATIC IMPORTED)共享库用add_library(name SHARED IMPORTED)指定二进制路径set_target_properties(name PROPERTIES IMPORTED_LOCATION .../${ANDROID_ABI}/libxxx.a)加入头文件路径target_include_directories(你的模块 PRIVATE .../include)链接target_link_libraries(你的模块 name)保证 ABI 全覆盖为所有需要支持的 ABI 提供对应二进制armeabi-v7a与arm64-v8a是移动设备的基本盘x86/x86_64常用于模拟器调试。总结hello-libs 示例用两个精心挑选的库一个静态、一个动态完整展示了 NDK 预编译库集成的三种 CMake 核心用法add_library(IMPORTED)声明、set_target_properties(IMPORTED_LOCATION)定位、target_include_directories引入头文件并以${ANDROID_ABI}实现了一次编写、多架构复用。同时gen-libs模块示范了库的构建与发布流水线让生产库与消费库两个环节在同一个仓库内形成闭环。对任何需要在 Android 应用中集成闭源 SDK 或内部预编译库的团队来说这套模式都是值得直接复用的标准范本。赞分享示例工程移动开发【免费下载链接】ndk-samplesAndroid NDK samples with Android Studio项目地址https://gitcode.com/gh_mirrors/nd/ndk-samples点击查看免费下载相关推荐如何在Android中链接预编译C/C库ndk-samples hello-libs静态库与动态库实战指南如何在Android中链接预编译C/C库ndk samples hello libs静态库与动态库实战指南 做 Android NDK 开发时你常常需要示例工程移动开发Apache Arrow C Minimal Build 示例深度解析从零编译到第三方项目集成Apache Arrow C Minimal Build 示例深度解析从零编译到第三方项目集成 本指南围绕 Apache Arrow 仓库中的 cpp/e数据工程数据分析大数据Signal-Android NDK应用C代码在Android中的集成Signal Android NDK应用C代码在Android中的集成 Signal作为一款注重隐私保护的即时通讯应用在Android平台上通过NDK上一篇GauAff CoT SFT Dataset与传统数据集对比结构化CoT推理如何提升AI模型性能下一篇10分钟掌握SpeechBrain超参数优化贝叶斯搜索与网格搜索终极指南创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考