30、综合实战:从零构建一个Android项目Gradle体系

终于到了最后一章。说实话,写到这里我挺感慨的。前面29章我们拆开了揉碎了讲Gradle的各种细节,从Groovy语法到Kotlin DSL,从依赖管理到自定义插件。但很多同学学完后问我:“老师,这些东西在实际项目中到底怎么串起来?”

嗯,这一章就是回答这个问题的。我会带着你从零开始,搭建一个完整的Android项目Gradle体系。包含多模块架构、Version Catalog、Convention Plugin、自定义插件,最后再配上CI/CD全流程。说白了,这就是我过去几年在几个中大型项目中沉淀下来的“最佳实践模板”。

本章核心目标:构建一个可维护、可扩展、可复用的Gradle构建体系。让新人加入项目时,不用看半天build.gradle才能开始写代码。

30.1 整体架构设计

先看一张图,这是整个Gradle体系的架构分层。我习惯把构建逻辑分成四层,每一层各司其职。

Gradle构建体系四层架构 第1层:项目根目录(Root Project) settings.gradle.kts · 根build.gradle.kts · gradle.properties · local.properties 第2层:版本目录(Version Catalog) gradle/libs.versions.toml · 统一管理依赖版本号 第3层:约定插件(Convention Plugin) build-logic模块 · 封装通用构建配置,避免重复代码 第4层:业务模块(Feature Modules) app · feature:home · feature:profile · library:network · library:ui

你看,这个分层很清晰。上层依赖下层,下层为上层提供能力。我在项目中遇到过不少团队,把所有配置都塞进根build.gradle里,结果一个文件上千行,谁都不敢动。用这种分层方式,每个文件职责单一,改起来心里有底。

30.2 第一步:创建Version Catalog

先别急着写代码。我个人习惯,任何新项目的第一步都是创建 gradle/libs.versions.toml。为什么?因为依赖管理是Gradle脚本里最乱的部分,先把这个理清了,后面就顺了。

# gradle/libs.versions.toml

[versions]
agp = "8.2.2"
kotlin = "1.9.22"
coroutines = "1.7.3"
compose-bom = "2024.02.00"
compose-compiler = "1.5.10"
retrofit = "2.9.0"
okhttp = "4.12.0"
hilt = "2.50"
room = "2.6.1"

[libraries]
# AndroidX Core
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "core-ktx" }
androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" }

# Compose
compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "compose-bom" }
compose-ui = { group = "androidx.compose.ui", name = "ui" }
compose-material3 = { group = "androidx.compose.material3", name = "material3" }

# Network
retrofit-core = { group = "com.squareup.retrofit2", name = "retrofit", version.ref = "retrofit" }
okhttp-core = { group = "com.squareup.okhttp3", name = "okhttp", version.ref = "okhttp" }

# DI
hilt-android = { group = "com.google.dagger", name = "hilt-android", version.ref = "hilt" }
hilt-compiler = { group = "com.google.dagger", name = "hilt-android-compiler", version.ref = "hilt" }

[plugins]
android-application = { id = "com.android.application", version.ref = "agp" }
android-library = { id = "com.android.library", version.ref = "agp" }
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
hilt = { id = "com.google.dagger.hilt.android", version.ref = "hilt" }

我的经验:版本号一定要提取成变量,不要直接写在library声明里。否则升级版本时你得一个一个改,很容易漏掉。我曾经因为漏改一个版本号,导致线上出现诡异的运行时崩溃,排查了两天才找到原因。

30.3 第二步:构建Convention Plugin

接下来是最核心的部分——约定插件。说白了,就是把所有模块通用的构建配置抽出来,放到一个独立的Gradle模块里。这样每个业务模块的build.gradle.kts只需要几行代码。

先创建 build-logic 模块:

// build-logic/settings.gradle.kts
dependencyResolutionManagement {
    repositories {
        google()
        mavenCentral()
    }
}

rootProject.name = "build-logic"
include(":convention")

然后写一个Android应用模块的约定插件:

// build-logic/convention/src/main/kotlin/AndroidApplicationConventionPlugin.kt
package com.example.buildlogic

import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.kotlin.dsl.*

class AndroidApplicationConventionPlugin : Plugin<Project> {
    override fun apply(target: Project) {
        with(target) {
            // 应用核心插件
            pluginManager.apply("com.android.application")
            pluginManager.apply("org.jetbrains.kotlin.android")

            // 配置Android扩展
            extensions.configure<com.android.build.gradle.AppExtension> {
                compileSdk = 34
                defaultConfig {
                    minSdk = 26
                    targetSdk = 34
                    versionCode = 1
                    versionName = "1.0.0"
                }
                compileOptions {
                    sourceCompatibility = JavaVersion.VERSION_17
                    targetCompatibility = JavaVersion.VERSION_17
                }
            }
        }
    }
}

