如何在打包时自动替换 manifest 中的 meta-data 字段?
解读
国内 Android 项目普遍需要“一套源码 + N 个渠道包”的打法:不同渠道(华为、小米、OPPL、应用宝、字节、海外 Google Play 等)要求 manifest 里写死不同的 appkey、buglykey、友盟 key、推送 secret、服务器环境开关等 meta-data。
面试时,面试官想确认两点:
- 你知不知道 manifest 最终是 mergedManifest,而不是 src/main/AndroidManifest.xml;
- 你能否在构建阶段(CI 或本地)把“写死值”变成“可配置值”,并且不污染主代码、不出人工失误。
回答思路必须围绕“构建脚本自动化”展开,拒绝手动改文件、拒绝运行时反射,否则会被判“不专业”。
知识点
- manifest merger 流程:
每个 variant 先合并 flavor + buildType + main + aar 中的 AndroidManifest.xml,生成 intermediates/merged_manifest/…/AndroidManifest.xml,再打包。 - manifest merger 占位符:
在 manifest 中写 ${CHANNEL_KEY},Gradle 在 merger 阶段会用 build.gradle 里 manifestPlaceholders 的同名键值做字符串替换。 - manifestPlaceholders 作用域:
可以写在 defaultConfig、productFlavor、buildType,后者覆盖前者;支持多渠道脚本(for 循环)动态生成 flavor。 - 资源替换与 BuildConfig 字段:
若 meta-data 是整型/布尔,可用 manifestPlaceholders 转字符串;也可在 manifest 中写
android:value="@integer/umeng_key"
然后在 build.gradle 里 resValue "integer", "umeng_key", "123456",实现资源级替换。 - CI 安全:
密钥不能写仓库,通过 local.properties、gradle.properties、环境变量、加密文件(如 Tencent KMS、阿里云 KMS、GitHub secret)注入,Gradle 读取后塞入 manifestPlaceholders。 - AAB 场景:
Google Play 开启“Google 签名”后,上传 AAB,manifest 中的 key 仍按上述方式注入,但上传前需确认 Play Console 的“应用签名”证书指纹与后台配置一致。 - 低版本 AGP 兼容:
AGP 3.6 以下 manifestPlaceholders 对 meta-data 的 value 只能做字符串替换;AGP 7.0+ 支持任意类型,但仍建议统一字符串,避免解析异常。 - 验证方法:
编译后执行
./gradlew :app:processReleaseManifest
查看 intermediates/merged_manifest/…/AndroidManifest.xml,确认占位符已被替换;再反编译 apk/aab 用 aapt dump xmltree 二次确认。
答案
“我们完全在构建阶段完成替换,不碰源码。
第一步,把需要变化的 meta-data 写成占位符:
AndroidManifest.xml
<meta-data android:name="UMENG_APPKEY" android:value="${UMENG_APPKEY}"/>
<meta-data android:name="BUGLY_APPID" android:value="${BUGLY_APPID}"/>
第二步,在模块级 build.gradle 中利用 manifestPlaceholders 注入:
android {
defaultConfig {
manifestPlaceholders = [UMENG_APPKEY: ""] // 兜底空值
}
productFlavors {
huawei {
manifestPlaceholders += [
UMENG_APPKEY: project.findProperty("HUAWEI_UMENG_KEY") ?: "",
BUGLY_APPID : project.findProperty("HUAWEI_BUGLY_ID") ?: ""
]
}
xiaomi {
manifestPlaceholders += [
UMENG_APPKEY: project.findProperty("XIAOMI_UMENG_KEY") ?: "",
BUGLY_APPID : project.findProperty("XIAOMI_BUGLY_ID") ?: ""
]
}
}
}
第三步,把真实 key 放在 CI 的环境变量或 local.properties:
local.properties
HUAWEI_UMENG_KEY=123456789
HUAWEI_BUGLY_ID=abcdef
第四步,CI 脚本里解密后写入 local.properties,再执行
./gradlew :app:assembleHuaweiRelease
打包完成即自动替换,无需人工干预。
若 key 需要区分 buildType(debug/release),可在 buildType 里再次覆盖 manifestPlaceholders,实现‘一套源码、多 flavor、多类型、零手工’的 manifest 自动替换。”
拓展思考
- 如果 meta-data 不是字符串而是 resource 引用,如何自动化?
答:用 resValue 在 build.gradle 中动态生成整数、布尔或字符串资源,manifest 里写 @integer/xxx 或 @string/xxx,同样可以做到 flavor 级隔离。 - 当渠道数量达到 200+,手写 flavor 会爆炸,怎么办?
答:在 gradle 中读取一份 channel.json,for 循环动态生成 productFlavors,并在循环体里把每个渠道的 key 写进 manifestPlaceholders,实现“配置即渠道”。 - 海外版本使用 Google Play 的“应用签名”功能后,上传的 AAB 会被 Google 重新签名,如何保证后台配置的证书指纹与最终签名一致?
答:在 CI 里把 Google 提供的“部署证书指纹”写进 Play Console 接口,自动校验;manifest 中的 key 仍按上述方式注入,不受重签名影响。 - 如果项目采用 Jetpack Compose + Multi-Module,manifest 分散在多个模块,如何统一管理?
答:只在 app 模块的 manifest 中放渠道相关 meta-data,library 模块的 manifest 只声明静态组件;通过 manifestPlaceholders 集中注入,避免子模块重复占位。 - 未来 Google 强制 targetSdk=35+,并引入“隐私仪表板”限制读取 metadata,如何提前适配?
答:把敏感 key 迁移到 Gradle 生成的 BuildConfig 字段或 NDK 层,通过 JNI 读取,manifest 只保留非敏感标识位,降低被系统扫描的风险。