OpenHarmony 应用启动关闭与压测:hdc 命令实战
2026/9/17 16:16:38 网站建设 项目流程

同事把一块刷了 OpenHarmony 的开发板扔到我桌上,说“帮我把这个应用反复拉起来、杀掉,跑两百遍,看会不会崩”。我第一反应是手指去点桌面图标,第二反应就是打开终端敲hdc。结果第一条命令就吃了闭门羹:终端里蹦出一串红字,设备根本没被认出来。后面折腾了大半天,我才把hdc启动和关闭应用这条链路彻底摸顺——从设备连接、包名确认、aa start参数拼装,到aa force-stopkill的取舍,再到用日志和截图证实“它真的起来了”“它真的停了”。

这篇东西就是把我踩过的东西摊开讲。hdc(HarmonyOS Device Connector)是 OpenHarmony 提供的一套命令行调试通道,跑在 PC 上的hdc客户端通过 USB 或网络跟设备端的hdcd守护进程通信,然后你就能用shell把命令送进设备内部执行。启动和关闭应用这两个动作,落在设备内部其实就是两条指令:aa startaa force-stop。听起来简单,但真正让它稳定跑起来,涉及包名、模块名、Ability 名三者对齐,涉及引号在多层解析里被吃掉,涉及多设备环境下的目标指定。如果你正在做 OpenHarmony 应用的功能验证、稳定性压测、自动化冒烟,或者只是想让调试少点重复点击,下面这些内容应该能直接抄去用。

1. 连接链路:先让 hdc 认得出你的设备

1.1 客户端 hdc 与设备端 hdcd 是一次握手,不是单向广播

很多人以为hdc就是个命令行工具,敲下去设备就执行。实际结构是两层:PC 上运行的是hdc客户端,它会顺带拉起一个本地的服务进程;设备那头跑着hdcd,监听在固定端口上等连接。客户端把命令打包发过去,hdcd收到后交给设备内部的 shell 执行,再把输出回传。所以当你说“命令没反应”时,可能断在三个位置:客户端自己没起来、客户端与hdcd没接上、命令执行了但返回内容被截断。

这个分层解释了一个很常见的现象:设备明明插着,hdc list targets却返回[Empty]。这时候别急着怀疑板子,先看 PC 侧的服务状态。我在 Windows 上最常用的一招是hdc kill -r,它会把本地服务杀掉重启,属于成本最低的“重启大法”。Linux 上同理。重启完再hdc list targets,十次里有六七次就恢复了。

hdc -v # 看客户端版本 hdc list targets # 列出已连接设备 hdc list targets -v # 带连接信息,多设备时非常有用 hdc kill -r # 重启本地服务,救活“卡住”的连接

1.2 把 hdc 放进 PATH,以及版本对齐这件容易被忽略的事

hdc不在系统自带命令里,它躺在 OpenHarmony SDK 的toolchains目录下。Windows 上是个hdc.exe,Linux/macOS 上是可执行文件。我见过太多人每次都要cd到那个目录再执行,效率极低还容易拼错相对路径。建议直接把toolchains目录加进环境变量 PATH,之后在任意终端里都能直接敲hdc

注意:加 PATH 之前先确认你加的是哪个 SDK 版本下的目录。机器上装了三四个 SDK 是常态,PATH 里写错一个,后面所有命令都跟着错。

版本对齐是更隐蔽的坑。设备上的hdcd是随系统镜像编译进去的,PC 上的hdc来自 SDK。两者版本差得太多时,握手会失败,报错文字通常是版本不匹配或者连接被拒绝。判断方法很简单:hdc -v看客户端版本,然后hdc shell进去看设备端相关信息(部分版本会在连接异常时直接把不匹配提示打出来)。我的经验是,调试哪块板子,就用配套那一版 SDK 里的hdc,别图省事用另一版顶替。

1.3 USB 之外的两条路:网络连接与多设备指定

USB 调试的问题在于线材和接口。廉价的 USB 线、前置面板接口、经过扩展坞转接,都可能让握手时断时续,表现为命令偶尔超时。排查顺序我固定:换线、换后置 USB 口、换电脑。三板斧下来还不行,再考虑软件。

网络连接适合板子固定在工装架上、不方便插线的场景。思路是让设备监听端口,PC 侧主动连接过去:

