Android构建优化:编译速度从 10 分钟编译到 10 秒

项目地址:android-gradle-smart-build

Android构建优化:智能任务裁剪与Git状态感知

如何让Gradle自动感知代码变更,只编译真正需要修改的模块?本文将揭秘一套完整的智能构建优化方案。

前言:构建慢的痛点

"又卡在编译了..." 相信每个Android开发者都经历过这样的焦虑时刻。随着项目规模增长,每次改动几行代码却要等待漫长的全量编译,开发效率严重下降。

传统的增量编译虽然有所帮助,但在多模块项目中仍有很大优化空间。今天,我将分享一套基于Git状态感知的智能构建优化方案,让Gradle只编译真正变更的模块。

一、原理分析

1.1 传统构建的瓶颈

传统Gradle构建流程大致如下:

即使使用了增量编译,Gradle仍然会:

  • 执行lint检查
  • 运行单元测试
  • 处理所有模块的资源编译
  • 执行kapt/ksp注解处理

1.2 我们的优化思路

我们设计了一个智能构建系统,其工作原理如下:

二、方案设计

2.1 核心目标

  1. Git状态感知:识别代码库的实质性变更
  2. 任务智能裁剪:跳过与当前开发无关的构建任务
  3. 失败回退机制:确保构建系统的健壮性
  4. 全局编译优化:提升所有任务的执行效率

2.2 技术架构

复制代码
复制
├── Git状态监控层
│   ├── HEAD变更检测
│   └── 分支变更检测
├── 任务裁剪层
│   ├── 非必需任务过滤
│   └── 变更模块识别
├── 编译优化层
│   ├── Kotlin编译优化
│   ├── Java编译优化
│   └── 测试并行化
└── 容错控制层
    ├── 失败标记机制
    └── 自动回退策略

三、实现详解

3.1 Git状态初始化(一次性执行)

复制代码
gradle
gradle
复制
// 全局状态标记,确保只初始化一次
ext._gitHeadChanged = false
ext._gitBranchChanged = false
ext._gitStateInited = false
ext._taskGraphInstalled = false

def initGitStateOnce = {
    if (rootProject.ext._gitStateInited) return
    rootProject.ext._gitStateInited = true
    
    // 获取当前Git HEAD
    def currentHead = executeGitCommand("git rev-parse HEAD")
    def lastHead = readLastState("last_git_head.txt")
    
    // 记录并比较变化
    rootProject.ext._gitHeadChanged = 
        lastHead && lastHead != currentHead
}

关键点

  • 使用ext全局变量保证单例
  • 异常时默认认为有变更(安全策略)
  • 状态持久化到build目录

3.2 通用编译优化(全工程生效)

复制代码
gradle
gradle
复制
allprojects { Project p ->
    afterEvaluate {
        // Kotlin编译优化
        tasks.withType(KotlinCompile).configureEach { task ->
            task.kotlinOptions {
                jvmTarget = "1.8"
                freeCompilerArgs += ["-Xjsr305=strict"]
                suppressWarnings = true  // 抑制警告提升速度
            }
            task.incremental = true  // 开启增量编译
        }
        
        // Java编译优化
        tasks.withType(JavaCompile).configureEach {
            options.incremental = true
            options.fork = true
            options.forkOptions.memoryMaximumSize = "1g"
        }
        
        // 测试并行化
        tasks.withType(Test).configureEach {
            maxParallelForks = Runtime.runtime.availableProcessors().intdiv(2) ?: 1
            forkEvery = 100
        }
        
        // Android资源优化
        android {
            aaptOptions {
                cruncherEnabled = false  // 禁用PNG压缩
            }
        }
    }
}

3.3 智能任务裁剪机制

3.3.1 构建策略判断
复制代码
gradle
gradle
复制
gradle.taskGraph.whenReady { graph ->
    initGitStateOnce()
    
    // 只处理assemble相关任务
    if (!graph.allTasks.any { it.name.startsWith("assemble") }) return
    
    val allowTaskTrim = !lastTrimFailed &&
                        !ext._gitHeadChanged &&
                        !ext._gitBranchChanged
    
    println """
    🧠 构建策略判断:
      Git HEAD 变化: ${ext._gitHeadChanged}
      Git 分支变化: ${ext._gitBranchChanged}
      上一次裁剪失败: ${lastTrimFailed}
      是否允许裁剪: ${allowTaskTrim}
    """
}
3.3.2 任务跳过策略
复制代码
gradle
gradle
复制
// 定义可跳过的任务类型
def skipTasks = [
    "lint",           // 代码检查
    "test",          // 单元测试
    "connectedAndroidTest",  // 仪器测试
    "kaptTestKotlin",       // 测试注解处理
    "testDebugUnitTest"     // Debug单元测试
]

graph.allTasks.each { task ->
    skipTasks.each { skip ->
        if (task.name.contains(skip)) {
            task.enabled = false
            println "⏭️ 跳过任务: ${task.path}"
        }
    }
}
3.3.3 Git变更模块识别
复制代码
gradle
gradle
复制
def changedModules = [] as Set<String>

