Skip to content

编译调度流程

这页不讲 Java、Kotlin、资源各自怎么编译,那些内容在增量编译子页里。这里只讲调度层:一次 Run 如何决定走增量还是 Gradle,增量产物如何进入 staging,什么时候继续补编译,什么时候停下并回退。

调度层要同时避开两类问题:判定太重会吃掉增量收益,判定太松会把本应回退 Gradle 的场景误当成可增量。Jugg 用分层判定先排除不可信现场,再用 staging、扩散补编译和单次重试控制后续流程。

判定本身的开销与误判风险

增量编译只有在"判断现在能不能安全增量"这一步足够便宜时才划算。完整解析工程会把判定成本拉回 Gradle 级别,抵消本轮省下的编译时间。

更隐蔽的是误判风险。一次 Run 的安全性取决于多个互相独立的条件:用户是否主动要求全量、构建脚本或依赖是否变化、目标设备状态是否可信、本轮改动规模是否超出增量收益区间。任何一个条件被忽略,都会把一个本应回退 Gradle 的场景误判成"可增量",让设备拿到与全量构建不一致的产物,直到运行时才暴露问题。

分层判定:先排除不可增量,再进入增量

Jugg 把判定拆成一组从粗到细、可短路的检查。任何一条命中,就直接给出回退或失败结论,不再往下走;全部通过才进入增量编译。

判定命中后的处理
用户强制全量直接回退 Gradle。
构建目标(app / androidTest)变化回退 Gradle,重建对应 APK 基线。
目标设备状态不可信返回设备状态给出的失败原因,不进入增量。
改动文件或涉及模块过多判定增量收益不足,返回"改动过多"。
构建脚本变化读取依赖变化结果,再决定是否仍可走依赖库增量。
部署状态不支持增量返回当前部署状态的限制原因。

判定阶段还会用部署历史做一次文件回滚过滤:如果某个文件被改回了已经部署过的内容,它会被移出本轮变化集合,避免无意义的重编译。

这套分层判定的设计目标是"廉价地证伪"。绝大多数日常 Run 在前几条就能确认可增量,无需触发更重的依赖解析或设备校验。

staging 推进:产物先暂存,成功才提交

进入增量后,本轮所有编译产物先写入 staging 暂存区,而不是直接更新全局基线和部署历史。部署阶段只消费 staging 中本轮有效的产物。

text
检测到的变化文件
  -> 按类型进入各编译链路
  -> 产物写入 staging
  -> 首轮成功后查询是否还有受影响源码
  -> 整轮部署成功后才提交为新的历史状态

这里有一条关键状态边界:只有整轮部署成功后,staging 才提交为新的历史基线;失败轮不能更新全局历史。否则下一轮会以一个不完整的基线继续增量,造成累积性偏差。

成功后的扩散补编译

只编译直接改动的文件并不安全。改动文件本身可能编译通过,但删除方法、修改字段签名或给抽象父类新增抽象方法时,旧的调用方或子类会在运行时抛出 NoSuchMethodErrorAbstractMethodError

一轮成功后,调度层会基于新旧 class 结构对比和引用索引,查出还需要重新编译的源码,把它们当作新输入递归进入下一轮编译。两条去重约束负责让扩散收敛:

  • 首轮成功的文件会从待编译集合移除;后续补编译轮不再更新这组状态,避免把派生出来的重编译文件误当成用户的原始改动。
  • 同一轮 Run 内,对相同影响已经跟编过的源码不再重复跟编;只有出现新的触发来源时才继续下一轮。

扩散补编译的机制细节见重编译 / 扩散编译

失败后的单次重试与回退

增量失败不会立刻回退 Gradle,而是先做一次有针对性的重试解析。重试只进行一次,按以下顺序尝试修复:

  • 出现 unresolved referencecannot find symbol 这类符号缺失错误时,刷新 Git 变化,把工程关闭期间或切分支带来的遗漏文件补进本轮。
  • 命中依赖缺失类错误时,刷新编译上下文后再编一次。

重试仍失败时,分两种收口:如果失败不可自动回退,返回当前增量失败结果,并提示下一次运行将走 Gradle;如果可以回退,则进入 Gradle 重建基线。

增量成功后还有一次异步 Git 补检。它只在补检发现"新的待编译文件"时才再触发一轮增量;仅仅是已编译文件的部署状态变化,不会重复触发。

多 APK 下的产物归属

多 APK 工程里,资源、Manifest、assets 这类产物必须带上它真正影响的 APK 集合。调度层按模块所属的 APK 分流编译,一个模块可以属于多个 APK。如果产物丢失了目标 APK 归属,部署阶段会把它发到错误的 APK 或漏发,所以这是分流环节的硬约束。

调度边界

  • 增量失败的自动重试只有一次;一次重试无法恢复就进入回退判定,不会反复重试。
  • 扩散范围越大,本轮要编译的文件越多;扩散范围超过增量收益区间时,分层判定会回退 Gradle。
  • 调度依赖一个可信的 Gradle 基线。构建脚本、依赖、注解处理器或生成代码无法由本轮增量结果确认时,必须回到 Gradle 刷新基线(见回退与限制)。

相关页面