You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Android多ProductFlavors能否复用同一externalNativeBuild的so库?

多ProductFlavor场景下复用原生库避免重复CMake编译方案

问题本质

Gradle 默认会为每一个ProductFlavor + BuildType组合生成独立的构建变体,每个变体会单独执行一套完整的externalNativeBuild流程。即使你的原生代码、编译参数和flavor完全无关,也会重复执行CMake配置、编译、链接全流程,构建耗时随flavor数量线性增长。
你的现有配置里已经把所有flavor的CMake构建输出目录设为同一路径,重复编译时产物会互相覆盖,但Gradle依然会按变体数量触发完整编译,属于完全无意义的耗时。

具体实现步骤

核心思路是将原生库编译逻辑和flavor构建流程解耦:原生库只按目标ABI编译一次,所有flavor打包时直接引用预编译好的so文件,彻底跳过重复的原生构建步骤。

1. 移除原有变体绑定的externalNativeBuild配置

删除defaultConfig块和android顶层下的所有externalNativeBuild相关配置,避免Gradle在每个flavor构建时自动触发CMake编译。

2. 配置全局jniLibs路径供所有flavor引用

在android配置块中添加sourceSets配置,指定所有构建变体统一从预编译目录读取so文件:

android {
    // 其他原有配置保持不变(applicationId、splits、productFlavors等)
    sourceSets {
        all {
            // 所有flavor、所有buildType都从该目录读取预编译so
            jniLibs.srcDirs = ["${RES_PATH}/proj/build/prebuilt-libs"]
        }
    }
}

预编译目录需要遵循Android jniLibs的ABI规范结构:

${RES_PATH}/proj/build/prebuilt-libs/
├── arm64-v8a/
│   └── libcocos.so
└── armeabi-v7a/
    └── libcocos.so

3. 编写独立的原生库编译Task

在app模块的build.gradle末尾添加独立编译任务,该任务不与任何flavor绑定,仅按需要的ABI编译一次原生库,同时支持增量编译——只有原生源码、CMake配置变更时才会重新执行,否则直接跳过:

task buildCocosNativeLibs {
    def cmakeVersion = "3.22.1" // 替换为你本地安装的CMake版本,和之前用的版本保持一致
    def minSdkVersion = 21 // 替换为你项目实际的minSdk
    def cmakeBinPath = "${android.sdkDirectory}/cmake/${cmakeVersion}/bin/cmake"
    def ninjaBinPath = "${android.sdkDirectory}/cmake/${cmakeVersion}/bin/ninja"
    def stagingDir = file("${RES_PATH}/proj/build/cmake-staging")
    def prebuiltDir = file("${RES_PATH}/proj/build/prebuilt-libs")

    doLast {
        // 遍历你配置的两个目标ABI分别编译
        ["arm64-v8a", "armeabi-v7a"].each { abi ->
            def abiBuildDir = file("${stagingDir}/${abi}")
            abiBuildDir.mkdirs()

            // 执行CMake配置
            exec {
                workingDir abiBuildDir
                commandLine cmakeBinPath,
                        "-H${projectDir}/../",
                        "-B${abiBuildDir.absolutePath}",
                        "-DANDROID_ABI=${abi}",
                        "-DANDROID_PLATFORM=android-${minSdkVersion}",
                        "-DANDROID_STL=c++_static",
                        "-DANDROID_TOOLCHAIN=clang",
                        "-DANDROID_ARM_NEON=TRUE",
                        "-DANDROID_LD=gold",
                        "-DRES_DIR=${RES_PATH}",
                        "-DCOCOS_X_PATH=${COCOS_ENGINE_PATH}",
                        "-DCMAKE_BUILD_TYPE=Release" // 打Debug包时替换为Debug
            }

            // 执行编译链接,用CPU核心数并行编译
            exec {
                workingDir abiBuildDir
                commandLine ninjaBinPath,
                        "-j${Runtime.runtime.availableProcessors()}",
                        "cocos"
            }

            // 将编译好的so拷贝到预编译目录对应ABI路径
            def abiPrebuiltDir = file("${prebuiltDir}/${abi}")
            abiPrebuiltDir.mkdirs()
            copy {
                from file("${abiBuildDir}/libcocos.so")
                into abiPrebuiltDir
            }
        }
    }

    // 配置增量编译规则:只有输入文件变化时才重新执行任务
    inputs.dir(file(projectDir, "../Classes")) // 替换为你项目实际的C++源码存放路径
    inputs.file(file(projectDir, "../CMakeLists.txt"))
    outputs.dir(prebuiltDir)
}

4. 配置打包任务依赖(可选)

如果需要在执行多flavor打包时自动检查so是否存在、源码是否有更新,自动触发原生库编译,可以添加如下配置:

tasks.whenTaskAdded { task ->
    // 匹配所有变体的assemble任务
    if (task.name.startsWith("assemble")) {
        task.dependsOn buildCocosNativeLibs
    }
}

优化效果

配置完成后,原生库只会按目标ABI数量编译(你的场景下是2次,对应arm64-v8a和armeabi-v7a),6个flavor打包时直接复用已经编译好的so,不会再重复执行CMake流程。对于Cocos这类原生代码量较大的项目,整体构建耗时通常可以降低60%以上。

注意事项

  • 如果需要区分Debug/Release的原生编译参数(比如Debug下开启调试符号、关闭优化),可以把编译任务拆分为buildCocosNativeDebug和buildCocosNativeRelease两个,分别配置对应参数,再让对应buildType的assemble任务依赖对应任务即可。
  • 如果你修改了原生代码或者CMakeLists.txt,增量编译机制会自动触发重新编译,不需要手动清理。

内容的提问来源于stack exchange,提问作者Mr.Hong

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.01 03:01:25