03 - 发布实战与排查手册
维度:发布操作步骤、真实实战记录、错误排查、踩坑清单、死代码清理、编译路线对比 本文基于 2026-07-14 两次真实发布(失败 → 成功)的完整记录
一、发布操作步骤
1.1 前置检查清单
cd /home/zbc/car/MiCarSettings
# 1. 账号是否配到正确来源(env 或 project.properties,不是 local.properties)
grep -q "^artifactory_user=" project.properties && echo "✅ project.properties 有账号" \
|| ([ -n "$artifactory_user" ] && echo "✅ 环境变量有账号" || echo "❌ 账号未配置或只在 local.properties")
# 2. skip 标志是否注释(app/build.gradle:174)
grep -q "^[^/]*artifactoryPublish.skip" app/build.gradle && echo "❌ skip 仍生效" || echo "✅ skip 已注释/移除"
# 3. 签名密钥存在
ls platform.keystore && echo "✅ keystore 就绪"
# 4. 磁盘空间(clean 全量编译需数 GB)
df -h . | tail -11.2 执行发布
# 方式一:用脚本(含 clean,全量编译,约 3-15 分钟)
./project_publish_xcd_app.sh # 或 dcd / global
# 方式二:手动(推荐,可控制是否 clean)
./gradlew clean && ./gradlew updateApkAssembleXcdRelease # 首次/代码变更后
./gradlew updateApkAssembleXcdRelease # 增量重发(apk 没变只重传,约 10-60s)1.3 验证结果
# 看真实结果(不是后台任务 exit code)
grep -E "BUILD SUCCESSFUL|BUILD FAILED" /tmp/micar_publish_xcd.log
grep "Deploying artifact" /tmp/micar_publish_xcd.log # 上传的制品 URL二、实战记录(2026-07-14)
2.1 第一次:失败(401)
sequenceDiagram participant Dev as 开发者 participant Gradle as Gradle participant Deploy as artifactoryDeploy participant Art as Artifactory Dev->>Gradle: clean + updateApkAssembleXcdRelease Note over Gradle: loadBuildConfig:<br/>artifactory_user=null(账号在<br/>local.properties,未被读) Gradle->>Gradle: 编译十几个模块 2m53s Gradle->>Gradle: assembleXcdRelease → 371MB apk ✅ Gradle->>Deploy: artifactoryDeploy Deploy->>Art: PUT apk(无认证/空账号) Art-->>Deploy: 401 Unauthorized ❌ Deploy-->>Gradle: BUILD FAILED
诊断过程:
- 日志
loadBuildConfig: artifactory_user=null(但注意这是 env 阶段打印,本身不证明最终为空) - 确认账号配在
local.properties,但loadBuildConfig只读env/project.properties(build.gradle:183-187) project.properties只有 versionName/versionCode,无账号- 根因:账号读取源不匹配 → 上传时空账号 → 401
2.2 修复
把账号配到 loadBuildConfig 能读到的来源(project.properties 或环境变量)。详见 02-认证机制 §四。
2.3 第二次:成功
./gradlew updateApkAssembleXcdRelease # 不 clean,apk 已存在> Task :app:assembleXcdRelease UP-TO-DATE ← 跳过编译
> Task :app:artifactoryPublish
[pool-17-thread-1] Deploying artifact: .../releases/com/mi/car/MiCarSettings/1.0.0.194-dev-xcd/MiCarSettings-1.0.0.194-dev-xcd.apk
[pool-17-thread-1] Deploying artifact: .../releases/com/mi/car/MiCarSettings/1.0.0.194-dev-xcd/MiCarSettings-1.0.0.194-dev-xcd.pom
> Task :artifactoryDeploy
BUILD SUCCESSFUL in 13s
产物:com.mi.car:MiCarSettings:1.0.0.194-dev-xcd(apk 371MB + pom),仓库 releases。
三、错误码对照
| HTTP 码 | 含义 | 根因 | 解法 |
|---|---|---|---|
| 401 Unauthorized | 认证失败 | 账号 null / 错误 / 没读到 | 配账号到 env 或 project.properties;见 02 |
| 403 Forbidden | 权限不足 | 账号对但无 releases 写权限 | 找管理员开仓库写权限;或改用有权限的仓库 |
| 409 Conflict | 版本冲突 | releases 不允许覆盖已存在版本 | 递增 versionName/project.properties 的 versionCode |
ResolveException(extractModule) | jfrog 解析依赖图失败 | 某依赖解析不出 | 非致命,apk/pom 仍上传;只影响 build info |
四、踩坑清单(详解)
坑 1:账号读取源不一致(最常见,导致 401)
- 现象:
401 Unauthorized,loadBuildConfig: artifactory_user=null - 根因:账号配在
local.properties,但loadBuildConfig(build.gradle:183-187)只读env/project.properties - 误导:
app/upload.gradle(死代码)声称读 local.properties,但没被 apply - 解法:见 02 §四,三选一
坑 2:skip=true 历史遗留
- 位置:
app/build.gradle:174 - 现象:
./gradlew updateApkAssembleXcdRelease执行成功但 Artifactory 里没有制品 - 根因:
artifactoryPublish.skip = true让上传被跳过(任务执行但 deploy 0 个文件) - 解法:注释掉(当前已是
//artifactoryPublish.skip = true)
坑 3:死代码误导
- 位置:
app/publish.gradle、app/upload.gradle - 现象:看代码以为账号从 local.properties 读,实际不生效
- 解法:清理(见 §五)
坑 4:后台任务 exit code 掩盖结果
- 现象:
run_in_background的 gradle 任务报 exit 0,但实际 BUILD FAILED - 根因:脚本末尾
echo "EXIT_CODE=$?"的 echo 本身 exit 0,掩盖了 gradle 的 exit 1 - 解法:判断成败只看日志
BUILD SUCCESSFUL/FAILED,不看后台任务 exit code
坑 5:project.properties 泄露风险
- 现象:把账号放 project.properties 后
git commit/push→ 密码入库 - 根因:project.properties 被 git 跟踪
- 解法:账号优先环境变量;若用 properties,
git status确认未提交,或改用 local.properties + 方式 C(改代码)
坑 6:print 误导
- 现象:
loadBuildConfig: artifactory_user=null让人以为账号没配 - 根因:
build.gradle:172的 print 在读 env 之后、读 properties 之前 - 解法:以
Deploying后有无 401 为准,不以 print 为准
五、死代码清理建议
app/publish.gradle 和 app/upload.gradle 没被任何文件 apply(grep apply from 全仓确认),是旧配置。建议:
# 确认无引用
grep -rn "publish.gradle\|upload.gradle" --include="*.gradle" . | grep -v "/build/"
# 应只看到 app/publish.gradle:1: apply from: "upload.gradle" (自身引用,无外部 apply)
# 删除(确认后)
git rm app/publish.gradle app/upload.gradle清理收益:消除”账号读 local.properties”的误导,降低 401 排查成本。
若想保留 local.properties 配账号的能力,先把
upload.gradle的loadArtifactoryAccountInfo逻辑搬到生效的loadBuildConfig(见 02 方式 C),再删死代码。
六、两条编译路线对比
| 路线 | 命令 | 产物 | 用途 |
|---|---|---|---|
| Artifactory 发布 | ./project_publish_xcd_app.sh 或 ./gradlew updateApkAssembleXcdRelease | APK 上传到 Artifactory(com.mi.car:MiCarSettings) | 独立 APK 发布,供 CI/OTA 拉取 |
| 源码树编译 | . build/envsetup.sh && lunch watt-userdebug && make CarSettings(build.sh) | 编进 Android 系统镜像 | 系统 priv-app 集成,出整机镜像 |
两条路线并存:发布脚本给独立 APK 用,build.sh 给系统镜像用。
七、快速诊断命令合集
# 发布日志关键字
grep -E "BUILD SUCCESSFUL|BUILD FAILED|EXIT_CODE=" /tmp/micar_publish_xcd.log
grep -E "Deploying artifact|Status code: 401|Unauthorized" /tmp/micar_publish_xcd.log
grep -A12 "What went wrong" /tmp/micar_publish_xcd.log
# 账号配置检查(不打印密码值)
grep -c "^artifactory_user=" project.properties local.properties 2>/dev/null
[ -n "$artifactory_user" ] && echo "env 有账号" || echo "env 无账号"
# skip 状态
grep -n "skip" app/build.gradle
# 制品/产物
ls -la app/build/outputs/apk/xcd/release/*.apk
# git 跟踪检查(避免密码入库)
git ls-files | grep -E "^(project|local)\.properties$"八、发布速查(复制即用)
# === 一次性配账号(选其一) ===
# A. 环境变量(推荐)
export artifactory_user='XXX'; export artifactory_password='XXX'
# B. 或写入 project.properties(注意别 commit)
# === 发布 ===
./gradlew updateApkAssembleXcdRelease 2>&1 | tee /tmp/publish.log
# === 看结果 ===
grep -E "BUILD SUCCESSFUL|BUILD FAILED|Deploying artifact|401" /tmp/publish.log