注意:Convention Plugin需要注册到Gradle的插件管理中,否则无法被其他模块引用。具体做法是在 build-logic/convention/build.gradle.kts 中添加 gradlePlugin 块来声明插件ID。

30.4 第三步:多模块项目结构

有了约定插件,业务模块的配置就变得极其简洁。来看看最终的项目结构:

MyApp/
├── settings.gradle.kts
├── build.gradle.kts
├── gradle/
│   └── libs.versions.toml
├── build-logic/
│   ├── settings.gradle.kts
│   └── convention/
│       ├── build.gradle.kts
│       └── src/main/kotlin/.../*.kt
├── app/
│   └── build.gradle.kts
├── feature/
│   ├── home/
│   │   └── build.gradle.kts
│   └── profile/
│       └── build.gradle.kts
└── library/
    ├── network/
    │   └── build.gradle.kts
    └── ui/
        └── build.gradle.kts

每个业务模块的build.gradle.kts长这样:

// feature/home/build.gradle.kts
plugins {
    id("myapp.android.library")
    id("myapp.android.compose")
    id("myapp.hilt")
}

android {
    namespace = "com.myapp.feature.home"
}

dependencies {
    implementation(project(":library:network"))
    implementation(project(":library:ui"))
    implementation(libs.compose.material3)
}

你想想看,以前写一个模块的build.gradle,少说也要三四十行。现在呢?十行搞定。而且所有模块的编译配置完全一致,不会出现A模块用Java 11、B模块用Java 17这种低级错误。

30.5 第四步:自定义插件——自动生成BuildConfig

接下来我们写一个自定义插件。这个插件的功能是:根据Git分支名自动生成BuildConfig字段,方便我们在代码里区分Debug/Release环境。

// build-logic/convention/src/main/kotlin/GitBranchPlugin.kt
class GitBranchPlugin : Plugin<Project> {
    override fun apply(project: Project) {
        val branchName = getGitBranch()
        val isMainBranch = branchName == "main" || branchName == "master"

        project.android().defaultConfig {
            buildConfigField("String", "GIT_BRANCH", "\"$branchName\"")
            buildConfigField("Boolean", "IS_MAIN_BRANCH", "$isMainBranch")
        }
    }

    private fun getGitBranch(): String {
        return try {
            Runtime.getRuntime()
                .exec(arrayOf("git", "rev-parse", "--abbrev-ref", "HEAD"))
                .inputStream.bufferedReader().readText().trim()
        } catch (e: Exception) {
            "unknown"
        }
    }
}

避坑指南:我曾经在CI环境里忘记安装Git,导致插件执行时报错。后来我加了一个try-catch,捕获异常时返回"unknown"并打印警告日志。这样即使没有Git环境,构建也不会中断。

30.6 第五步:CI/CD全流程配置

最后一步,把整个构建体系接入CI/CD。我用GitHub Actions举例,但思路同样适用于GitLab CI或Jenkins。

# .github/workflows/build.yml
name: Android CI

on:
  push:
    branches: [ main, develop ]
  pull_request:
    branches: [ main ]

jobs:
  build:
    runs-on: ubuntu-latest
    
    steps:
      - uses: actions/checkout@v4
      
      - name: Set up JDK 17
        uses: actions/setup-java@v4
        with:
          java-version: '17'
          distribution: 'temurin'
      
      - name: Cache Gradle
        uses: actions/cache@v3
        with:
          path: |
            ~/.gradle/caches
            ~/.gradle/wrapper
          key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
      
      - name: Build with Gradle
        run: ./gradlew assembleDebug --build-cache
      
      - name: Run Tests
        run: ./gradlew testDebugUnitTest
      
      - name: Upload APK
        uses: actions/upload-artifact@v4
        with:
          name: app-debug
          path: app/build/outputs/apk/debug/app-debug.apk
阶段 任务 说明
代码检查 detekt + ktlint 在build之前运行,确保代码风格统一
编译构建 assembleDebug 使用--build-cache加速增量构建
单元测试 testDebugUnitTest 生成测试报告,失败则中断流程
产物归档 upload-artifact 保存APK和测试报告,供后续使用

30.7 写在最后

到这里,一个完整的Android项目Gradle体系就搭建完成了。从Version Catalog管理依赖,到Convention Plugin统一配置,再到自定义插件扩展能力,最后用CI/CD把整个流程自动化。这套体系我在三个不同的项目里实践过,每次都能让团队的构建效率提升30%以上。

说实话,Gradle脚本写得好不好,直接决定了团队后续的开发体验。花一周时间把构建体系搭好,后面一年都能受益。这笔账,怎么算都划算。

如果你在实际搭建中遇到什么问题,欢迎随时交流。毕竟,构建脚本这东西,一个人踩坑不如大家一起分享经验。


公众号:蓝海资料掘金营,微信deep3321