HarmonyOS开发:自动化部署

举报
Jack20 发表于 2026/06/24 16:51:22 2026/06/24
【摘要】 HarmonyOS开发:自动化部署📌 核心要点:hdc是鸿蒙的adb——掌握它的命令体系,HAP安装、多设备批量部署、部署验证全都能自动化,让"构建完即部署"成为现实。 背景与动机构建完了,测试也过了,然后呢?你拿着HAP文件,打开DevEco Studio,连上设备,点击运行,等安装完成,再手动验证功能是否正常。一台设备还好,五台设备呢?十个测试同学每人一台呢?每次发版都要手动装一遍?...

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对部署工具的改进:

  1. hdc 2.0:新版hdc支持更快的文件传输速度,大HAP安装速度提升约40%。

  2. 增量安装:HarmonyOS 6支持增量安装,只传输变化的部分,大幅减少安装时间。

  3. 多用户部署:支持在同一设备的不同用户空间安装应用,方便测试多用户场景。

  4. 部署状态查询API:新增应用部署状态查询接口,可以在代码中检查部署是否完成。

  5. 安全部署增强:HarmonyOS 6强化了安装验证,未签名的HAP无法安装,debug签名HAP无法在正式设备上运行。

总结

自动化部署是CI/CD的最后一公里——前面跑得再快,这一步掉链子,整个流水线就白搭。

维度 评价
学习难度 ⭐⭐⭐ hdc命令不难,但多设备管理、部署验证、回滚策略需要经验
使用频率 ⭐⭐⭐⭐ 每次发布和测试都要部署,频率很高
重要程度 ⭐⭐⭐⭐ 部署是交付的最后一关,直接影响用户体验

几个关键提醒:

  • hdc不是adb,命令参数有差异,别直接套adb经验
  • 签名必须匹配,debug包装不了正式设备,反过来也一样
  • 批量部署要独立,一台设备失败不能影响其他设备
  • 验证比部署更重要,装上去了不代表能用,必须验证启动和核心功能
  • 回滚方案提前准备,部署失败时能快速恢复到上一版本

到这里,CI/CD流水线的核心环节都讲完了:CI构建→CD交付→构建优化→测试自动化→部署自动化。接下来进入版本管理和发布阶段——版本号怎么管、灰度怎么发、线上怎么监控。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。