HarmonyOS开发:自动化部署
HarmonyOS开发:自动化部署
📌 核心要点:hdc是鸿蒙的adb——掌握它的命令体系,HAP安装、多设备批量部署、部署验证全都能自动化,让"构建完即部署"成为现实。
背景与动机
构建完了,测试也过了,然后呢?
你拿着HAP文件,打开DevEco Studio,连上设备,点击运行,等安装完成,再手动验证功能是否正常。一台设备还好,五台设备呢?十个测试同学每人一台呢?每次发版都要手动装一遍?
更要命的是,手动部署容易出错。你确定装的是最新版本?确定装到了所有测试设备?确定每台设备上应用都能正常启动?
自动化部署解决的就是这个问题——构建产物自动安装到目标设备,并验证部署是否成功。CI/CD流水线的最后一公里,也是最容易被人忽略的一公里。
鸿蒙的部署工具是hdc(HarmonyOS Device Connector),定位和Android的adb一模一样。但hdc的命令体系和adb有不少差异,直接套adb经验会踩坑。
核心原理
自动化部署的核心流程:HAP产物 → hdc安装 → 启动验证 → 结果反馈。
flowchart TB
A[CI构建产物] --> B[发现目标设备]
B --> C{设备类型}
C -->|本地设备| D[hdc直连]
C -->|远程设备| E[hdc tconn远程连接]
C -->|云设备| F[DevEco Testing API]
D --> G[安装HAP]
E --> G
F --> H[云部署]
G --> I[启动应用]
H --> I
I --> J[部署验证]
J --> K{验证结果}
K -->|启动成功| L[✅ 部署完成]
K -->|启动失败| M[❌ 部署失败+日志采集]
M --> N[问题诊断]
N --> O[重试/回滚]
classDef input fill:#6C5CE7,stroke:#5B4BC9,color:#fff
classDef connect fill:#00B894,stroke:#00A381,color:#fff
classDef process fill:#0984E3,stroke:#0770C2,color:#fff
classDef verify fill:#FDCB6E,stroke:#F0B429,color:#333
classDef success fill:#55EFC4,stroke:#00B894,color:#333
classDef fail fill:#FF7675,stroke:#D63031,color:#fff
classDef recover fill:#74B9FF,stroke:#0984E3,color:#fff
class A input
class D,E,F connect
class G,H,I process
class J,K verify
class L success
class M,N fail
class O recover
hdc vs adb 关键差异:
| 功能 | adb (Android) | hdc (HarmonyOS) |
|---|---|---|
| 安装应用 | adb install | hdc install |
| 卸载应用 | adb uninstall | hdc uninstall |
| 查看设备 | adb devices | hdc list targets |
| 推送文件 | adb push | hdc file send |
| 拉取文件 | adb pull | hdc file recv |
| Shell命令 | adb shell | hdc shell |
| 端口转发 | adb forward | hdc fport |
| 远程连接 | adb connect | hdc tconn |
| 日志查看 | adb logcat | hdc hilog |
看着差不多?但参数和行为有不少差异,后面踩坑部分会详细说。
代码实战
基础用法:HAP自动安装与部署
先掌握hdc的核心命令,这是自动化的基础。
# ===== 设备管理 =====
# 查看已连接设备
hdc list targets
# 远程连接设备(局域网内)
hdc tconn 192.168.1.100:5555
# 断开远程设备
hdc tconn 192.168.1.100:5555 --remove
# ===== 应用安装 =====
# 安装HAP(-r覆盖安装)
hdc install -r entry-default-signed.hap
# 安装指定设备的HAP
hdc -t <device-id> install -r entry-default-signed.hap
# 强制安装(即使签名不一致)
hdc install -r -g entry-default-signed.hap
# ===== 应用管理 =====
# 卸载应用
hdc uninstall com.example.entry
# 启动应用
hdc shell aa start -a EntryAbility -b com.example.entry
# 强制停止应用
hdc shell aa force-stop com.example.entry
# 清除应用数据
hdc shell aa clear -b com.example.entry
# ===== 日志采集 =====
# 实时查看日志
hdc hilog
# 过滤特定标签的日志
hdc hilog | findstr "EntryAbility"
# 导出日志到文件
hdc hilog > app_log.txt
# ===== 文件操作 =====
# 推送文件到设备
hdc file send local_file.txt /data/local/tmp/
# 从设备拉取文件
hdc file recv /data/local/tmp/remote_file.txt ./
一个最基础的自动部署脚本:
#!/bin/bash
# basic_deploy.sh - 基础自动部署脚本
set -e
HAP_FILE="entry/build/default/outputs/default/entry-default-signed.hap"
PACKAGE_NAME="com.example.entry"
ABILITY_NAME="EntryAbility"
# 1. 检查HAP文件
if [ ! -f "$HAP_FILE" ]; then
echo "❌ HAP文件不存在: $HAP_FILE"
exit 1
fi
# 2. 检查设备连接
DEVICES=$(hdc list targets 2>/dev/null | grep -v "^\[Empty\]")
if [ -z "$DEVICES" ]; then
echo "❌ 没有连接的设备"
exit 1
fi
echo "📱 已连接设备:"
echo "$DEVICES"
# 3. 卸载旧版本(可选,避免签名冲突)
echo "🗑️ 卸载旧版本..."
hdc uninstall "$PACKAGE_NAME" 2>/dev/null || true
# 4. 安装新版本
echo "📦 安装新版本..."
hdc install -r "$HAP_FILE"
# 5. 启动应用
echo "🚀 启动应用..."
hdc shell aa start -a "$ABILITY_NAME" -b "$PACKAGE_NAME"
# 6. 等待启动
sleep 3
# 7. 验证部署
echo "🔍 验证部署..."
hdc shell aa dump -l | grep "$PACKAGE_NAME" && echo "✅ 部署成功" || echo "❌ 部署失败"
进阶用法:多设备批量部署
测试环境通常有多台设备,需要批量部署。
#!/bin/bash
# batch_deploy.sh - 多设备批量部署脚本
set -e
HAP_FILE="entry/build/default/outputs/default/entry-default-signed.hap"
PACKAGE_NAME="com.example.entry"
ABILITY_NAME="EntryAbility"
DEPLOY_LOG="deploy_$(date +%Y%m%d_%H%M%S).log"
# 检查HAP文件
if [ ! -f "$HAP_FILE" ]; then
echo "❌ HAP文件不存在"
exit 1
fi
# 获取所有设备列表
echo "📱 扫描设备..."
DEVICES=()
while IFS= read -r line; do
# 过滤掉空行和[Empty]
if [[ -n "$line" && "$line" != "[Empty]" ]]; then
DEVICES+=("$line")
fi
done < <(hdc list targets 2>/dev/null)
if [ ${#DEVICES[@]} -eq 0 ]; then
echo "❌ 没有发现设备"
exit 1
fi
echo "发现 ${#DEVICES[@]} 台设备:"
for device in "${DEVICES[@]}"; do
echo " - $device"
done
# 批量部署
echo ""
echo "===== 开始批量部署 ====="
SUCCESS_COUNT=0
FAIL_COUNT=0
FAIL_DEVICES=()
for device in "${DEVICES[@]}"; do
echo ""
echo "📱 部署到设备: $device"
# 卸载旧版本
hdc -t "$device" uninstall "$PACKAGE_NAME" 2>/dev/null || true
# 安装新版本
if hdc -t "$device" install -r "$HAP_FILE" >> "$DEPLOY_LOG" 2>&1; then
echo " ✅ 安装成功"
# 启动应用
if hdc -t "$device" shell aa start -a "$ABILITY_NAME" -b "$PACKAGE_NAME" >> "$DEPLOY_LOG" 2>&1; then
echo " ✅ 启动成功"
SUCCESS_COUNT=$((SUCCESS_COUNT + 1))
else
echo " ❌ 启动失败"
FAIL_COUNT=$((FAIL_COUNT + 1))
FAIL_DEVICES+=("$device (启动失败)")
fi
else
echo " ❌ 安装失败"
FAIL_COUNT=$((FAIL_COUNT + 1))
FAIL_DEVICES+=("$device (安装失败)")
fi
done
# 部署报告
echo ""
echo "===== 部署报告 ====="
echo "总设备数: ${#DEVICES[@]}"
echo "成功: ${SUCCESS_COUNT}"
echo "失败: ${FAIL_COUNT}"
if [ ${#FAIL_DEVICES[@]} -gt 0 ]; then
echo ""
echo "失败设备列表:"
for fail_device in "${FAIL_DEVICES[@]}"; do
echo " ❌ $fail_device"
done
fi
# 部署失败超过半数则返回错误
if [ $FAIL_COUNT -gt $(( ${#DEVICES[@]} / 2 )) ]; then
echo ""
echo "❌ 部署失败率过高,请检查"
exit 1
fi
echo ""
echo "✅ 批量部署完成"
完整示例:部署验证与回滚
部署完了不算完,还得验证应用确实能正常运行。如果验证失败,自动回滚到上一版本。
// deploy_verifier.ets - 部署验证工具
// 在设备端运行的验证脚本,检查应用是否正常
import { uiObserver } from '@kit.ArkUI';
import { common } from '@kit.AbilityKit';
// 部署验证配置
interface VerifyConfig {
packageName: string;
abilityName: string;
checkPoints: CheckPoint[];
timeout: number; // 超时时间(毫秒)
}
interface CheckPoint {
name: string;
type: 'ability_launch' | 'page_render' | 'network_ready' | 'data_loaded';
expected: string;
}
// 验证结果
interface VerifyResult {
success: boolean;
checkResults: CheckResult[];
duration: number;
error?: string;
}
interface CheckResult {
name: string;
passed: boolean;
message: string;
duration: number;
}
class DeployVerifier {
private config: VerifyConfig;
constructor(config: VerifyConfig) {
this.config = config;
}
/**
* 执行部署验证
*/
async verify(): Promise<VerifyResult> {
const startTime = Date.now();
const results: CheckResult[] = [];
try {
for (const checkPoint of this.config.checkPoints) {
const checkStart = Date.now();
let passed = false;
let message = '';
switch (checkPoint.type) {
case 'ability_launch':
// 验证Ability是否启动成功
const launchResult = await this.checkAbilityLaunch(checkPoint.expected);
passed = launchResult.success;
message = launchResult.message;
break;
case 'page_render':
// 验证页面是否渲染完成
const renderResult = await this.checkPageRender(checkPoint.expected);
passed = renderResult.success;
message = renderResult.message;
break;
case 'network_ready':
// 验证网络是否就绪
const networkResult = await this.checkNetworkReady();
passed = networkResult.success;
message = networkResult.message;
break;
case 'data_loaded':
// 验证数据是否加载完成
const dataResult = await this.checkDataLoaded(checkPoint.expected);
passed = dataResult.success;
message = dataResult.message;
break;
}
results.push({
name: checkPoint.name,
passed,
message,
duration: Date.now() - checkStart
});
// 如果关键检查点失败,立即终止
if (!passed) {
break;
}
}
const allPassed = results.every(r => r.passed);
return {
success: allPassed,
checkResults: results,
duration: Date.now() - startTime
};
} catch (error) {
return {
success: false,
checkResults: results,
duration: Date.now() - startTime,
error: String(error)
};
}
}
private async checkAbilityLaunch(abilityName: string): Promise<{ success: boolean; message: string }> {
try {
// 检查Ability是否在前台运行
// 实际项目中通过AbilityLifecycleCallback监听
await this.waitWithTimeout(3000);
return { success: true, message: `${abilityName} 启动成功` };
} catch (e) {
return { success: false, message: `${abilityName} 启动失败: ${e}` };
}
}
private async checkPageRender(componentId: string): Promise<{ success: boolean; message: string }> {
try {
// 检查页面组件是否渲染
await this.waitWithTimeout(2000);
return { success: true, message: `页面渲染完成` };
} catch (e) {
return { success: false, message: `页面渲染失败: ${e}` };
}
}
private async checkNetworkReady(): Promise<{ success: boolean; message: string }> {
try {
// 尝试发起一个简单请求验证网络
await this.waitWithTimeout(1000);
return { success: true, message: `网络就绪` };
} catch (e) {
return { success: false, message: `网络不可用: ${e}` };
}
}
private async checkDataLoaded(dataKey: string): Promise<{ success: boolean; message: string }> {
try {
await this.waitWithTimeout(2000);
return { success: true, message: `数据 ${dataKey} 加载完成` };
} catch (e) {
return { success: false, message: `数据加载失败: ${e}` };
}
}
private waitWithTimeout(ms: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms));
}
}
// 使用示例
const verifier = new DeployVerifier({
packageName: 'com.example.entry',
abilityName: 'EntryAbility',
checkPoints: [
{ name: '应用启动', type: 'ability_launch', expected: 'EntryAbility' },
{ name: '首页渲染', type: 'page_render', expected: 'index_page' },
{ name: '网络连接', type: 'network_ready', expected: '' },
{ name: '数据加载', type: 'data_loaded', expected: 'user_profile' },
],
timeout: 10000
});
CI中的部署验证与回滚脚本:
#!/bin/bash
# deploy_with_verify.sh - 带验证和回滚的部署脚本
set -e
HAP_FILE="entry/build/default/outputs/default/entry-default-signed.hap"
PACKAGE_NAME="com.example.entry"
ABILITY_NAME="EntryAbility"
BACKUP_DIR="deploy_backups"
# ===== 1. 备份当前版本 =====
echo "📦 备份当前版本..."
mkdir -p "$BACKUP_DIR"
# 从设备拉取当前HAP(如果已安装)
CURRENT_HAP="${BACKUP_DIR}/backup_$(date +%Y%m%d_%H%M%S).hap"
hdc shell bm dump -n "$PACKAGE_NAME" > /dev/null 2>&1
if [ $? -eq 0 ]; then
echo " 当前版本已安装,备份中..."
# 记录当前版本信息
hdc shell bm dump -n "$PACKAGE_NAME" > "${CURRENT_HAP}.info"
fi
# ===== 2. 安装新版本 =====
echo "📦 安装新版本..."
hdc install -r "$HAP_FILE"
if [ $? -ne 0 ]; then
echo "❌ 安装失败"
exit 1
fi
# ===== 3. 启动应用 =====
echo "🚀 启动应用..."
hdc shell aa start -a "$ABILITY_NAME" -b "$PACKAGE_NAME"
# ===== 4. 部署验证 =====
echo "🔍 执行部署验证..."
sleep 3 # 等待应用启动
VERIFY_PASSED=true
# 检查1:应用是否在运行
RUNNING=$(hdc shell aa dump -l 2>/dev/null | grep "$PACKAGE_NAME")
if [ -z "$RUNNING" ]; then
echo " ❌ 应用未运行"
VERIFY_PASSED=false
else
echo " ✅ 应用正在运行"
fi
# 检查2:是否有崩溃日志
CRASH_LOG=$(hdc hilog -x 2>/dev/null | grep -i "crash\|fatal" | grep "$PACKAGE_NAME" | tail -5)
if [ -n "$CRASH_LOG" ]; then
echo " ❌ 发现崩溃日志:"
echo "$CRASH_LOG"
VERIFY_PASSED=false
else
echo " ✅ 无崩溃日志"
fi
# 检查3:关键页面是否渲染
# 通过UI自动化工具检查(简化示例)
echo " ✅ 页面渲染检查通过"
# ===== 5. 验证结果处理 =====
if [ "$VERIFY_PASSED" = true ]; then
echo ""
echo "✅ 部署验证通过"
else
echo ""
echo "❌ 部署验证失败,开始回滚..."
# 回滚:卸载新版本,安装旧版本
hdc uninstall "$PACKAGE_NAME"
# 如果有备份版本,安装回去
LATEST_BACKUP=$(ls -t "${BACKUP_DIR}"/*.info 2>/dev/null | head -1)
if [ -n "$LATEST_BACKUP" ]; then
echo " 正在恢复到上一版本..."
# 实际回滚需要从制品库获取上一版本的HAP
# 这里简化处理
fi
echo "❌ 部署失败并已回滚"
exit 1
fi
# ===== 6. 采集部署信息 =====
echo ""
echo "📊 部署信息:"
echo " 包名: $PACKAGE_NAME"
echo " HAP: $HAP_FILE"
echo " 部署时间: $(date '+%Y-%m-%d %H:%M:%S')"
echo " 验证结果: 通过"
hdc shell bm dump -n "$PACKAGE_NAME" | grep -E "versionCode|versionName|installTime" || true
踩坑与注意事项
坑1:hdc install签名不一致报错
debug签名的HAP装到生产Profile的设备上,直接报签名错误。
解决方案:确保HAP签名和设备Profile匹配。
# 安装前检查签名类型
# 查看HAP签名信息
java -jar hapsigntoolv2.jar verify-app -inFile your-app.hap
# 如果签名不匹配,需要重新签名
# 使用对应的Profile重新签名后再安装
坑2:hdc连接不稳定
USB连接偶尔断开,Wi-Fi连接更不稳定,部署到一半设备掉了。
解决方案:增加连接检测和自动重连。
#!/bin/bash
# robust_hdc.sh - 带重连的hdc封装
ROBUST_HDC_MAX_RETRIES=3
robust_hdc() {
local cmd="$@"
local retry=0
while [ $retry -lt $ROBUST_HDC_MAX_RETRIES ]; do
# 执行命令
result=$(hdc $cmd 2>&1)
exit_code=$?
if [ $exit_code -eq 0 ]; then
echo "$result"
return 0
fi
# 检查是否是连接问题
if echo "$result" | grep -q "Connect\|timeout\|refused"; then
echo "⚠️ hdc连接异常,重试中... ($((retry + 1))/$ROBUST_HDC_MAX_RETRIES)"
sleep 2
# 尝试重新连接
if [ -n "$REMOTE_DEVICE" ]; then
hdc tconn "$REMOTE_DEVICE" 2>/dev/null || true
fi
retry=$((retry + 1))
continue
fi
# 非连接问题,直接返回错误
echo "$result"
return $exit_code
done
echo "❌ hdc命令重试失败: $cmd"
return 1
}
# 使用示例
robust_hdc install -r entry-default-signed.hap
robust_hdc shell aa start -a EntryAbility -b com.example.entry
坑3:多设备部署时设备掉线
批量部署5台设备,第3台中途掉线了,但脚本继续跑,后面的设备也受影响。
解决方案:每台设备独立执行,互不影响。
# 使用并行部署,每台设备独立进程
for device in "${DEVICES[@]}"; do
(
echo "部署到 $device..."
hdc -t "$device" install -r "$HAP_FILE" && \
hdc -t "$device" shell aa start -a "$ABILITY_NAME" -b "$PACKAGE_NAME"
) &
done
# 等待所有部署完成
wait
坑4:hdc hilog日志量太大
部署验证时采集日志,但hilog信息量太大,有用的信息被淹没了。
解决方案:过滤日志,只看关键信息。
# 只看特定进程的日志
hdc hilog -P 00000001.d # 按进程ID过滤
# 只看特定标签
hdc hilog -t "EntryAbility"
# 只看ERROR及以上级别
hdc hilog -L E
# 导出最近100行日志
hdc hilog -x | tail -100 > recent_log.txt
坑5:部署后应用闪退,但日志来不及看
应用启动后立即崩溃,hilog还没来得及采集就退出了。
解决方案:先启动日志采集,再启动应用。
# 先启动日志采集(后台运行)
hdc hilog > crash_log.txt &
HILOG_PID=$!
# 再启动应用
hdc shell aa start -a EntryAbility -b com.example.entry
# 等待一段时间
sleep 5
# 停止日志采集
kill $HILOG_PID 2>/dev/null
# 查看崩溃日志
grep -i "crash\|fatal\|exception" crash_log.txt
HarmonyOS 6适配说明
HarmonyOS 6对部署工具的改进:
-
hdc 2.0:新版hdc支持更快的文件传输速度,大HAP安装速度提升约40%。
-
增量安装:HarmonyOS 6支持增量安装,只传输变化的部分,大幅减少安装时间。
-
多用户部署:支持在同一设备的不同用户空间安装应用,方便测试多用户场景。
-
部署状态查询API:新增应用部署状态查询接口,可以在代码中检查部署是否完成。
-
安全部署增强:HarmonyOS 6强化了安装验证,未签名的HAP无法安装,debug签名HAP无法在正式设备上运行。
总结
自动化部署是CI/CD的最后一公里——前面跑得再快,这一步掉链子,整个流水线就白搭。
| 维度 | 评价 |
|---|---|
| 学习难度 | ⭐⭐⭐ hdc命令不难,但多设备管理、部署验证、回滚策略需要经验 |
| 使用频率 | ⭐⭐⭐⭐ 每次发布和测试都要部署,频率很高 |
| 重要程度 | ⭐⭐⭐⭐ 部署是交付的最后一关,直接影响用户体验 |
几个关键提醒:
- hdc不是adb,命令参数有差异,别直接套adb经验
- 签名必须匹配,debug包装不了正式设备,反过来也一样
- 批量部署要独立,一台设备失败不能影响其他设备
- 验证比部署更重要,装上去了不代表能用,必须验证启动和核心功能
- 回滚方案提前准备,部署失败时能快速恢复到上一版本
到这里,CI/CD流水线的核心环节都讲完了:CI构建→CD交付→构建优化→测试自动化→部署自动化。接下来进入版本管理和发布阶段——版本号怎么管、灰度怎么发、线上怎么监控。
- 点赞
- 收藏
- 关注作者
评论(0)