
同事把一块刷了 OpenHarmony 的开发板扔到我桌上说“帮我把这个应用反复拉起来、杀掉跑两百遍看会不会崩”。我第一反应是手指去点桌面图标第二反应就是打开终端敲hdc。结果第一条命令就吃了闭门羹终端里蹦出一串红字设备根本没被认出来。后面折腾了大半天我才把hdc启动和关闭应用这条链路彻底摸顺——从设备连接、包名确认、aa start参数拼装到aa force-stop与kill的取舍再到用日志和截图证实“它真的起来了”“它真的停了”。这篇东西就是把我踩过的东西摊开讲。hdcHarmonyOS Device Connector是 OpenHarmony 提供的一套命令行调试通道跑在 PC 上的hdc客户端通过 USB 或网络跟设备端的hdcd守护进程通信然后你就能用shell把命令送进设备内部执行。启动和关闭应用这两个动作落在设备内部其实就是两条指令aa start和aa 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.exeLinux/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.json5里bundleName字段那串反向域名。模块名是module.nameAbility 名是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|modulebm dump -n的输出是结构化文本信息量很足。我会重点看四样东西moduleName是什么、abilities下面有哪几个name、每个 Ability 的type是page还是service、以及exported是true还是false。exported这一项值得单独说。它决定这个 Ability 是否允许被外部拉起来。从 shell 发起的aa start调用方身份并不是应用自身exported为false的入口 Ability 很可能启动失败。如果你只是想验证应用能跑就找那个标记为导出的入口如果非要用未导出的 Ability那属于另一个话题了。2.2 aa dump 看当前系统里跑着什么确认完“它装的是什么”还要确认“它现在在什么状态”。Ability Assistant 的 dump 子命令可以把系统里的 Ability 状态列出来# 列出所有 Ability 及其状态 hdc shell aa dump -a # 查看任务栈 / 任务列表 hdc shell aa dump -l # 查看当前版本支持哪些子参数 hdc shell aa -haa dump -a的输出里能直接看到每个 Ability 挂在前台还是后台。这对“我以为它起来了其实它没起来”这种自我欺骗特别有效。压测时我的固定流程是先 dump 一次拿到基线启动后再 dump 一次做对比状态字段变了才算真的成功光看命令没有报错不算数。提示不同版本的aa子命令集合有差异-h的输出就是权威文档比翻网上零散的帖子可靠。养成先看-h的习惯能省下大量试错时间。2.3 三个名字对不上时的典型报错我把常见的报错和成因整理成一张表方便对着看。报错关键字大概率原因处理方式failed to start abilityAbility 名拼错或该 Ability 未导出用bm dump -n核对名称与exportedbundle not found/ 找不到包包名错或应用压根没装bm dump -a确认必要时重新安装module not found-m传的模块名与该应用不匹配从 dump 结果里抄moduleNameconnect-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.myapp3. 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 entryWindows 的 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 -D3.4 命令没报错界面却没变化的几种情况这是最耗心力的一类问题命令返回干净aa dump里也能看到状态变化但肉眼看到的界面就是不对。我遇到过三种。第一种是应用已经在后台停留aa start触发的是“唤到前台”而不是“重新创建”。所以你看不到启动页直接跳到了上次停留的页面。想验证冷启动必须先force-stop再启动中间留足两三秒让系统回收干净。第二种是启动到了另一个任务栈。aa dump -l里能看到多个任务实例界面上显示的是其中某一个。多窗口或者分屏状态下这个现象尤其明显。第三种是渲染确实出了问题界面元素存在但显示异常。这种时候命令行层面已经帮不上忙了需要截图看一眼。我在第 5 部分会讲截图取证的做法。4. 关闭应用三种手段的适用边界4.1 aa force-stop 是最正统的杀法关闭应用的第一选择是force-stophdc 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.myappkill -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 -505.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 BUNDLEcom.example.myapp ABILITYEntryAbility MODULEentry ROUNDS50 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 BUNDLEcom.example.myapp set ABILITYEntryAbility set MODULEentry 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 -h和hdc shell bm dump -n确认当前版本的语法和实际名称别照搬网上的命令。多设备环境所有命令统一加-t脚本里把连接标识做成变量。-m能加就加不加也能跑通不等于跑对了。参数值用引号包住含空格的值必须包布尔值统一小写。关闭应用优先force-stop只有需要模拟异常退出时才用kill -9。判断结果靠日志加截图不靠命令返回值因为返回值只代表请求送达。脚本里每轮之间留足等待时间省下来的那两秒会让你得到一堆假失败。我个人在实际操作中的体会是hdc这套命令本身并不复杂真正的门槛在“确认目标”这一步确认设备、确认包名、确认模块、确认 Ability、确认状态。前面这两分钟做扎实了后面敲命令几乎不会再失败反过来跳过确认直接抄命令就只能在红字里反复猜。篇幅允许的话后面还可以把power-shell的休眠唤醒、hidumper的内存观测串进来做一套更完整的应用生命周期自动化验证流程那个思路跟这里完全一致只是把“启动关闭”换成了更多维度的观测点。