Android 自定义 APK 文件名

前言

DebugRelease 包时,默认的 apk 文件名往往不便区分版本或渠道。
下面只给出两种脚本形态下的示例:Groovy 的 build.gradle 与 Kotlin DSL 的 build.gradle.kts
把片段放进 android { } 里即可,其余 defaultConfigbuildTypes 等保持你工程原有配置。

可按需修改本文 tagscategories

基础写法

在无任何 productFlavors 时,用 applicationIdbuildTypeversionName 拼出文件名。

Groovy

Groovy 里 output 支持动态属性,可直接赋值 outputFileName

build.gradle

1
2
3
4
5
6
7
8
android {
applicationVariants.all { variant ->
variant.outputs.all { output ->
output.outputFileName =
"${variant.applicationId}-${variant.buildType.name}-v${variant.versionName}.apk"
}
}
}

Kotlin DSL

Kotlin 里 outputs 的公开类型不带 outputFileName,需要转成插件实现类后再赋值。
类名随 AGP 版本可能调整,编译报错时请按当前依赖改 import 或类名。

build.gradle.kts

1
2
3
4
5
6
7
8
9
10
11
import com.android.build.gradle.internal.api.ApkVariantOutputImpl

android {
applicationVariants.configureEach {
val v = this
outputs.configureEach {
(this as ApkVariantOutputImpl).outputFileName =
"${v.applicationId}-${v.buildType.name}-v${v.versionName}.apk"
}
}
}

若找不到 ApkVariantOutputImpl,可试 com.android.build.gradle.internal.api.BaseVariantOutputImpl,逻辑相同。

含 productFlavors 的写法

有渠道风味时,把 flavorName 拼进文件名,避免各渠道输出互相混淆。
更常见做法是把渠道放在文件名末尾:同一 applicationId、构建类型和版本号会排在一起,目录里先对齐「是谁、什么包、哪一版」,最后一截才是渠道,扫一眼或按名称排序时更直观。
若团队习惯「按渠道分堆」,也可以把渠道放在前面,全项目约定一致即可。

配置渠道示例(Groovy)

先声明一个风味维度(flavorDimensions),再在 productFlavors 里为每个渠道建一条风味;dimension 必须与维度名一致。
下面示例里渠道名为 huaweixiaomi,可按业务改成应用宝、官网等任意标识。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
android {
flavorDimensions "channel"

productFlavors {
huawei {
dimension "channel"
// 可选:applicationIdSuffix ".huawei"
// 可选:versionNameSuffix "-huawei"
}
xiaomi {
dimension "channel"
}
}
}

配置渠道示例(Kotlin DSL)

Kotlin DSL 用 flavorDimensions +=productFlavors { create("xxx") { } },语义与 Groovy 相同。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
android {
flavorDimensions += "channel"

productFlavors {
create("huawei") {
dimension = "channel"
// 可选:applicationIdSuffix = ".huawei"
// 可选:versionNameSuffix = "-huawei"
}
create("xiaomi") {
dimension = "channel"
}
}
}

输出文件名(Groovy)

在已配置 productFlavors 的前提下,用 variant.flavorName 拼进 outputFileName,渠道段放在末尾。

1
2
3
4
5
6
7
8
9
android {
applicationVariants.all { variant ->
variant.outputs.all { output ->
def flavor = variant.flavorName ?: "noflavor"
output.outputFileName =
"${variant.applicationId}-${variant.buildType.name}-v${variant.versionName}-${flavor}.apk"
}
}
}

输出文件名(Kotlin DSL)

1
2
3
4
5
6
7
8
9
10
11
12
import com.android.build.gradle.internal.api.ApkVariantOutputImpl

android {
applicationVariants.configureEach {
val v = this
val flavor = v.flavorName ?: "noflavor"
outputs.configureEach {
(this as ApkVariantOutputImpl).outputFileName =
"${v.applicationId}-${v.buildType.name}-v${v.versionName}-${flavor}.apk"
}
}
}

验证

  1. 无渠道时执行 ./gradlew assembleRelease(Windows 使用 gradlew.bat assembleRelease)。
  2. 已配置渠道时执行对应任务,例如 ./gradlew assembleHuaweiRelease,任务名为 assemble + 风味名首字母大写 + 构建类型首字母大写(与 Gradle 任务命名规则一致)。
  3. app/build/outputs/apk/ 下对应子目录查看生成的 apk 文件名是否与规则一致。

总结

  1. 多渠道时先在 productFlavors 里声明风味,再写 applicationVariants 里改 outputFileName 的片段。
  2. Groovy 用 output.outputFileName;Kotlin 需强转实现类后再赋 outputFileName
  3. 依赖 AGP 内部类型可能在升级后变更,届时以编译器提示为准或改为打包结束后脚本重命名。