# 让设备进入网络监听模式,端口按需指定 hdc tmode port 8710 # PC 侧连接设备的 IP 和端口 hdc tconn 192.168.1.100:8710 # 断开 hdc tconn 192.168.1.100:8710 -remove

多设备同时连在同一台 PC 上是压测环境的常态,也是最容易让人懵的场景。此时任何命令都需要指明“发给谁”,否则直接报need connect-key之类的提示。先hdc list targets -v拿到每个设备的连接标识,然后用-t指定:

hdc -t 7001005458323933328a01b4a07400 shell aa dump -a

我习惯把这个标识存成变量,脚本里再引用,避免手抄十几个字符抄错一位。

2. 启动应用前必须确认的三件事:包名、模块名、Ability 名

2.1 用 bm dump 把包的真实信息翻出来

aa start失败的原因里,超过一半是名字不对。包名不是应用的中文名、不是桌面图标下面的文字,而是module.json5bundleName字段那串反向域名。模块名是module.name,Ability 名是abilities[].name。三个名字里任何一个写错,命令都会失败。

最稳的确认方式是让设备自己告诉你。Bundle Manager 提供了 dump 能力:

# 列出设备上所有已安装应用 hdc shell bm dump -a # 查单个应用的详细信息:模块名、Ability 名、类型、是否导出 hdc shell bm dump -n com.example.myapp # 输出较长时直接过滤关键字 hdc shell "bm dump -n com.example.myapp | grep -i -E 'name|ability|module'"

bm dump -n的输出是结构化文本,信息量很足。我会重点看四样东西:moduleName是什么、abilities下面有哪几个name、每个 Ability 的typepage还是service、以及exportedtrue还是false

exported这一项值得单独说。它决定这个 Ability 是否允许被外部拉起来。从 shell 发起的aa start,调用方身份并不是应用自身,exportedfalse的入口 Ability 很可能启动失败。如果你只是想验证应用能跑,就找那个标记为导出的入口;如果非要用未导出的 Ability,那属于另一个话题了。

2.2 aa dump 看当前系统里跑着什么

确认完“它装的是什么”,还要确认“它现在在什么状态”。Ability Assistant 的 dump 子命令可以把系统里的 Ability 状态列出来:

# 列出所有 Ability 及其状态 hdc shell aa dump -a # 查看任务栈 / 任务列表 hdc shell aa dump -l # 查看当前版本支持哪些子参数 hdc shell aa -h

aa dump -a的输出里能直接看到每个 Ability 挂在前台还是后台。这对“我以为它起来了其实它没起来”这种自我欺骗特别有效。压测时我的固定流程是先 dump 一次拿到基线,启动后再 dump 一次做对比,状态字段变了才算真的成功,光看命令没有报错不算数。

提示:不同版本的aa子命令集合有差异,-h的输出就是权威文档,比翻网上零散的帖子可靠。养成先看-h的习惯能省下大量试错时间。

2.3 三个名字对不上时的典型报错

我把常见的报错和成因整理成一张表,方便对着看。

报错关键字大概率原因处理方式
failed to start abilityAbility 名拼错或该 Ability 未导出bm dump -n核对名称与exported
bundle not found/ 找不到包包名错,或应用压根没装bm dump -a确认;必要时重新安装
module not found-m传的模块名与该应用不匹配从 dump 结果里抄moduleName
connect-key/ 多设备提示多台设备同时在线未指定目标-t <连接标识>
版本不匹配类提示PC 侧 hdc 与设备端 hdcd 版本差异过大换用配套 SDK 里的 hdc

装包和卸载的命令顺手记一下,排查“没装”这类问题时用得到:

# 安装,-r 表示替换已存在的同名应用 hdc install -r ./entry-default-signed.hap hdc shell bm install -p /data/local/tmp/entry-default-signed.hap -r # 卸载 hdc uninstall com.example.myapp hdc shell bm uninstall -n com.example.myapp

3. aa start 的正确姿势与参数细节

3.1 最小可用命令长什么样

把名字确认清楚之后,启动命令本身并不复杂:

# 只给 Ability 名和包名 hdc shell aa start -a EntryAbility -b com.example.myapp # 补上模块名,兼容性更好 hdc shell aa start -a EntryAbility -b com.example.myapp -m entry

-a是 Ability 名,-b是包名,-m是模块名。我建议永远带上-m,哪怕当前版本不写也能跑通。原因很实际:同一台设备上可能装了好几个使用相同 Ability 命名的应用,或者同一个应用里不同模块有同名 Ability,少了-m就可能启动到非预期的目标上,而且它不会报错,你只会觉得“怎么界面不太对”。

