CI/CD集成:Jenkins/GitHub Actions/GitLab CI配置、Gradle参数化构建、并行任务、报告上传、自动化发布
CI/CD,说白了就是让机器替我们干那些重复的体力活。我刚开始做Android开发那会儿,每次发版都得手动打包、签名、上传,折腾下来大半天就没了。后来我意识到,这些流程完全可以自动化。今天我们就聊聊怎么把Gradle脚本和CI/CD工具结合起来,让构建、测试、发布一气呵成。
核心思路:Gradle脚本是构建的“大脑”,CI/CD工具是“手脚”。你只需要把参数暴露出来,剩下的交给流水线去调度。
CI/CD工具的选择与对比
目前主流的CI/CD工具有三个:Jenkins、GitHub Actions、GitLab CI。我三个都用过,简单说说我的感受。
| 工具 | 托管方式 | 配置语言 | 适合场景 |
|---|---|---|---|
| Jenkins | 自托管 | Groovy / Pipeline DSL | 企业内部、复杂流水线 |
| GitHub Actions | 云托管 | YAML | 开源项目、中小团队 |
| GitLab CI | 云托管/自托管 | YAML | GitLab深度用户 |
我个人习惯是:如果公司有现成的Jenkins集群,就用Jenkins;如果是个人项目或小团队,GitHub Actions最省心。GitLab CI嘛,如果你已经在用GitLab管理代码,那自然是最顺手的。
Gradle参数化构建
要让CI/CD灵活起来,第一步就是把构建参数暴露出来。你想想看,如果每次改版本号、改构建类型都要改代码,那还叫什么自动化?
我一般在项目的根目录build.gradle.kts里定义一些可配置的参数:
// 根目录 build.gradle.kts
ext {
set("appVersionName", project.findProperty("versionName") ?: "1.0.0")
set("appVersionCode", (project.findProperty("versionCode") ?: "1").toString().toInt())
set("buildType", project.findProperty("buildType") ?: "debug")
}
然后在app模块的build.gradle.kts里引用:
android {
defaultConfig {
versionName = rootProject.ext.get("appVersionName") as String
versionCode = rootProject.ext.get("appVersionCode") as Int
}
buildTypes {
getByName("release") {
isMinifyEnabled = true
// 只有传了 buildType=release 才启用混淆
if (rootProject.ext.get("buildType") != "release") {
isMinifyEnabled = false
}
}
}
}
这样做的好处是,CI/CD工具只需要通过-P参数传值就行。比如:
./gradlew assembleRelease -PversionName=2.0.0 -PversionCode=10 -PbuildType=release
小技巧:我习惯把敏感信息(比如签名密码、API Key)放在gradle.properties里,但这个文件不提交到Git。CI/CD工具通过环境变量注入,然后在脚本里读取System.getenv()。
并行任务:让构建飞起来
你有没有遇到过这种情况:打包一个release版本要等十几分钟?其实很多任务是可以并行的。Gradle本身就支持并行执行,你只需要在gradle.properties里开启:
org.gradle.parallel=true
org.gradle.caching=true
org.gradle.daemon=true
但要注意,并行不是万能的。我在项目中遇到过一个问题:多个模块同时执行lint任务,结果因为共享同一个输出目录,导致报告互相覆盖。后来我给每个模块单独指定了输出路径:
android {
lintOptions {
xmlOutput = file("${project.buildDir}/reports/lint-${project.name}.xml")
}
}
嗯,这里要注意:并行任务一定要考虑资源竞争,尤其是文件写入和网络请求。
在CI/CD层面,你也可以并行执行多个Job。比如GitHub Actions的矩阵策略:
jobs:
build:
strategy:
matrix:
api-level: [21, 29, 33]
steps:
- run: ./gradlew connectedCheck -PapiLevel=${{ matrix.api-level }}
这样三个API级别的测试可以同时跑,总时间从30分钟降到10分钟。
报告上传:让结果看得见
构建完了,测试跑完了,报告得有个地方放。我见过不少团队,测试报告只存在CI机器的临时目录里,出了问题还得去翻日志,太原始了。
我建议把报告上传到CI平台自带的Artifact存储里。以GitHub Actions为例:
- name: Upload Lint Report
uses: actions/upload-artifact@v3
with:
name: lint-report
path: app/build/reports/lint-results-*.html
- name: Upload Unit Test Report
uses: actions/upload-artifact@v3
with:
name: unit-test-report
path: app/build/reports/tests/
如果是Jenkins,可以用publishHTML插件:
publishHTML(target: [
reportName: 'Lint Report',
reportDir: 'app/build/reports',
reportFiles: 'lint-results.html'
])
我曾经踩过的坑:有一次Jenkins的HTML报告一直显示空白,查了半天发现是Jenkins默认开启了CSRF保护,导致内嵌的CSS和JS被拦截。解决办法是在Jenkins系统设置里关闭“Enable security”中的“Prevent Cross Site Request Forgery”选项,或者改用纯静态HTML报告。
自动化发布:一键上架
自动化发布的终极目标是什么?是“代码合并到主分支,然后睡一觉醒来发现App已经上架了”。
我常用的流程是这样的:
- 打Release包:用
./gradlew assembleRelease,同时传入版本号参数。 - 签名:签名信息从环境变量读取,不写死在脚本里。
- 上传到分发平台:比如Firebase App Distribution、蒲公英、或者直接上传到Google Play。
- 打Tag:自动给Git打上版本标签。
- 通知团队:发个Slack消息或者邮件。
下面是一个GitHub Actions的完整示例:
name: Release to Play Store
on:
push:
branches: [ main ]
jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up JDK 17
uses: actions/setup-java@v3
with:
java-version: '17'
- name: Build Release APK
run: ./gradlew assembleRelease -PversionName=${{ github.ref_name }}
- name: Sign APK
env:
KEYSTORE_BASE64: ${{ secrets.KEYSTORE_BASE64 }}
KEY_ALIAS: ${{ secrets.KEY_ALIAS }}
KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }}
run: |
echo $KEYSTORE_BASE64 | base64 -d > keystore.jks
jarsigner -keystore keystore.jks -storepass $KEY_PASSWORD \
-keypass $KEY_PASSWORD app/build/outputs/apk/release/app-release-unsigned.apk \
$KEY_ALIAS
- name: Upload to Google Play
uses: r0adkll/upload-google-play@v1
with:
serviceAccountJsonPlainText: ${{ secrets.SERVICE_ACCOUNT_JSON }}
packageName: com.example.app
releaseFiles: app/build/outputs/apk/release/app-release-unsigned.apk
track: production
我的建议:不要把Google Play的Service Account JSON直接放在代码里。用GitHub的Secrets功能存Base64编码后的内容,在CI里解码使用。这样既安全又方便。
知识体系总览
下面这张图是我整理的CI/CD集成核心逻辑,你可以对照着看:
从这张图可以看出来,整个流程是线性的,但中间有并行分支。我个人觉得,CI/CD的核心不是工具本身,而是你如何设计这个流程。参数化构建是基础,并行任务是加速器,报告上传是保障,自动化发布是最终目标。
好了,这一章的内容就到这里。记住一句话:让机器做机器擅长的事,让人做人擅长的事。CI/CD就是帮你实现这个目标的工具。