27、构建与CI/CD深度集成:构建矩阵(Matrix Build)、并行构建策略、构建产物自动化测试、构建通知与告警
说实话,很多团队把CI/CD理解得太简单了。
不就是配个Jenkins,写个脚本,每天跑一遍吗?
嗯,如果你只是一个人写个小Demo,这样确实够了。但当你面对几十个模块、几百种设备、多个构建变体的时候——你会发现,构建这件事本身,就是一个需要精心设计的系统。
今天我们就来聊聊,怎么把Android构建真正“嵌入”到CI/CD流水线里。我会从四个核心维度展开:构建矩阵、并行策略、自动化测试、通知告警。每个点我都会结合我踩过的坑来讲。
核心观点:CI/CD不是“自动执行脚本”,而是“自动做决策”。构建矩阵解决的是“测什么”,并行策略解决的是“怎么快”,自动化测试解决的是“准不准”,通知告警解决的是“谁负责”。
27.1 构建矩阵(Matrix Build)—— 别让“漏测”成为你的噩梦
先问一个问题:你的App支持多少种屏幕密度?多少种API级别?多少种CPU架构?
我见过一个项目,只在API 30上测试通过,结果用户用API 26的手机打开就闪退。为什么?因为构建矩阵没配好。
构建矩阵,说白了就是“排列组合”。你把所有需要验证的维度列出来,系统自动生成所有组合的构建任务。
在GitHub Actions里,矩阵配置长这样:
jobs:
build:
strategy:
matrix:
api-level: [21, 26, 30, 33]
abi: [x86_64, armeabi-v7a, arm64-v8a]
density: [mdpi, hdpi, xhdpi, xxhdpi]
steps:
- uses: actions/checkout@v3
- name: Run tests on ${{ matrix.api-level }} / ${{ matrix.abi }}
run: ./gradlew testDebug --api-level=${{ matrix.api-level }}
你看,这样一次就能跑4×3×4=48个组合。但注意——不是组合越多越好。
我曾经踩过的坑:一开始我把所有维度都加上了,结果一次构建跑了200多个任务,CI排队排了3小时。后来我学乖了——只保留“高风险组合”。比如API 21 + arm64-v8a这种老设备+新架构的组合,才是真正容易出问题的。
我建议你按这个优先级来筛选矩阵维度:
| 优先级 | 维度 | 说明 |
|---|---|---|
| P0 | API Level | 最低支持版本 + 主流版本 + 最新版本 |
| P1 | CPU架构 | arm64-v8a必测,x86_64可选 |
| P2 | 屏幕密度 | 只测hdpi和xxhdpi两个极端 |
| P3 | 语言/地区 | 按用户分布比例抽样 |
27.2 并行构建策略——别让CPU闲着
你想想看,一个大型Android项目,光编译就要10分钟。如果串行跑48个矩阵组合,那就是480分钟——8个小时。黄花菜都凉了。
并行构建的核心思路就一句话:把大任务拆成小任务,同时跑。
在CI/CD里,并行策略通常分两层:
- 任务级并行:不同的矩阵组合同时跑。这个CI平台一般自带支持。
- 模块级并行:单个构建任务内部,利用Gradle的并行编译能力。
Gradle的并行配置很简单:
# gradle.properties
org.gradle.parallel=true
org.gradle.caching=true
org.gradle.jvmargs=-Xmx4096m -XX:MaxMetaspaceSize=1024m
但这里有个坑——并行不是越多越好。我记得有一次我把并行数设成了16,结果CI机器直接OOM了。为什么?因为每个并行任务都要占用内存,你设的并行数不能超过机器的CPU核心数。
我的经验值:对于4核8G的CI机器,并行数设4最稳。8核16G的机器,设6-8就够了。别贪心,留点内存给系统。
另外,我强烈建议你开启构建缓存。Gradle的构建缓存可以复用上次编译的结果,尤其是那些没改动的模块。配合并行策略,能把构建时间压缩到原来的1/3甚至更少。
27.3 构建产物自动化测试——别让“能编译”骗了你
能编译通过,不代表能跑起来。这个道理我是在一次线上事故里深刻体会到的。
那次我们的CI只跑了单元测试,结果发布后用户反馈登录页面白屏。查了半天,发现是一个资源文件路径写错了,单元测试根本覆盖不到。
所以,构建产物的自动化测试,至少要覆盖三层:
- 单元测试:跑得最快,验证逻辑正确性。用JUnit + Mockito就行。
- 集成测试:验证模块间交互。比如网络请求+数据库读写。
- UI测试:验证界面渲染和用户操作。用Espresso或Compose UI Test。
在CI流水线里,我习惯这样安排:
jobs:
test:
steps:
- name: Unit tests
run: ./gradlew testDebugUnitTest
- name: Integration tests
run: ./gradlew connectedDebugAndroidTest
- name: UI tests (on emulator)
run: |
avdmanager create avd -n testDevice -k "system-images;android-30;google_apis;x86_64"
emulator -avd testDevice -no-window &
./gradlew :app:connectedDebugAndroidTest
注意看,UI测试我用了模拟器。这里有个小技巧——启动模拟器时加 -no-window 参数,这样不会弹出图形界面,节省资源。
我曾经踩过的坑:模拟器启动需要时间,如果CI任务超时了,测试还没开始跑。我的解决方案是——在启动模拟器后加一个等待循环,检测到模拟器完全启动后再执行测试命令。
27.4 构建通知与告警——别让失败没人管
构建失败了,没人知道。这种情况我见过太多次了。
有一次,我们的CI因为一个依赖库版本冲突挂了整整两天,直到开发同学手动触发构建才发现。为什么?因为通知配置没做好。
通知策略,我建议按“严重程度”分级:
| 级别 | 触发条件 | 通知方式 | 接收人 |
|---|---|---|---|
| 🔴 紧急 | 主分支构建失败 | 电话/短信 + 即时消息 | 团队负责人 + 构建责任人 |
| 🟡 警告 | 功能分支构建失败 | 即时消息 | 提交代码的开发同学 |
| 🔵 信息 | 构建成功但测试覆盖率下降 | 邮件/日报 | 全体开发 |
在GitHub Actions里配置通知,可以这样写:
jobs:
notify:
needs: build
if: failure()
steps:
- name: Send Slack notification
uses: slackapi/slack-github-action@v1.24.0
with:
payload: |
{
"text": "❌ 构建失败: ${{ github.repository }}/${{ github.run_id }}",
"channel": "#build-alerts"
}
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK }}
我个人习惯再加一个“静默期”机制——同一个分支连续失败超过3次,才升级为紧急通知。不然开发同学改个代码就收到一堆告警,反而会麻木。
一个小建议:通知内容里一定要包含“谁提交的代码”和“哪个模块出了问题”。这样收到通知的人能立刻定位问题,不用再去翻日志。
27.5 整体架构:把这些串起来
好了,上面四个部分都讲完了。但它们是孤立的吗?当然不是。
我画了一张图,展示它们之间的关系:
从这张图你能看到,整个流程是串行的——代码提交后,先经过构建矩阵筛选出需要测试的组合,然后并行构建,构建产物自动测试,最后根据结果发送通知。任何一个环节失败,都会阻断后续流程。
嗯,这就是我理解的“深度集成”。不是简单地把几个工具拼在一起,而是让它们形成一个有机的整体,自动决策、自动执行、自动反馈。
最后说一句:CI/CD的终极目标,是让开发者只关心写代码,其他事情交给系统。你配得越好,团队就越轻松。
公众号:蓝海资料掘金营,微信deep3321