引号这个细节值得单独拎出来。hdc shell后面的内容会被拼成一条命令送到设备端 shell 执行,中间经过本地终端和hdc两层解析。当参数里含空格、通配符、重定向符号时,不加引号很容易被本地终端先处理掉。稳妥写法是把整条设备端命令用引号包起来:

hdc shell "aa start -a EntryAbility -b com.example.myapp -m entry"

Windows 的 cmd 和 PowerShell 对这个的处理又不一样,PowerShell 里单引号含义与 cmd 不同,用错会直接报本地语法错误而不是设备端错误。分辨方法看报错语言和上下文:本地报错里出现的是你 PC 上的路径风格,设备端报错里的路径是 Linux 风格。

3.2 传参启动:--ps / --pi / --pb 与引号陷阱

调试时经常需要带着初始参数启动,比如指定要跳转的页面、传一个测试账号、打开某个开关。aa start支持三种类型的参数:

hdc shell "aa start -a EntryAbility -b com.example.myapp -m entry \ --ps sceneId detail_page \ --pi itemId 1024 \ --pb debugMode true"

--ps传字符串,--pi传整数,--pb传布尔值。跟-b-a一样,参数名和值之间用空格分隔。

这里最容易翻车的是布尔值。有些同学写--pb debugMode 1或者--pb debugMode TRUE,结果应用侧解析出来的始终是默认值。老老实实用小写的true/false,别自作聪明。

另一个坑是字符串里带空格。--ps title hello world会被拆成两个参数,应用只能拿到hello。正确做法是给值本身加引号,注意要跟外层引号区分开:

hdc shell "aa start -a EntryAbility -b com.example.myapp -m entry --ps title 'hello world'"

如果值里有更复杂的字符,我的建议是干脆别走命令行传参,改成让应用从配置文件读,或者用文件推送到设备再读取。跟多层 shell 解析较劲的时间成本,远高于换一种传参方式。

3.3 调试模式 -D 和用户 ID -U

-D是调试模式启动,让目标 Ability 进入可被调试的状态。对做性能分析或者要用调试器挂上去的场景很有用。需要注意的是调试模式会改变运行行为,测出来的耗时数值跟正常启动不可比,别拿调试模式的数据去做性能结论。

-U用来指定用户 ID。设备上存在多用户环境时,如果不指定,命令可能落到默认用户下,你在当前用户界面上自然看不到任何变化。这个坑在多人共用工装设备的实验室环境里特别常见:你以为应用没起来,其实它起在另一个用户空间里了。

# 指定用户并进入调试模式 hdc shell "aa start -a EntryAbility -b com.example.myapp -m entry -U 100 -D"

3.4 命令没报错,界面却没变化的几种情况

这是最耗心力的一类问题:命令返回干净,aa dump里也能看到状态变化,但肉眼看到的界面就是不对。我遇到过三种。

第一种是应用已经在后台停留,aa start触发的是“唤到前台”而不是“重新创建”。所以你看不到启动页,直接跳到了上次停留的页面。想验证冷启动,必须先force-stop再启动,中间留足两三秒让系统回收干净。

第二种是启动到了另一个任务栈。aa dump -l里能看到多个任务实例,界面上显示的是其中某一个。多窗口或者分屏状态下这个现象尤其明显。

第三种是渲染确实出了问题,界面元素存在但显示异常。这种时候命令行层面已经帮不上忙了,需要截图看一眼。我在第 5 部分会讲截图取证的做法。

4. 关闭应用:三种手段的适用边界

4.1 aa force-stop 是最正统的杀法

关闭应用的第一选择是force-stop

hdc shell aa force-stop com.example.myapp

它由系统服务执行,会走完整的应用销毁流程:清理任务栈、回调生命周期、回收资源。对大多数验证场景来说这就是你要的“关掉”。它只需要包名,不需要 Ability 名和模块名,用起来比启动简单。

有个容易被忽略的点:force-stop之后包名对应的进程通常会消失,但如果应用注册了常驻服务、或者被系统标记为需要保活,进程可能换个形态继续存在。所以判断“关干净了没有”,不能只看命令返回值,得去确认进程和状态,方法在下一节。

4.2 kill -9 的用法与副作用

有时候force-stop不生效,或者你要模拟的是“进程被系统直接杀掉”这种极端场景,那就需要精确到进程级别:

