第二十三章:安装与打包——从本地构建到远程交付
说实话,很多做 NDK 开发的朋友,写代码是一把好手,但一到「怎么把我的 .so 交给别人用」就卡壳了。我自己早年也踩过这个坑——编译好的库往同事那一扔,结果人家连头文件路径都找不到。后来我才意识到,安装与打包这件事,其实是你代码工程的「最后一公里」。
这一章,我们就来聊聊这「最后一公里」怎么走。从 CMake 的 install() 命令开始,到 CPack 打包,再到生成 AAR 发布到 Maven 仓库。嗯,一条龙讲清楚。
23.1 install() 命令:让构建产物各归其位
先问个问题:你编译出来的 .so、头文件、可执行文件,它们应该放在哪?
如果你只是在自己机器上调试,那无所谓。但如果你要交付给别人,或者集成到 Android Studio 项目里,那就必须有一套规范的「摆放规则」。CMake 的 install() 命令就是干这个的。
23.1.1 基本用法
语法其实很简单:
install(TARGETS target_name
RUNTIME DESTINATION bin
LIBRARY DESTINATION lib
ARCHIVE DESTINATION lib/static)
install(FILES header.h
DESTINATION include)
这里我解释一下:
- TARGETS:指定你要安装的目标(库或可执行文件)
- RUNTIME:可执行文件(Windows 下 .exe,Linux 下无后缀)
- LIBRARY:共享库(.so / .dylib)
- ARCHIVE:静态库(.a / .lib)
- FILES:头文件或其他资源
我在项目中遇到过一种情况:同事把 .so 和 .a 都扔到同一个目录,结果链接器总是优先找到静态库,导致运行时崩溃。后来我强制用 ARCHIVE DESTINATION 分开存放,问题就解决了。
23.1.2 在 Android NDK 中的特殊处理
Android 的 ABI 架构很多(arm64-v8a、armeabi-v7a、x86_64...),你不能一股脑全装到同一个目录。我个人的习惯是:
install(TARGETS my_native_lib
LIBRARY DESTINATION ${CMAKE_ANDROID_ARCH_ABI})
这样每个 ABI 的 .so 就会自动放到对应的文件夹里。你想想看,如果手动去分,那得多累。
install() 的目标路径通常是相对于 CMAKE_INSTALL_PREFIX 的。你可以通过 cmake --install . --prefix /some/path 来指定安装根目录。
23.2 CPack:一键打包成安装包
install() 只是把文件放好,但你要交付给用户,总不能让人家自己一个个拷贝吧?这时候 CPack 就登场了。
CPack 是 CMake 自带的打包工具。它能把你安装好的文件打包成各种格式:
- Linux:DEB、RPM、TGZ
- macOS:DMG、Bundle
- Windows:NSIS、WiX
配置起来也很简单,在 CMakeLists.txt 里加上:
include(CPack)
set(CPACK_PACKAGE_NAME "MyNativeLib")
set(CPACK_PACKAGE_VERSION "1.0.0")
set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "A native library for Android")
set(CPACK_GENERATOR "TGZ") # 先打个 tar.gz 试试
然后执行:
cmake --build . --target install
cpack
你就会得到一个 MyNativeLib-1.0.0-Linux.tar.gz。打开看看,里面的目录结构跟你 install 时定义的一模一样。
23.3 生成 AAR 包:Android 原生库的标准交付物
如果你做的是 Android 库,那 AAR 才是正道。AAR 是 Android Archive 的缩写,它把 .so、Java/Kotlin 代码、资源文件、AndroidManifest 都打包在一起。
生成 AAR 有两种方式:
23.3.1 方式一:用 Android Gradle Plugin 的 native 模块
在 build.gradle 里配置:
android {
defaultConfig {
ndk {
abiFilters 'arm64-v8a', 'armeabi-v7a'
}
}
externalNativeBuild {
cmake {
path 'src/main/cpp/CMakeLists.txt'
}
}
}
然后执行 ./gradlew assembleRelease,就会在 build/outputs/aar/ 下生成 library-release.aar。
打开这个 AAR(其实就是个 zip),你会看到:
jni/arm64-v8a/libnative.sojni/armeabi-v7a/libnative.soclasses.jar(如果有 Java 代码)AndroidManifest.xml
23.3.2 方式二:手动组装 AAR(适用于纯 C++ 库)
有时候你不想引入 Gradle 那一套,只想用 CMake 搞定一切。那你可以手动构建 AAR 结构:
# 在 CMakeLists.txt 中
install(TARGETS my_native_lib
LIBRARY DESTINATION jni/${CMAKE_ANDROID_ARCH_ABI})
# 然后写一个脚本,把 jni 目录、AndroidManifest.xml、proguard.txt 打包成 zip,改后缀为 .aar
我个人更推荐方式一,因为 Gradle 会自动处理 ABI 筛选、混淆配置等细节。但如果你在做一个纯 C++ 的 SDK,方式二反而更灵活。
23.4 发布到 Maven 仓库:让全世界都能用你的库
AAR 生成好了,怎么给别人用?最规范的方式就是发布到 Maven 仓库。
Maven 仓库分两种:
- 本地仓库:
~/.m2/repository,仅供自己或局域网使用 - 远程仓库:如 Maven Central、JitPack、私有 Nexus
23.4.1 发布到本地 Maven 仓库
在 build.gradle 里添加:
apply plugin: 'maven-publish'
publishing {
publications {
release(MavenPublication) {
groupId = 'com.example'
artifactId = 'my-native-lib'
version = '1.0.0'
artifact "${buildDir}/outputs/aar/library-release.aar"
}
}
repositories {
mavenLocal()
}
}
执行 ./gradlew publishReleasePublicationToMavenLocal,你的 AAR 就发布到本地了。其他项目引用时,只需要在 build.gradle 里加:
implementation 'com.example:my-native-lib:1.0.0'
23.4.2 发布到远程 Maven 仓库
发布到远程仓库稍微复杂一点,需要配置认证信息。以 Maven Central 为例:
publishing {
repositories {
maven {
url = 'https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/'
credentials {
username = project.findProperty('ossrhUsername')
password = project.findProperty('ossrhPassword')
}
}
}
}
然后在 gradle.properties 里设置用户名和密码(千万别提交到 Git!)。
ORG_GRADLE_PROJECT_ossrhPassword,安全多了。
23.5 知识体系总览
这一章的内容比较多,我画了一张图帮你理清思路:
从图上你可以看到,整个流程是线性的:构建 → 安装 → 打包 → 发布。每一步都有对应的工具和最佳实践。
23.6 一些实战建议
最后,分享几个我这些年总结出来的经验:
- 版本号管理:我习惯把版本号定义在
CMakeLists.txt的project()命令里,然后通过-D参数传递给 Gradle。这样版本号只有一个源头,不会出现两边不一致的情况。 - ABI 选择:不要一股脑把所有 ABI 都打进去。如果你的库只用在手机端,arm64-v8a 就够了。armeabi-v7a 可以保留,但 x86 和 x86_64 除非你确定需要,否则建议去掉。包体积能小不少。
- 签名与校验:发布到 Maven Central 的 AAR 必须签名。我建议在 CI 流程里自动完成签名,避免手动操作漏掉。
- 文档与示例:AAR 包里可以附带一个
docs/目录,放 API 文档。虽然 Maven 仓库不强制,但用户会很感激。
./gradlew publishToMavenLocal,然后在另一个干净项目里引用这个本地版本,验证一切正常后,再发布到远程仓库。这一步虽然简单,但能避免很多尴尬的「发布即翻车」事故。
好了,这一章的内容就到这里。安装与打包看似是体力活,但做好了,能让你和你的用户都省心不少。记住:好的工程习惯,往往体现在这些「最后一公里」的细节里。
公众号:蓝海资料掘金营,微信deep3321