自定义APK&gradle全局配置文件

103 阅读4分钟

Gradle 的 Groovy 语法确实也是很多 Android 开发者的“噩梦”。它属于一种动态语言 DSL (领域特定语言),写起来灵活但读起来晦涩,而且 IDE 的代码提示往往也不太好用。

这也是为什么现在 Google 正在大力推行 KTS (Kotlin DSL) 的原因(用 Kotlin 写 Gradle 脚本,有类型检查和自动补全)。

下面我针对两个问题,用“说人话”的方式拆解一下它们的原理


第一个话题:自定义 APK 名称

这段代码看起来复杂,其实它是 Android Gradle Plugin (AGP) 提供的一套遍历逻辑

applicationVariants.all { variant ->
    // ...
}

原理解析:

  1. applicationVariants: 这是 Android 插件提供的一个集合,里面装着所有的“变体”。

    • 什么是变体?就是 BuildType (Debug/Release) 和 Flavor (渠道包) 组合后的产物。比如:debug, release, demoDebug, fullRelease
  2. .all { variant -> ... }: 这是 Groovy 的语法,相当于 Java 的 forEach 循环。它在说:“对于每一个生成的变体,都执行下面的代码

  3. variant.outputs.all { ... }: 每个变体最终可能会输出多个文件(虽然绝大多数情况我们只输出一个 APK,但 Android 支持根据 CPU 架构或屏幕密度拆分输出多个 APK) 为了保险起见,这里又做了一层循环:“对于这个变体下的每一个输出文件...

  4. outputFileName = "...": 这就是最终的目的。修改这个输出文件的文件名属性

为什么这么写?

因为 Gradle 在构建过程中,APK 的名称是在构建的中途生成的。你不能直接写 outputFileName = "xxx",必须挂载到 variant 的处理流程中,等 Gradle 算出当前是哪个变体后,你才有机会去修改它的名字

总结就是:

“Gradle,请监听构建过程。每当你准备好一个变体(比如 Release 版),就去把那个变体即将生成的 APK 改个名字,名字格式我定”


第二个话题:统一版本管理 (config.gradle)

1. 为什么 config.gradle 能被引用?

build.gradle (Root) 中:

apply from: 'config.gradle'

这句话的意思是:config.gradle 里的代码,原封不动地拿过来,在这个位置执行一遍

注意:通常的做法是直接放在文件的最外层 而不是放在 buildscript { ... } 里面。如果放在 buildscript 里还能生效,可能是因为 Groovy 的作用域穿透或者 Gradle 版本的特性,但标准做法是放在根目录 build.gradle 的第一行或 buildscript 闭包之后

2. ext { ... } 是什么?

ext 是 Gradle 的一个特殊属性,全称叫 ExtraPropertiesExtension。可以把它想象成一个 Map (键值对字典) 所有的 Gradle 对象(Project, Task 等)都有这个 ext

config.gradle 里写:

ext {
    versionCode = 123
}

因为这个脚本是应用在 Root Project 上的,所以相当于给 根工程对象 挂载了一个全局变量 rootProject.ext.versionCode = 123

3. rootProject.versionCode 原理

在 App Module (app/build.gradle) 中:

versionCode rootProject.versionCode

这里的逻辑链条是这样的:

  1. rootProject:指代你的根工程对象(就是最外层的那个)
  2. .versionCode:在 Java/Kotlin 中,并没有 versionCode 这个字段。但是 Groovy 是动态语言,当它在 rootProject 对象里找不到 versionCode 属性时,它会自动去 ext 里面找
    • 所以 rootProject.versionCode 其实是 rootProject.ext.versionCode 的缩写(语法糖)

这里 rootProject 就代表 config.gradle 的 ext { 了是吗?

不完全是,但效果一样

  • rootProject 代表根工程对象
  • config.gradle 里的内容通过 apply from 被注入到了根工程对象里的 ext 属性中
  • 所以你是通过根工程对象,访问到了被注入进去的变量

为什么验证成功但你不确定?

比如定义的那几个方法:

def getVersionCode(build){ ... }

这些定义在 config.gradle 里的方法,因为 apply from 的作用,变成了根工程脚本的一部分。当调用 ext.versionCode = getVersionCode(...) 时,它只是在脚本执行期间计算出了一个整数,并赋值给了 versionCode 变量。子模块拿到的是计算好的结果(那个整数),而不是那个函数


总结与建议

Gradle 的 Groovy 语法确实很“乱”,因为它不仅是配置,还是脚本代码,混杂了声明式(配置)和命令式(逻辑)的代码

建议:

  1. 关于 config.gradle 的引用位置: 建议将 apply from: 'config.gradle' 移出 buildscript {} 块,直接放在根目录 build.gradle 的顶部 这样逻辑更符合官方规范:

    // 根目录 build.gradle
    apply from: 'config.gradle' // 放在最顶层
    
    buildscript {
       // ...
    }
    
  2. 拥抱 Kotlin DSL (KTS): 如果觉得 Groovy 很难懂,如果是新项目,强烈建议尝试 Kotlin DSL (build.gradle.kts)

    • 优点:你写的 android { ... } 都可以点进去看源码,知道里面有什么属性,需要什么类型,IDE 会智能提示。
    • Version Catalog:现在 Android 官方推荐用 libs.versions.toml 文件来管理版本号,比 config.gradle 这种方式更清晰、更规范,Android Studio 对它的支持也更好。

目前的写法虽然“乱”,但在这个项目中是奏效且成熟的方案(国内很多旧项目都这么干),可以放心地继续使用