你要是做过 Unity、iOS、Android、或者任何需要“打包交付”的活,大概率都经历过这种人生惨剧:
- 早上 10 点:产品说“给测试一个包看看”
- 中午 12 点:测试说“再来一个修复版”
- 下午 3 点:运营说“渠道包要十个,今晚投放”
- 晚上 8 点:老板说“iOS 也要,顺便上传 TestFlight”
- 晚上 11 点:你电脑风扇像直升机,证书突然掉了,Gradle 还报红
然后你发现:你一天没写几行业务代码,全在“打包、签名、传包、发群、解释为什么失败”。
这时候你终于明白:
打包这件事不该靠人。
人应该做创造性的事情,打包这种重复劳动应该交给机器。
这台机器就叫CI(Continuous Integration,持续集成)。
用一句大白话概括本文:
CI 就是你的“自动打包小弟”,你把流程写成脚本,它负责跑、负责签名、负责归档、负责把结果送到你面前。
下面我们就用大白话把 CI 讲透:
它到底负责啥、Jenkins/GitLab CI/GitHub Actions 三个怎么选、怎么搭一套“跑脚本 + 签名 + 归档”的流水线、以及最关键的坑:签名资产怎么管、归档怎么做才可追溯、失败怎么定位。
1. 先把 CI 说成人话:从“手工厨房”变成“中央厨房”
没有 CI 的打包交付像什么?
像一家小饭馆:
每次有订单,你就跑去厨房现做,盐多盐少全凭手感,锅糊了就重来,做完还要你自己打包、自己送外卖。
有 CI 之后像什么?
像中央厨房:
菜谱写清楚(脚本),做菜流程固定(流水线),谁来下单都按同一套标准出餐,做完自动封装、贴标签、入库,顺手发短信告诉你“外卖到了”。
大白话总结:
- 脚本:菜谱
- Runner/Agent:厨师 + 厨房
- Pipeline:出餐流程(切菜→炒菜→装盒→贴单→入库)
- Artifact:装盒后的成品(APK/IPA/Zip/日志)
- Signing:盖章(这包是“官方出品”,不是野包)
- Archive/归档:入库 + 可追溯(想找哪次出餐都能找回)
2. CI 负责的“三件大事”:跑脚本、签名、归档
题目已经把核心写出来了:
CI 在打包交付里通常就干这三件事。
2.1 跑脚本:把“点点点”变成“跑跑跑”
你手工打包要点:
- 切平台
- 改版本号
- 选 Development/Release
- Build
- 导出 Xcode/Gradle
- 再去 Xcode/Gradle 编译
- 再上传
CI 的思路是:
把这堆动作写成命令行脚本,然后在固定环境里自动执行。
Unity 常见是:
Unity -batchmode -executeMethod
Android 常见是:./gradlew assembleRelease / bundleRelease
iOS 常见是:xcodebuild archive+xcodebuild -exportArchive
再加上 fastlane:fastlane pilot(上传 TestFlight)fastlane supply(上传 Google Play)
2.2 签名:让包“合法”,并且“能升级”
签名这件事是打包里最容易翻车的环节:
- Android:keystore、alias、密码
- iOS:证书、私钥、Provisioning Profile、Team、Bundle ID
CI 必须做两件事:
- 安全地拿到签名材料(不能明文放仓库)
- 稳定地完成签名(不靠某个人电脑)
2.3 归档:让每一次构建都可追溯、可回滚、可复现
归档不是“把包扔群里”。归档应该包含:
- 安装包(APK/AAB/IPA)
- 符号文件(Android mapping.txt、iOS dSYM)
- 构建日志(Unity.log、Gradle log、xcodebuild log)
- 构建元信息(commit hash、分支、构建号、渠道、环境、版本号)
- 依赖/配置快照(ExportOptions.plist、gradle.properties 的脱敏版本等)
大白话:
归档就是“把这次做饭用的菜谱、食材批次、做菜录像、成品照片都存起来”。
下次出问题,你才能追责、复盘、重做、回滚。
3. Jenkins / GitLab CI / GitHub Actions:三兄弟怎么选?
它们都能干活,但气质不一样。
3.1 Jenkins:老牌“自建工厂”,自由度最高,也最需要运维
优点:
- 插件多、生态成熟
- 自由度很高,想怎么玩都行
- 适合复杂流水线、大公司多项目统一管理
- 可以接各种自建机器(macOS 构建机、Windows 构建机)
缺点:
- 需要你自己维护 Jenkins 服务
- 插件多也意味着“插件地狱”
- 安全、权限、升级都得有人管
适用:
- 你们公司有运维/DevOps
- 需要自建 macOS 节点(iOS 打包)
- 有复杂的内部流程(审批、签名机、制品库)
3.2 GitLab CI:自带“流水线基因”,适合代码在 GitLab 的团队
优点:
- 和 GitLab 仓库深度绑定
.gitlab-ci.yml配起来统一- Runner 机制成熟,自建机器也方便
- 权限、变量、制品、环境管理都比较顺
缺点:
- 依赖 GitLab 的平台
- 某些高级玩法仍需要自己折腾 Runner 与缓存
适用:
- 代码在 GitLab
- 想要“仓库即流水线”的体验
- 团队规模中等到大,想把 CI 当基础设施
3.3 GitHub Actions:云端“拎包入住”,开源项目和轻量团队很爽
优点:
- 仓库里放个 workflow 就能跑
- Marketplace action 多
- 对开源项目、跨平台构建很方便
- 生态和 GitHub 强绑定(PR、Release、Tag)
缺点:
- 私有仓库、大型构建可能会受限于额度/并发
- iOS 构建需要 macOS runner(费用/资源)
- 一些公司对代码出云有合规顾虑
适用:
- 代码在 GitHub
- 想要快速上手
- 团队不想自己维护 CI 服务
大白话选型口诀:
- 想“自己开厂”→ Jenkins
- 用 GitLab、想“内网一体化”→ GitLab CI
- 用 GitHub、想“省事上云”→ GitHub Actions
4. 一条能落地的流水线长什么样?(通用模板)
我们先给一个“跑脚本 + 签名 + 归档”的通用流程,你不管用谁都差不多:
Stage 0:准备(Prepare)
- 拉代码(checkout)
- 拉 LFS(如果有)
- 初始化子模块
- 读取构建参数(分支、环境、渠道、版本号策略)
Stage 1:跑 Unity 构建脚本(Build Unity)
- Unity batchmode 执行
-executeMethod - 导出 Android Gradle 工程 / iOS Xcode 工程
- 生成 build_info.json
- 保存 Unity Editor.log(非常关键)
Stage 2:平台编译(Build Platform)
- Android:
./gradlew assembleRelease或bundleRelease - iOS:
pod install(如需)→xcodebuild archive→xcodebuild export
Stage 3:签名(Sign)
- Android:Gradle signingConfigs / apksigner
- iOS:codesign + profile(一般由 Xcode/fastlane 接管)
Stage 4:归档与发布(Archive & Distribute)
- 归档产物:APK/AAB/IPA、dSYM、mapping.txt、日志、build_info
- 上传制品库:Nexus/Artifactory/S3/OSS
- 上传分发平台:蒲公英、Firebase App Distribution、TestFlight
- 发通知:飞书/钉钉/Slack/邮件(带下载链接和构建信息)
Stage 5:清理(Cleanup)
- 清缓存(可选)
- 清 keychain 临时证书(iOS)
- 清临时文件
5. CI 里最难的其实是“签名”:怎么做到安全又稳定?
签名材料有一个共同点:
它们都不能随便放,但又必须让 CI 机器拿到。
5.1 Android 签名:keystore 的正确保管姿势
错误做法:
- keystore 放仓库
- 密码写在脚本里
- keystore 发群里
正确做法:
- keystore 放在 CI 的“秘密变量/文件变量”里
- 或放在私有制品库,CI 运行时拉取
- 密码走 CI Secret(Jenkins Credentials/GitLab CI Variables/GitHub Secrets)
常用套路:
- keystore 以 base64 字符串存在 secret 里
- CI 运行时写回文件
release.keystore
大白话:
keystore 是公章,你可以放保险柜里,但不能贴在办公室门上。
5.2 iOS 签名:证书+私钥+Profile,三件套缺一不可
iOS 签名更麻烦,因为它是“三件套”:
- 证书(.cer)
- 私钥(.p12,或者 Keychain 里的私钥)
- Provisioning Profile(.mobileprovision)
CI 要做的事:
- 把 p12 导入到构建机的 keychain(建议临时 keychain)
- 安装 provisioning profile 到
~/Library/MobileDevice/Provisioning Profiles/ - 确保 Xcodebuild 能找到匹配的 team/bundle id/profile
更高级的做法是 fastlane match:
- 证书和 profile 存在加密仓库
- CI 用密钥解密拉取
- 保证团队所有机器签名一致
大白话:
iOS 签名像“三把钥匙开一扇门”,少一把都进不去。
match 就是“把钥匙统一放一个加密钥匙柜”,谁需要谁去拿。
6. 归档到底怎么做才算“专业”?别只存一个安装包
很多团队的归档是:
“打出来的包扔到群里/网盘里,名字叫 final_final_3.apk。”
出了线上崩溃你就会傻眼:
没有符号文件,没法还原堆栈;不知道是哪次提交;不知道是哪套宏;回滚也找不到旧包。
6.1 归档清单(建议固定模板)
每次构建至少归档这些:
- 安装包:
- Android:
app-release.apk/app-release.aab - iOS:
App.ipa
- Android:
- 符号文件:
- Android(IL2CPP):
symbols.zip(或 Unity 生成的符号) - Android(Proguard/R8):
mapping.txt - iOS:
dSYM.zip
- Android(IL2CPP):
- 构建日志:
- Unity
Editor.log - Gradle log
- xcodebuild log
- Unity
- 构建信息:
build_info.json- commit、branch、pipeline id、version、channel、env
- 导出参数快照:
- iOS ExportOptions.plist(脱敏)
- Android 的 gradle 配置(脱敏)
6.2 归档命名规范(别小看它,救命)
推荐命名:
{project}_{platform}_{env}_{channel}_v{version}_{buildNumber}_{commitShort}例子:
MyGame_android_prod_huawei_v1.8.0_10234_a1b2c3d.apkMyGame_ios_staging_appstore_v1.8.0_567_a1b2c3d.ipa
大白话:
包名写清楚,等于你给每个包贴了身份证。
没身份证,迟早“找不到人”。
7. 三个平台的落地示例(不贴大段 YAML/Jenkinsfile,但把思路讲清)
7.1 Jenkins 怎么干(核心点)
- 用 Pipeline(Jenkinsfile)
- 节点(agent)分平台:
- Android 可以 Linux/Windows/macOS
- iOS 必须 macOS
- Credentials 管理:
- Android keystore 文件
- iOS p12 + 密码
- App Store Connect API Key(用于上传)
建议:
- iOS 构建机尽量固定一台或几台专用 Mac mini
- 构建前创建临时 keychain,构建后删掉
7.2 GitLab CI 怎么干(核心点)
.gitlab-ci.yml写 stages- GitLab Runner 自建:
- 安卓 runner 可以 docker 化
- iOS runner 需要 shell executor + macOS 机器
- CI/CD Variables 存 secret
- artifacts + cache:
- artifacts 存产物
- cache 存 Unity Library/Gradle 缓存提升速度
7.3 GitHub Actions 怎么干(核心点)
.github/workflows/build.yml- runner 选
windows-latest / ubuntu-latest / macos-latest - secrets 存 keystore/p12
- artifacts 上传产物
- release 或 tag 触发发布
注意:
- macos runner 成本较高,建议只在需要 iOS 时触发
- Unity License 激活需要处理(用 Unity 官方 action 或自建)
8. 提升效率的“隐藏大招”:缓存、并行、分层
CI 之所以让人又爱又恨,是因为它可能很慢。
想快,主要靠三招:
8.1 缓存 Unity Library(但要小心)
Library 缓存能大幅减少导入时间,但要注意:
- Unity 版本变更要刷新缓存
- 平台切换会导致缓存失效
- 缓存键要包含 Unity 版本 + 平台 + 分支
8.2 缓存 Gradle / CocoaPods
~/.gradle~/.cocoapodsPods/(视情况)
8.3 并行构建
- Android 与 iOS 可以并行(前提是资源与脚本能并行)
- 多渠道包可以 matrix 并行(但注意签名与配置隔离)
大白话:
缓存像“预制菜”,并行像“开两个灶”。
你不想快都难。
9. 失败怎么定位?CI 不是黑盒,关键看日志和分段
很多人觉得 CI 难,是因为失败时只看到一屏红字。
解决方法是“分段 + 关键日志抓取”。
建议每个 stage 都把日志独立保存,并在失败时:
- 输出最后 200 行关键日志
- 上传完整日志作为 artifact
- 对常见错误做关键词提示(可选)
例如:
- iOS codesign 失败就提示:检查 bundleId、profile、teamId
- Gradle 依赖冲突提示:检查 duplicate class
10. 一套“能上生产”的 CI 最终长什么样?(目标态)
当你把 CI 真正跑顺,它会变成这样:
- 开发合并到
develop→ 自动打测试包→ 上传分发平台 → 群里发链接 - 打 tag(例如
v1.8.0)→ 自动打正式包:- Android AAB 上传 Google Play(或生成渠道 APK)
- iOS IPA 上传 TestFlight/App Store
- 自动生成 Release Note(从 commit/PR 里提取)
- 自动归档符号与日志到制品库
- 出问题随时回滚:
- 找到某次构建产物 → 一键重发/重签/重传
大白话:
你从“打包工”升级成“按按钮发车的人”。
车怎么造、怎么检修、怎么开,都在流水线里。
11. 结尾:CI 不是工具,是“把团队从重复劳动里解放出来”的生产力
CI 最值钱的点从来不是“省一次打包时间”,而是:
- 打包变得稳定
- 交付变得可追溯
- 新人上手不靠口口相传
- 出问题能快速定位
- 多渠道、多环境不再恐怖
你们团队的效率,很大一部分就藏在这条流水线里。