// 分析Git变更,识别影响的模块
def processChangedFiles = { String filePath ->
    if (!filePath.contains("/")) return
    def parts = filePath.split("/")
    def moduleParts = []
    
    // 解析模块路径(直到src目录为止)
    for (part in parts) {
        if (part == "src") break
        moduleParts << part
    }
    
    if (!moduleParts.isEmpty()) {
        changedModules << ":" + moduleParts.join(":")
    }
}

// 检查三种Git状态
[
    "git diff --name-only HEAD~1..HEAD",  // 最后一次提交
    "git diff --name-only",               // 工作区变更
    "git diff --name-only --cached"       // 暂存区变更
].each { cmd ->
    try {
        def output = executeGitCommand(cmd)
        output.readLines().each(processChangedFiles)
    } catch (ignored) {
        // 静默处理异常
    }
}
3.3.4 编译任务精准裁剪
复制代码
gradle
gradle
复制
graph.allTasks.each { task ->
    // 只处理编译相关任务
    if (!task.path.contains(":compileDebug") &&
        !task.path.contains(":kaptDebug") &&
        !task.path.contains(":kspDebug")) return
    
    // 提取模块路径
    def modulePath = task.path.substring(0, task.path.lastIndexOf(":"))
    def isChanged = changedModules.any { modulePath.startsWith(it) }
    
    if (!isChanged) {
        task.enabled = false
    } else {
        println "✅ 保留编译: ${task.path}"
    }
}

3.4 容错与回退机制

复制代码
gradle
gradle
复制
// 构建前:设置失败标记(默认本次会失败)
gradle.taskGraph.whenReady { graph ->
    trimFailFlagFile.text = "failed"
}

// 构建后:根据结果处理标记
gradle.buildFinished { result ->
    if (result.failure == null) {
        if (trimFailFlagFile.exists()) {
            trimFailFlagFile.delete()
            println "\n✅ 构建成功,清除裁剪失败标记"
        }
    } else {
        println """
        ❌ 构建失败,可能原因:
          1. 跨模块API变更未同步编译
          2. 资源文件依赖变更
          3. 注解处理器生成代码变更
        下次构建将自动回退到全量编译
        """
    }
}

四、使用方式

4.1 引入脚本

在根项目的build.gradle中添加:

复制代码
gradle
gradle
复制
apply from: 'build-optimization.gradle'

或在settings.gradle中:

复制代码
gradle
gradle
复制
apply from: file('build-optimization.gradle')

4.2 配置选项

可以根据项目需求调整以下参数:

复制代码
gradle
gradle
复制
// 在脚本开头添加配置块
ext.buildOptimization = {
    // 是否启用智能裁剪
    enableSmartTrim = true
    
    // 跳过的任务列表
    skipTasks = ["lint", "test", "..."]
    
    // 并行测试的最大进程数
    maxTestForks = Runtime.runtime.availableProcessors().intdiv(2)
    
    // Kotlin编译参数
    kotlinOptions = {
        jvmTarget = "1.8"
        freeCompilerArgs = ["-Xjsr305=strict"]
    }
}

4.3 查看构建报告

每次构建会输出详细的决策日志:

复制代码
复制
🧠 构建策略判断:
  Git HEAD 变化: false
  Git 分支变化: false
  上一次裁剪失败: false
  是否允许裁剪: true

📌 Git 变更模块:
  :app
  :lib:network

⏭️ 跳过任务: :app:lintDebug
⏭️ 跳过任务: :app:testDebugUnitTest
✅ 保留编译: :app:compileDebugKotlin
✅ 保留编译: :lib:network:compileDebugKotlin
⏭️ 跳过任务: :lib:database:compileDebugKotlin

五、性能对比

在测试项目(10个模块,20万行代码)中的表现:

场景 传统构建 优化后构建 提升幅度
单模块小改动 45s 12s 73%
跨模块API变更 52s 52s 0%
Clean后构建 68s 68s 0%
日常开发构建 38s 15s 60%

测试环境:MacBook Pro M1, 16GB RAM, Gradle 7.4

项目地址:android-gradle-smart-build

相关推荐
冰_河5 小时前
QPS从300到3100:我靠一行代码让接口性能暴涨10倍,系统性能原地起飞!!
java·后端·性能优化
阿巴斯甜16 小时前
Android 报错:Zip file '/Users/lyy/develop/repoAndroidLapp/l-app-android-ble/app/bu
android
Kapaseker16 小时前
实战 Compose 中的 IntrinsicSize
android·kotlin
xq952717 小时前
Andorid Google 登录接入文档
android
黄林晴19 小时前
告别 Modifier 地狱,Compose 样式系统要变天了
android·android jetpack
冬奇Lab1 天前
Android触摸事件分发、手势识别与输入优化实战
android·源码阅读
城东米粉儿1 天前
Android MediaPlayer 笔记
android
Jony_1 天前
Android 启动优化方案
android
阿巴斯甜1 天前
Android studio 报错:Cause: error=86, Bad CPU type in executable
android
张小潇1 天前
AOSP15 Input专题InputReader源码分析
android