本次编译核心实现基于 CMake FetchContent 自动拉取第三方依赖(Abseil/Protobuf)+ 编译生成 Android 动态库 libnativesdk.so,全程适配 NDK25/CMake3.22,最终实现无报错编译成功。
注:CMakeLists.txt完整文件在文章末尾
一、编译核心目标
基于现有业务代码(core/jni 目录)+ Protobuf 生成代码,通过 CMake 实现:
- 自动化拉取 Abseil、Protobuf 3.25.3 第三方依赖,无需本地存放依赖源码;
- 复用 Protobuf 内置的 utf8_range 库,解决其独立仓库 404、无源文件问题;
- 编译生成 Android 可执行动态库 libnativesdk.so,仅实现编译 + 链接依赖,无需安装 / 导出依赖库;
- 配置兼容跨平台(后续可直接迁移至 Visual Studio),无冗余配置。
二、完整编译配置流程(按 CMake 执行顺序)
步骤 1:基础编译环境与配置初始化
- 指定 CMake 最低版本(3.10.2)和项目名(nativesdk),匹配 Android Studio 内置 CMake 版本;
- 配置跨平台编译基础参数:C++17 标准、位置无关代码(PIC)、Android 静态 STL(c++_static),保证编译产物兼容性;
- 引入 CMake 原生模块
FetchContent,为自动化拉取 Git 仓库依赖做准备。
步骤 2:第三方依赖拉取配置(FetchContent)
(1)Abseil 配置
- 拉取官方仓库:
https://github.com/abseil/abseil-cpp.git,锁定稳定 Tag 20240116.0; - 关闭浅克隆(GIT_SHALLOW OFF)、开启拉取进度(GIT_PROGRESS ON);
- 禁用 Abseil 测试 / 示例 / 基准编译(ABSL_BUILD_TESTING/OFF 等),减少编译冗余。
(2)Protobuf 3.25.3 核心配置
- 拉取官方仓库:
https://github.com/protocolbuffers/protobuf.git,升级版本至 v3.25.3(稳定版); - 核心修复:设置
GIT_SUBMODULES "",禁止拉取 gtest/benchmark 子模块,彻底解决网络 SSL 拉取错误; - 启用 Lite 运行时(protobuf_USE_LITE_RUNTIME ON)、编译静态库(protobuf_BUILD_SHARED_LIBS OFF),匹配轻量开发需求;
- 关键新增:
set(protobuf_INSTALL OFF CACHE BOOL "" FORCE),禁用 Protobuf 内置的安装 / 导出逻辑,从根源规避导出依赖链错误; - 禁用 Protobuf 测试 / 示例编译,提升编译速度。
(3)依赖初始化
执行 FetchContent_MakeAvailable(abseil-cpp protobuf),CMake 自动完成:
- 首次构建:检测本地无缓存时,调用系统 Git 拉取依赖源码至本地缓存目录;
- 后续构建:直接读取本地缓存,不发起网络请求;
- Protobuf 3.25.3 自动编译其内置的 utf8_range 库,生成可用的 CMake 目标。
步骤 3:utf8_range 库适配(核心难点解决)
- 定位 Protobuf 内置 utf8_range 头文件目录:
${protobuf_SOURCE_DIR}/third_party/utf8_range; - 通过
include_directories暴露头文件路径,保证业务代码可正常#include "utf8_range.h"; - 直接复用 Protobuf 自动构建的 utf8_range 目标,无需手动编译,避免命名冲突。
步骤 4:业务代码与 Protobuf 生成代码整合
- 通过
file(GLOB)递归获取核心业务代码(core/ .cpp、jni/ .cpp)和 Protobuf 编译生成的代码(proto/generated/*.pb.cc); - 整合所有源码为
ALL_SRC,作为后续编译动态库的输入。
步骤 5:动态库(libnativesdk.so)编译与链接
- 执行
add_library(nativesdk SHARED ${ALL_SRC}),指定编译为 Android 动态库; - 配置头文件搜索路径:包含项目根目录、Protobuf 生成目录、所有第三方依赖源码目录,保证头文件可被正确找到;
- 设置编译宏:保留
PROTOBUF_USE_LITE_RUNTIME等核心宏,匹配 Protobuf Lite 版本使用规范; - 库链接:将业务动态库与 Abseil 核心模块、Protobuf-lite、utf8_range 及 Android 系统库(log/z/atomic/m)链接,形成完整依赖链。
步骤 6:执行编译(Android Studio)
- 点击「Build -> Make Project」,Android Studio 自动调用 CMake + NDK 执行构建;
- 编译成功后,在
build/intermediates/cmake/[Debug/Release]/obj/[架构(如arm64-v8a)]/生成最终产物libnativesdk.so。
三、编译过程中遇到的核心问题及解决方案
问题 1:utf8_range 独立仓库拉取失败(404 / 无有效链接)
- 现象:最初配置拉取
akrylysov/utf8_range.git报 404,Google 官方无独立 utf8_range 仓库; - 原因:utf8_range 是轻量级工具库,无官方独立仓库,仅作为子目录内嵌于 Protobuf/Google Fonts 等项目;
- 解决方案:放弃独立拉取,复用 Protobuf 内置的 utf8_range 目录,无需额外网络请求。
问题 2:CMake 无法确定 utf8_range 链接语言(CMake Error: CMake can not determine linker language)
- 现象:手动编译 utf8_range 时,仅添加头文件
utf8_range.h,CMake 报语言识别失败; - 原因:CMake 通过源文件后缀(.c/.cpp)识别编译 / 链接语言,仅头文件无有效识别依据;
- 解决方案:因后续升级 Protobuf 3.25.3 后其会自动编译 utf8_range,直接移除手动编译逻辑,复用官方构建目标,规避该问题。
问题 3:utf8_range 目标命名冲突(add_library cannot create target "utf8_range" because another target with the same name already exists)
- 现象:升级 Protobuf 3.25.3 后,手动编译 utf8_range 与 Protobuf 自动构建的同名目标冲突;
- 原因:Protobuf 3.25.3 优化构建脚本,会自动将内置 utf8_range 编译为 CMake 目标,3.21.12 无此逻辑;
- 解决方案:彻底移除所有手动编译 utf8_range 的代码,直接复用 Protobuf 自动生成的目标。
问题 4:Protobuf 导出依赖链不完整(install (EXPORT "protobuf-targets" ...) includes target "libprotobuf-lite" which requires target "absl_absl_check" that is not in any export set)
- 现象:编译时触发 Protobuf 内置的 install/export 逻辑,报 Abseil 目标未加入导出集合;
- 原因:Protobuf 3.25.3 依赖 Abseil,但其内置脚本未配置 Abseil 目标的导出规则,CMake 导出时校验依赖链失败;
- 解决方案:项目仅需编译链接,无需安装 / 导出依赖,添加
protobuf_INSTALL OFF禁用 Protobuf 所有 install/export 逻辑,从根源解决问题。
问题 5:Protobuf 子模块拉取 SSL 错误
- 现象:最初拉取 Protobuf 时,因自动拉取 gtest/benchmark 子模块,报网络 SSL 连接错误;
- 原因:网络环境无法直连子模块仓库,或子模块拉取触发证书校验问题;
- 解决方案:设置
GIT_SUBMODULES "",禁止 Protobuf 拉取任何子模块,仅拉取核心源码。
四、关键注意事项(核心避坑点,必须记录)
(一)依赖拉取与缓存相关
- FetchContent 缓存规则:首次拉取的依赖源码缓存于 Android Studio 目录
项目根目录/.cxx/[构建类型]/[随机标识]/[架构]/_deps/,后续构建不重复拉取;如需更新依赖版本(修改 GIT_TAG),必须彻底删除项目根目录的 build/ 和 .cxx/ 文件夹,否则会使用旧缓存; - Git 环境要求:FetchContent 依赖系统 Git,需保证本地安装 Git 并配置到系统环境变量(PATH),否则拉取失败;
- 网络配置:若拉取依赖缓慢 / 失败,需配置 Git 全局代理(
git config --global https.proxy http://127.0.0.1:7890,替换为自身代理地址); - Protobuf 版本差异:3.21.12 不自动编译 utf8_range,3.25.3 会自动构建,升级版本后必须移除手动编译 utf8_range 的代码,避免命名冲突。
(二)CMake 配置相关
- Android 特有配置:
CMAKE_ANDROID_STL_TYPE c++_static仅对 Android 生效,迁移至 Visual Studio 时会被自动忽略,无需修改; - 编译标准一致性:项目指定 C++17 标准,第三方依赖(Abseil 20240116.0、Protobuf 3.25.3)均兼容 C++17,无需调整;
- 静态库 / 动态库规范:Abseil、Protobuf 均编译为静态库,最终链接到动态库 libnativesdk.so 中,避免运行时依赖缺失;
- 禁止随意修改官方开关:
protobuf_INSTALL OFF、GIT_SUBMODULES ""等均为官方提供的配置开关,非自定义 hack,稳定性高,无需额外调整。
(三)文件与路径相关
- 业务代码路径:core/、jni/、proto/generated/ 目录结构不可随意修改,否则需同步调整 CMake 中
file(GLOB)的路径; - 头文件引用规范:业务代码中引用 utf8_range 直接写
#include "utf8_range.h",引用 Protobuf 头文件按生成路径写,无需添加绝对路径; - 跨平台路径要求:若后续迁移至 Visual Studio,项目根目录和构建目录不能包含中文、空格或特殊字符,否则会触发 Git 拉取失败或 CMake 路径识别错误。
(四)编译与产物相关
- 构建目录清理:遇到任何 CMake 配置错误、编译报错,优先删除
build/和.cxx/文件夹(彻底清除缓存),可解决 90% 以上的配置冲突问题; - 编译产物位置:成功编译后,libnativesdk.so 按「构建类型(Debug/Release)+ 架构(arm64-v8a/x86_64 等)」分类存放,可根据需求拷贝使用;
- Debug/Release 区别:Debug 版本包含调试信息,体积大;Release 版本开启优化,体积小、运行快,正式集成需使用 Release 版本。
(五)跨平台兼容相关(后续迁移 Visual Studio)
- 无需修改 CMake 配置:当前配置为纯跨平台写法,Visual Studio 2019+ 可直接打开项目根目录(文件夹形式),自动识别构建;
- VS 环境要求:保证 Visual Studio 内置 CMake 版本 ≥3.11,且 Git 已配置到系统环境变量;
- VS 缓存路径:Visual Studio 中 FetchContent 缓存于
项目根目录/.vs/CMakeFiles/_deps/,与 Android 环境缓存独立,互不干扰。
五、编译成功后核心成果
当前 CMakeLists.txt 已实现零手动依赖、零配置冲突、跨平台兼容,核心能力:
- 自动化拉取:一键拉取 Abseil 20240116.0 + Protobuf 3.25.3,无需手动下载、解压、配置依赖路径;
- 无冗余编译:所有第三方依赖均禁用测试 / 示例,仅编译核心代码,提升编译速度;
- 产物可用:生成的 libnativesdk.so 可直接集成到 Android 项目中,正常链接并调用业务逻辑;
- 易于维护:后续升级依赖版本,仅修改
FetchContent_Declare中的GIT_TAG,删除缓存后重新构建即可。
六、后续开发维护建议
- 禁止随意修改第三方依赖配置:如无特殊需求,不要修改 Abseil/Protobuf 的编译开关(如 BUILD_TESTS、USE_LITE_RUNTIME 等),避免引入新的配置冲突;
- 定期清理缓存:若项目长时间开发,可定期删除
build/和.cxx/文件夹,避免缓存文件过多占用磁盘空间; - 版本锁定:第三方依赖尽量使用稳定 Tag(如 v3.25.3),避免使用 main/master 分支,防止源码更新引入兼容性问题;
- 错误排查优先级:后续若出现编译错误,按「清理 build/.cxx 缓存 → 检查 Git 网络 / 环境 → 检查路径是否含特殊字符 → 检查依赖版本兼容性」的顺序排查,高效定位问题。
cmake_minimum_required(VERSION 3.10.2)
project(nativesdk)
# 基础编译配置(适配NDK25/CMake3.22,无冗余)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_POSITION_INDEPENDENT_CODE ON CACHE BOOL "" FORCE)
set(CMAKE_ANDROID_STL_TYPE c++_static CACHE STRING "" FORCE)
# ================================= 全依赖自动化拉取 =================================
include(FetchContent)
# 1. Abseil:官方正确Tag 20240116.0,关闭浅克隆
FetchContent_Declare(
abseil-cpp
GIT_REPOSITORY https://github.com/abseil/abseil-cpp.git
GIT_TAG 20240116.0
GIT_SHALLOW OFF
GIT_PROGRESS ON
)
set(ABSL_BUILD_TESTING OFF CACHE BOOL "" FORCE)
set(ABSL_BUILD_EXAMPLES OFF CACHE BOOL "" FORCE)
set(ABSL_BUILD_BENCHMARKS OFF CACHE BOOL "" FORCE)
# 2. Protobuf:3.25.3 Lite版 + 核心修复:禁止拉取任何子模块(解决SSL错误)
FetchContent_Declare(
protobuf
GIT_REPOSITORY https://github.com/protocolbuffers/protobuf.git
GIT_TAG v3.25.3 # 已改为3.25.3稳定版
GIT_SHALLOW OFF
GIT_PROGRESS ON
GIT_SUBMODULES "" # 关键!禁止拉取gtest/benchmark子模块,彻底避免SSL错误
)
set(protobuf_BUILD_TESTS OFF CACHE BOOL "" FORCE)
set(protobuf_BUILD_EXAMPLES OFF CACHE BOOL "" FORCE)
set(protobuf_USE_LITE_RUNTIME ON CACHE BOOL "" FORCE)
set(protobuf_BUILD_SHARED_LIBS OFF CACHE BOOL "" FORCE)
set(protobuf_INSTALL OFF CACHE BOOL "" FORCE) # 核心新增!禁用Protobuf的安装/导出逻辑,从根源避免错误
# 一次性初始化Abseil+Protobuf(Protobuf 3.25.3会自动构建内置的utf8_range库,无需手动处理)
FetchContent_MakeAvailable(abseil-cpp protobuf)
# ================================= 核心优化:复用Protobuf自动构建的utf8_range库(无命名冲突) =================================
# 定位protobuf 3.25.3内置的utf8_range头文件目录(仅暴露头文件,无需重新编译)
set(UTF8_RANGE_SRC_DIR "${protobuf_SOURCE_DIR}/third_party/utf8_range")
# 暴露utf8_range头文件路径,确保业务代码可正常#include "utf8_range.h"
include_directories(${UTF8_RANGE_SRC_DIR})
# ================================= 对接本地业务代码=================================
file(GLOB YOUR_BUSINESS_SRC
${CMAKE_CURRENT_SOURCE_DIR}/core/*.cpp
${CMAKE_CURRENT_SOURCE_DIR}/jni/*.cpp
)
file(GLOB PROTO_GEN_SRC ${CMAKE_CURRENT_SOURCE_DIR}/proto/generated/*.pb.cc)
set(ALL_SRC ${YOUR_BUSINESS_SRC} ${PROTO_GEN_SRC})
# ================================= 编译SO + 头文件 + 链接(一键对接,无任何修改) =================================
add_library(nativesdk SHARED ${ALL_SRC})
target_include_directories(nativesdk PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}
${CMAKE_CURRENT_SOURCE_DIR}/proto/generated
${abseil-cpp_SOURCE_DIR}
${protobuf_SOURCE_DIR}
${UTF8_RANGE_SRC_DIR} # 保留utf8_range头文件路径
)
target_compile_definitions(nativesdk PRIVATE
PROTOBUF_USE_LITE_RUNTIME
GOOGLE_PROTOBUF_NO_RTTI
PROTOBUF_DISABLE_JSON=1
)
# 链接配置(直接使用Protobuf自动构建的utf8_range库,无命名冲突)
target_link_libraries(nativesdk PRIVATE
absl::base absl::strings absl::hash absl::synchronization absl::time absl::status
protobuf::libprotobuf-lite
utf8_range # 直接链接Protobuf内置的utf8_range库(无需::别名,原生支持)
log z atomic m
)