# 拿到进程号 hdc shell pidof com.example.myapp # 直接强杀 hdc shell kill -9 12345

写成一行的写法更顺手:

hdc shell "kill -9 $(hdc shell pidof com.example.myapp)"

pidof在部分镜像上不存在,这时候退回到ps过滤:

hdc shell "ps -ef | grep com.example.myapp" hdc shell "ps -A | grep com.example.myapp"

kill -9的副作用是跳过了应用的清理逻辑:文件句柄不关闭、临时数据不落盘、正在写的日志可能被截断。用它做压力测试是合理的,因为它恰好模拟了最恶劣的异常退出;但如果是想验证应用自身的正常退出行为,用force-stop,别用kill -9,否则测出来的是另一码事。

注意:某些发行版本或用户版本上,对进程的操作权限受限,普通调试身份可能杀不掉系统级进程。这类限制是设备安全策略的一部分,遇到时不要硬绕,改用应用自身提供的退出入口来做验证。

4.3 ServiceAbility 要用 stop-service

如果目标是服务型 Ability 而不是带界面的页面,force-stop不一定是最贴切的工具。Ability Assistant 提供了对应的停止命令:

hdc shell aa stop-service -a MyServiceAbility -b com.example.myapp -m entry

这里又需要 Ability 名和模块名了,所以前面bm dump拿到的信息是整套操作的基础,建议一开始就把关键名称记成一张便签贴在旁边,比每次重新 dump 省事得多。

4.4 杀掉之后又自己起来了,该怎么查

这是做压测时最让人困惑的现象:脚本明明连续执行了十次force-stop,进程数量却没降下去。可能的原因有三类。

一是应用自身逻辑。很多应用会在启动时注册定时任务、监听某些系统事件,被杀掉后触发条件一满足就重新拉起。这类问题在代码里找,hilog里搜索你自己的启动日志是最快的路径。

二是系统侧联动。应用之间通过 Ability 或服务存在调用关系,A 被杀了,依赖它的 B 触发拉起逻辑。

三是预设的保活配置。这类要去看应用的 profile 配置,而不是命令。

排查顺序我固定为:先hilog抓日志看是谁发起的启动、再aa dump -a看是哪个调用方、最后才怀疑命令本身。跳过前两步直接怀疑命令,基本上是浪费时间。

5. 用日志和截图确认结果,而不是靠猜

5.1 hilog 抓取与应用侧关键字过滤

命令返回成功只是“请求被接受了”,不代表应用真的按预期跑起来。日志是最直接的证据:

# 一次性抓取日志并过滤包名相关行 hdc shell "hilog | grep -i myapp" # 把日志重定向到 PC 侧文件,方便慢慢翻 hdc hilog > device_log.txt

过滤关键字的选择有讲究。用包名过滤出来的是系统侧对应用的操作记录,用应用自己打的 tag 过滤出来的才是应用内部流程。我一般两遍都跑:第一遍包名确认“系统收到了启动请求”,第二遍应用 tag 确认“应用真的执行到了入口逻辑”。

压测场景下日志量会爆掉,这时候要控制节奏。我的做法是清空缓冲再启动,抓一小段就停,避免几十万行日志把终端拖死:

hdc shell "hilog -r" # 清空缓冲区 hdc shell "aa start -a EntryAbility -b com.example.myapp -m entry" sleep 3 hdc shell "hilog | grep -i myapp | tail -50"

5.2 aa dump -l 看任务栈,确认前后台状态

日志能证明流程走过,但不能证明界面在前台。aa dump -l输出的任务列表是判断前后台的可靠依据:

hdc shell aa dump -l hdc shell "aa dump -l | grep -A 5 myapp"

看两点:目标应用的任务是否出现在列表里,以及状态字段显示的是前台还是后台。启动前后各 dump 一次做对比,比盯着一长串输出里猜要靠谱得多。

5.3 snapshot_display 加 file recv,留下可复核的证据

做自动化验证时,截图的价值极高。OpenHarmony 提供了截屏命令,配合文件传输就能把画面取回 PC:

# 设备侧截屏并保存到临时目录 hdc shell snapshot_display -f /data/local/tmp/shot.jpeg # 取回本地 hdc file recv /data/local/tmp/shot.jpeg ./shots/ # 顺手清理设备侧文件 hdc shell "rm -f /data/local/tmp/shot.jpeg"

