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

相关推荐
heartbeat..2 小时前
数据库性能优化:优化的时机(表结构+SQL语句+系统配置与硬件)
java·数据库·mysql·性能优化
2501_944521592 小时前
Flutter for OpenHarmony 微动漫App实战:标签筛选功能实现
android·开发语言·前端·javascript·flutter
mjhcsp2 小时前
如何做一个网站?
android
2501_915909062 小时前
在无需越狱的前提下如何对 iOS 设备进行文件管理与数据导出
android·macos·ios·小程序·uni-app·cocoa·iphone
没有bug.的程序员2 小时前
Spring Boot 数据访问:JPA 与 MyBatis 集成对比与性能优化深度解密
java·spring boot·性能优化·mybatis·jpa·集成对比
_F_y2 小时前
MySQL表的增删查改
android·数据库·mysql
海雅达手持终端PDA3 小时前
海雅达Model 10X工业平板赋能MES系统全场景落地应用方案
android·物联网·硬件工程·能源·健康医疗·制造·平板
普马萨特3 小时前
如何从安卓系统中获取扫描到的 Wi‑Fi 的 MAC 地址和 RSSI?
android·macos
游戏开发爱好者83 小时前
iPhone 网络调试的过程,请求是否发出,是否经过系统代理,app 绕过代理获取数据
android·网络·ios·小程序·uni-app·iphone·webview