不装 DevEco Studio:用官方 CLI 把 ArkTS 应用装进鸿蒙云手机,改一版只要 24 秒
方向四「基于官方工具的模拟验证与开发提效」实践。全程使用华为官方 Command Line Tools + 鸿蒙云手机,不装 IDE、不用真机、不配签名,把「改代码 → 看到界面」的循环压到 24 秒。
环境与版本
| 项目 | 版本 / 规格 |
|---|---|
| 云手机 | 8 vCPU / 12 GB / 1260×2720 |
| 设备型号 | ALN-AL00(Mate 60 Pro) |
| 系统版本 | HarmonyOS 5.0.1.130(SP2C00E1R4P1log) |
| 设备 API | 17 |
| Command Line Tools | 6.0.1.251(hvigor 6.21.1 / ohpm 6.0.1) |
| 编译 SDK | HarmonyOS 6.0.1 (API 21) |
| JDK | 17.0.21 |
| hdspace CLI | 2.3.3 |
| 宿主系统 | Windows 11 x64 |
说明:本文验证的是工具链与流程,云手机当前为 HarmonyOS 5.0.1 / API 17,编译 SDK 为 API 21。流程对 API 26 同样适用,只需把
compatibleSdkVersion提高。
目录
一、这件事为什么值得写
搜「鸿蒙云手机 命令行」,能搜到的内容基本都是「打开 DevEco Studio,点这个按钮」。但有两种场景 IDE 帮不上忙:
- CI/CD:Jenkins/GitLab Runner 是纯命令行的,装不了图形界面
- 远程开发:服务器只有 SSH,本地带宽还紧
我这次就是第二种——目标是一台 8c12g 的云手机,手边只有命令行。整个过程踩了 5 个坑,每个都卡了不短时间。
二、工具链从哪来
官方下载页 developer.huawei.com/consumer/cn/download/command-line-tools-for-hmos 必须登录华为账号才能下。CI 环境里没人能给你扫码。
三个绕法:
| 方案 | 可行性 | 说明 |
|---|---|---|
| 登录后手动下载,缓存到 CI | 可行 | 最稳,但要预先准备 |
| HuggingFace 镜像 | 可行 | 实测 commandline-tools-windows-x64-6.0.1.251.zip 完整 |
| 从 DevEco Studio 安装目录拷 | 可行 | sdk/ + hvigor/ + tool/ 三块拷出来即可 |
我是第二种。包体 2.36 GB,单线程只有 0.45 MB/s(约 90 分钟),16 线程分段后 5.8 MB/s(约 7 分钟)。
# 分段下载 + 断点续传核心逻辑
chunk = size // THREADS + 1
def worker(i):
start, end = i * chunk, min(size - 1, i * chunk + chunk - 1)
have = os.path.getsize(part_file) if exists else 0
while have < end - start + 1:
headers = {"Range": f"bytes={start + have}-{end}"}
with requests.get(final_url, headers=headers, stream=True) as r:
with open(part_file, "ab") as f:
for blk in r.iter_content(262144):
f.write(blk); have += len(blk)
下载完先校验 ZIP 完整性(zipfile.ZipFile(path).namelist()),我第一版就是包被截断了却以为下完,解压时才报错。
三、环境变量:三个,一个都不能少
export JAVA_HOME=/d/harmony/jdk-17.0.21+9
export DEVECO_SDK_HOME=/d/harmony/clt/command-line-tools/sdk
export PATH=$JAVA_HOME/bin:$PATH
Command Line Tools 不带 JRE。不配 JAVA_HOME 时 hvigorw 会报一个跟 Java 毫无关系的错误,很容易误判成 SDK 问题。
JDK 17 就够,不用 21。
四、工程骨架:五个文件决定能不能编
一个能 assembleHap 的最小 stage 模型工程,必须有这五处:
HarmonyShadow/
├── AppScope/
│ ├── app.json5 # bundleName / versionName / icon
│ └── resources/base/
│ ├── element/string.json # app_name
│ └── media/app_icon.png # 图标必须真实存在
├── entry/
│ ├── src/main/
│ │ ├── module.json5 # abilities + permissions
│ │ ├── ets/entryability/EntryAbility.ets
│ │ ├── ets/pages/Index.ets
│ │ └── resources/base/
│ │ ├── element/string.json
│ │ ├── element/color.json
│ │ ├── profile/main_pages.json # 页面路由,缺了白屏
│ │ └── media/app_icon.png
│ ├── build-profile.json5
│ ├── hvigorfile.ts
│ └── oh-package.json5
├── build-profile.json5
├── hvigorfile.ts
├── oh-package.json5
└── hvigor/hvigor-config.json5 # 最容易漏
坑 1:app_icon.png 缺失会直接编译失败
app.json5 和 module.json5 都引用了 $media:app_icon。工程是手搓的,没有图。
用 Python 生成一个纯色 PNG 就行(不依赖 PIL):
import zlib, struct
def png(path, w=192, h=192, rgb=(26,115,232)):
raw = b''.join(b'\x00' + bytes(rgb) * w for _ in range(h))
def chunk(t, d):
c = t + d
return struct.pack('>I', len(d)) + c + struct.pack('>I', zlib.crc32(c) & 0xffffffff)
data = (b'\x89PNG\r\n\x1a\n'
+ chunk(b'IHDR', struct.pack('>IIBBBBB', w, h, 8, 2, 0, 0, 0))
+ chunk(b'IDAT', zlib.compress(raw))
+ chunk(b'IEND', b''))
open(path, 'wb').write(data)
547 字节,够用。
坑 2:「工程结构需要升级」其实缺 modelVersion
ERROR: 00303024 Configuration Error
The project structure and configuration need to be upgraded before use.
Try the following:
> Use the migration tool to update the project structure and configuration with Deveco Studio
命令行里没有 Studio,这条建议等于没有。翻 hvigor 源码 validate-util.js 才看到真实校验:
const i = HvigorConfigLoader.getInstance().getHvigorConfig()?.modelVersion; // hvigor-config.json5
const r = parseJsonFile(oh-package.json5).modelVersion; // 根 oh-package.json5
i && r ? (i === r ? ... : printErrorExit("INCONSISTENT_MODEL_VERSION"))
: printErrorExit("PROJECT_STRUCTURE_AND_CONFIGURATION");
结论:
- 两边都得有
modelVersion - 两个值必须完全相等
- 版本号必须在 hvigor 支持的区间内
只改一边,报错一模一样,看不出区别。
坑 3:hdc 缓存死连接,隧道活了也连不上
云手机隧道断线后重连,hdc tconn 一直 Connect failed,但 hdspace 日志显示 connect to tunnel server success。
原因:隧道服务端活着,但本地 hdc 客户端还握着上一个死 socket。
def reconnect():
subprocess.run([HDC, "kill"]) # 关键:先杀 hdc 服务
time.sleep(2)
for _ in range(6):
r = subprocess.run([HDC, "tconn", "127.0.0.1:8712"], capture_output=True, text=True)
if "Connect OK" in (r.stdout + r.stderr):
return True
time.sleep(2)
return False
hdc kill 这一步不能省。少了它,重连十次全是 Connect failed。
坑 4:未签名 HAP 也能装
编译完会看到:
hvigor WARN: No signingConfig found for product default
第一次看到这个我以为是致命错误,去配签名证书,结果要实名、要 CSR、要签名 Profile,流程很长。
实测:云手机直接装未签名 HAP 成功。
hdc -t 127.0.0.1:8712 install entry-default-unsigned.hap
# [Info]... msg:install bundle successfully.
开发调试阶段完全不用碰签名。
坑 5:aa start 报错但应用其实起来了
hdc shell "aa start -a EntryAbility -b com.example.harmonyshadow"
# error: failed to start ability.
但 hilog 里能看到:
C03F00/com.example.harmonyshadow/ArkCompiler: [gc] SmartGC: app cold start just finished
应用在跑,aa start 的返回码不可信。判断标准应该是截图,不是命令返回值。
hdc shell "snapshot_display -f /data/local/tmp/s1.jpeg"
hdc file recv /data/local/tmp/s1.jpeg ./s1.jpeg
五、一键脚本
把上面所有坑封成一个流程,改完代码到看到界面:
import subprocess, time
HDC = "path/to/hdc.exe"
KEY = "127.0.0.1:8712"
def sh(cmd):
subprocess.run(cmd, shell=True, check=True)
def hdc(*args):
subprocess.run([HDC, "kill"], capture_output=True)
time.sleep(2)
subprocess.run([HDC, "tconn", KEY], capture_output=True)
time.sleep(1)
r = subprocess.run([HDC, "-t", KEY] + list(args), capture_output=True, text=True)
return (r.stdout or "") + (r.stderr or "")
# 1. 编译
sh("hvigorw --mode module -p product=default -p module=entry@default assembleHap --no-daemon")
# 2. 安装
hap = "entry/build/default/outputs/default/entry-default-unsigned.hap"
print(hdc("install", hap))
# 3. 启动
print(hdc("shell", "aa start -a EntryAbility -b com.example.harmonyshadow"))
time.sleep(5)
# 4. 截图
hdc("shell", "snapshot_display -f /data/local/tmp/shot.jpeg")
hdc("file", "recv", "/data/local/tmp/shot.jpeg", "./shot.jpeg")
耗时实测:
| 步骤 | 耗时 |
|---|---|
| hvigor 增量编译 | 18.3 s |
| install HAP | ~3 s |
| aa start | ~1 s |
| 截图 + 回传 | ~1.5 s |
| 合计 | 约 24 s |
改一行 ArkTS,24 秒后在本地看到云手机截图。
提效对比:三种开发方式
| 方式 | 首次准备 | 单次改动反馈 | 能否进 CI | 多人共享 |
|---|---|---|---|---|
| DevEco Studio + 本地模拟器 | 装 IDE + 下载模拟器镜像(数 GB) | 热重载约 3–8 s | 否 | 否 |
| DevEco Studio + 真机 | IDE + 数据线 + 签名 | 约 15–30 s | 否 | 否 |
| 官方 CLI + 云手机 | 工具链 2.36 GB + 云手机开箱即用 | 约 24 s(全自动) | 是 | 是(同一隧道多端复用) |
CLI 方案的单次反馈不是最快的(热重载更快),但它有两个别人没有的属性:
- 可进 CI:整条链路都是命令,能塞进 Jenkins / GitLab Runner
- 可共享:云手机在云端,团队成员不用各自准备设备
这就是方向四「开发提效」真正的落点:不是让单人更快,而是让流程可自动化、可复制。
实际收益
我这次给「多设备任务接续」做端侧验证,一共改了 7 版 ArkTS(接 WebSocket、加序号、加缺口检测、加重连、修 RTT、改 UI、修日志截断)。
- 按真机方式:要等设备、插线、签名,每版至少 5 分钟起
- 按 CLI + 云手机:7 × 24 s ≈ 3 分钟全部跑完
改版次数越多,这个差距越明显。
六、云手机真机信息
型号:ALN-AL00(Mate 60 Pro)
系统:HarmonyOS 5.0.1.130(SP2C00E1R4P1log)
API:17
分辨率:1260 × 2720
规格:8 vCPU / 12 GB
用 hdc shell param get 一次性读全:
hdc shell "param get const.product.software.version; \
param get const.ohos.apiversion; \
param get const.product.model; \
param get const.product.devicetype"
5.0.1.130(SP2C00E1R4P1log)
17
ALN-AL00
phone
注意 -t 127.0.0.1:8712 必须带。不带的话报:
[Fail]ExecuteCommand need connect-key? please confirm a device by help info
因为本地可能同时连着多台,hdc 不知道你指哪台。
七、效果验证:应用真的跑起来了
编译产物 entry-default-unsigned.hap 只有 48 KB(一个页面 + 一个 Ability + 网络权限),安装后启动,云手机实时抓屏如下:

界面元素全部正常渲染:标题、输入框、按钮、统计栏、日志列表。没有任何白屏、错位、字体缺失。
点「连接」后,应用通过 WebSocket 连上公网的 Node 服务,收到对端消息:

说明整条链路都通了:
hvigorw 编译 → hdc install → aa start → 应用进程 → ohos.net.webSocket → 公网服务端
从 assembleHap 到看到这张截图,总耗时约 24 秒。
八、这套流程适合谁
适合:
- CI 里跑鸿蒙自动化测试(有
hdc就能装包、截图、点按) - 服务器上做远程开发
- 需要批量在多台云手机上验证兼容性
不适合:
- 需要 UI 预览、实时热重载 —— 老老实实用 DevEco Studio
- 需要调试 ArkTS 断点 —— CLI 工具链不支持,只能看 hilog
九、总结
纯命令行跑通鸿蒙云手机,关键就三件事:
- 工具链:
hvigorw(编译)+hdc(部署/调试)+ JDK 17,SDK 自带 hdc 不用另外下 - 工程配置:
modelVersion两边一致、app_icon.png真实存在、main_pages.json别漏 - 连接稳定性:
hdc kill→tconn是唯一可靠的复位方式
五个坑里,三个是错误信息骗人(说是升级问题其实是配置缺失、说是启动失败其实已启动、说是签名问题其实能装),两个是状态残留(hdc 死连接、ZIP 断包)。
这些都不在官方文档里,只能靠踩。希望这篇能让你少踩几个。
下一篇我会用这套流程把「多设备任务接续」的完整客户端跑起来,包括 WebSocket 长连接、消息序号和断线影子恢复的真实数据。
更多推荐



所有评论(0)