这套组合在压测里几乎是必备的。因为屏幕上出现的异常(白屏、错位、渲染残留)在日志里可能一行错误都没有,只有画面能说明问题。我现在的习惯是每次启动后都截一张、文件名带序号,跑完一批之后快速翻图,一眼就能定位到第几次循环开始出问题。

6. 把命令封装成脚本:批量启动关闭与压测

6.1 Bash 版本:启动、等待、截图、关闭

单条命令敲两次没问题,敲两百次就必须脚本化。下面这个骨架我在 Linux 和 macOS 上都用过,改几个变量就能跑:

#!/bin/bash set -u BUNDLE="com.example.myapp" ABILITY="EntryAbility" MODULE="entry" ROUNDS=50 SHOT_DIR="./shots" mkdir -p "$SHOT_DIR" # 前置检查:必须有设备在线 if ! hdc list targets | grep -qv "Empty"; then echo "没有检测到设备,先解决连接问题" exit 1 fi for i in $(seq 1 "$ROUNDS"); do echo "===== 第 $i 轮 =====" hdc shell "aa force-stop $BUNDLE" sleep 1 hdc shell "aa start -a $ABILITY -b $BUNDLE -m $MODULE" sleep 3 hdc shell "snapshot_display -f /data/local/tmp/s_$i.jpeg" hdc file recv "/data/local/tmp/s_$i.jpeg" "$SHOT_DIR/" > /dev/null hdc shell "rm -f /data/local/tmp/s_$i.jpeg" # 记录进程是否存在,作为异常线索 PID=$(hdc shell "pidof $BUNDLE" | tr -d '\r') echo "round $i pid=$PID" done

几个细节值得说:sleep的秒数不要压到极限,冷启动三秒是保守值,设备性能差的时候还要加;tr -d '\r'是为了去掉 Windows 换行残留,不加的话变量比较会莫名其妙失败;每轮先杀后起,保证测到的是冷启动而不是后台唤醒。

6.2 Windows 批处理版本与其局限

Windows 上用批处理也能做,但字符串处理和返回值判断比 Bash 别扭不少:

@echo off set BUNDLE=com.example.myapp set ABILITY=EntryAbility set MODULE=entry for /l %%i in (1,1,50) do ( echo ===== Round %%i ===== hdc shell "aa force-stop %BUNDLE%" timeout /t 1 /nobreak > nul hdc shell "aa start -a %ABILITY% -b %BUNDLE% -m %MODULE%" timeout /t 3 /nobreak > nul hdc shell "snapshot_display -f /data/local/tmp/s_%%i.jpeg" hdc file recv /data/local/tmp/s_%%i.jpeg .\shots\ > nul hdc shell "rm -f /data/local/tmp/s_%%i.jpeg" )

如果 PC 上装了 Git Bash 或者 WSL,我更推荐直接跑前面那个 Bash 脚本。批处理在取命令输出、做条件判断时的坑太多,为了省一次环境安装去跟它较劲不太值。

6.3 一份容易踩的清单,附我自己的处理习惯

最后把这篇里散落的坑集中一下,都是真金白银换来的。

  • 连接类问题,先hdc kill -r重启服务,再换线换口,最后才怀疑设备。
  • 命令类问题,先hdc shell aa -hhdc shell bm dump -n确认当前版本的语法和实际名称,别照搬网上的命令。
  • 多设备环境,所有命令统一加-t,脚本里把连接标识做成变量。
  • -m能加就加,不加也能跑通不等于跑对了。
  • 参数值用引号包住,含空格的值必须包,布尔值统一小写。
  • 关闭应用优先force-stop,只有需要模拟异常退出时才用kill -9
  • 判断结果靠日志加截图,不靠命令返回值,因为返回值只代表请求送达。
  • 脚本里每轮之间留足等待时间,省下来的那两秒会让你得到一堆假失败。

我个人在实际操作中的体会是,hdc这套命令本身并不复杂,真正的门槛在“确认目标”这一步:确认设备、确认包名、确认模块、确认 Ability、确认状态。前面这两分钟做扎实了,后面敲命令几乎不会再失败;反过来,跳过确认直接抄命令,就只能在红字里反复猜。篇幅允许的话,后面还可以把power-shell的休眠唤醒、hidumper的内存观测串进来,做一套更完整的应用生命周期自动化验证流程,那个思路跟这里完全一致,只是把“启动关闭”换成了更多维度的观测点。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询