一谈到鸿蒙开发,过去大家默认就是手机平板上那点事。但到了 HarmonyOS 6.0 这个阶段,PC 真机应用开发已经不是一个“预告”级的噱头,而是真正可以把手边的电脑变成测试设备,把原来手机上的 App 以桌面级形态直接跑起来。这段时间我把一个非常基础的水果列表 demo 从创建工程、写代码、到部署进 PC 真机完整走了一遍,整个过程踩了不少坑,也摸清了原生开发到桌面级运行体验之间到底有哪些差异。
这篇内容就是一份完整的实践记录,适合刚接触 HarmonyOS 原生开发、正准备适配 PC 真机的人参考。我会从工程结构、ArkTS 列表实现、真机部署、以及最后的桌面级体验优化这几个维度展开,尽量把我在实际操作中遇到的每个问题都说清楚,能给你的开发前置避坑最好不过。
1. 项目构思:为什么先做水果列表 demo
1.1 HarmonyOS 6.0 PC 真机开发意味着什么
先说一个大背景。HarmonyOS 6.0 的 PC 形态已经不再只是“把手机应用放大到电脑上”这么简单,而是真正要求应用能够运行在桌面窗口环境里,适配键盘、鼠标、多窗口缩放这些桌面级交互。这对开发者的意义在于:你不能再用手机上的布局习惯去写代码了。
我这次拿到的是一个可以实际连接的 PC 真机,系统版本就是 HarmonyOS 6.0。虽然从开发框架角度看,它还是使用 ArkTS + ArkUI 那一套东西,但当你把应用部署进去之后会发现,很多在手机模拟器上表现良好的页面,在 PC 上会出现窗口拉伸、列表留白、鼠标滚轮事件不跟手之类的问题。这些不是逻辑 bug,而是你对“设备形态”的理解还停留在手机。
所以我建议如果你想快速摸清 PC 真机开发的套路,第一个 demo 别选太复杂的项目。一个水果列表应用刚刚好:它包含了数据模型、列表渲染、状态切换、基础交互和自适应布局,几乎把 PC 开发最基础的能力点全覆盖了一遍。等这个列表应用能顺畅跑在电脑桌面上,你再去做复杂业务逻辑就有底了。
1.2 从水果列表起手能学到什么
水果列表这个项目听起来很普通,但它的学习路径非常清晰。第一,你要搞定数据源,是写死数据还是用网络图片;第二,你要处理列表渲染,包括 ArkUI 中 List、ListItem、ForEach 的基本用法;第三,你要做交互,比如点击选中、收藏切换;第四,也是 PC 上最重要的,你要让列表在宽屏下自适应。
我最后实现的是这样一个效果:左侧一列彩色圆形色块,右侧是水果名称和描述,最右侧显示价格。点击整个卡片后,顶部会出现当前选中的水果名称和购买数量。窗口拉宽后,列表自动从单列变成双列,不再是一片空白。
如果你也想做类似效果,下面几节的代码和步骤可以直接照着敲。
2. 工程搭建:从创建项目到多设备适配
2.1 开发环境准备
开发工具方面,我目前用的是 DevEco Studio 的最新稳定版,安装之后在 SDK Manager 里下载了 HarmonyOS 6.0 对应的 SDK。这里有个容易忽略的地方:默认新建项目时,DevEco 会让你选择支持哪些设备。如果你一开始只勾选了 Phone,后面就要手动到module.json5里去补齐 PC 设备的配置,比较麻烦。
建议在向导页面直接勾选Phone、Tablet、2in1这三种设备类型。2in1 就是二合一形态,对应 PC 和平板混合场景,这样应用会自动带上可折叠窗口的适配能力。
另外提醒一点,如果电脑上装过多个版本的 DevEco Studio,一定要检查当前项目用的 SDK 路径,避免出现编译版本不一致导致的“api版本不支持”这类报错。
2.2 创建支持 PC 设备的工程
新建工程的步骤其实不复杂:选择 Empty Ability 模板,语言选 ArkTS,Devices 勾选 Phone 和 2in1,点击 Finish。等工程同步完成后,打开entry/src/main/module.json5,在deviceTypes数组里确认包含了"phone"、"tablet"、"2in1"。如果没有 2in1,手动补上即可。
{ "module": { "name": "entry", "type": "entry", "deviceTypes": [ "phone", "tablet", "2in1" ] } }然后删掉模板自带的pages/Index.ets里那堆测试代码,开始写我们自己的列表页。
2.3 工程目录先看这几处
一个 ArkTS 工程看起来目录很多,但真正需要关注的没几个。entry/src/main/ets/pages放页面文件,entry/src/main/ets/model放数据模型类,entry/src/main/resources/base放字符串、颜色、图片等资源。对于水果列表这种小 demo,我建议把数据模型单独拆出来,而不是把数据写在页面文件里。这样后面如果想替换成接口请求,改动面会小很多。
3. 核心实现:用 ArkTS 写一个水果列表
3.1 数据模型:别把数据写在 UI 里
我先建了一个FruitModel.ets文件,里面定义一个水果实体类。包含名称、描述、价格、颜色四个字段。之所以用颜色字段而不是直接引本地图片,是因为在 PC 真机上调试图片资源时,很容易遇到“开发工具里显示正常但真机上空白”的问题,用纯色块先跑通核心逻辑最省时间,图片问题后面单独排查。
// model/FruitModel.ets export class Fruit { name: string desc: string price: number color: string constructor(name: string, desc: string, price: number, color: string) { this.name = name this.desc = desc this.price = price this.color = color } }然后初始化一组数据:
// pages/Index.ets 中的部分代码 import { Fruit } from '../model/FruitModel' const initFruits: Fruit[] = [ new Fruit('苹果', '红富士,脆甜多汁,适合直接吃', 6.5, '#F56C6C'), new Fruit('香蕉', '进口香蕉,软糯香甜,补充能量', 4.8, '#E6A23C'), new Fruit('橙子', '赣南脐橙,果肉饱满,维C充足', 7.2, '#FFA500'), new Fruit('葡萄', '阳光玫瑰,鲜甜爽脆,冷藏更佳', 15.9, '#67C23A'), new Fruit('草莓', '丹东草莓,个头大,味浓多汁', 22.0, '#FF4D6A'), new Fruit('梨', '库尔勒香梨,皮薄肉厚,清甜润燥', 5.6, '#D4B895') ]数据里的价格我用的是 number 类型,后面展示时通过toFixed(1)控制小数位。如果你要接后端接口,记得先把 JSON 字段名和实体类字段对齐,否则会出现所有价格都是 undefined 的问题。
3.2 List + ForEach:列表的骨架
列表页的 UI 结构是:外层一个Column,顶部放标题和选中信息,下面是List。在 ArkUI 中,List配合ListItem是性能最好的滚动列表方案。用ForEach遍历水果数组,每次渲染出一个自定义行组件。
下面是我实际使用的核心代码:
@Entry @Component struct FruitListPage { @State fruitList: Fruit[] = initFruits @State selectedName: string = '' @State selectedCount: number = 0 @State isWide: boolean = false build() { Column({ space: 12 }) { // 顶部信息栏 Row() { Text(this.selectedName === '' ? '点击下方水果开始选购' : `当前选中:${this.selectedName}`) .fontSize(16) .fontColor('#606266') Blank() Text(`数量: ${this.selectedCount}`) .fontSize(16) .fontWeight(FontWeight.Bold) } .width('100%') .padding({ left: 16, right: 16, top: 12, bottom: 12 }) .backgroundColor(Color.White) .borderRadius(12) // 水果列表 List({ space: 12, lanes: this.isWide ? 2 : 1 }) { ForEach(this.fruitList, (item: Fruit) => { ListItem() { this.FruitRow(item) } }, (item: Fruit) => item.name) } .width('100%') .layoutWeight(1) .scrollBar(BarState.Auto) } .width('100%') .height('100%') .backgroundColor('#F5F7FA') .padding(16) } @Builder FruitRow(item: Fruit) { Row({ space: 12 }) { Circle() .width(44) .height(44) .fill(item.color) Column({ space: 4 }) { Text(item.name) .fontSize(18) .fontWeight(FontWeight.Bold) Text(item.desc) .fontSize(14) .fontColor('#909399') .maxLines(1) .textOverflow({ overflow: TextOverflow.Ellipsis }) } .alignItems(HorizontalAlign.Start) .layoutWeight(1) Text(`¥${item.price.toFixed(1)}`) .fontSize(16) .fontColor('#E6A23C') .fontWeight(FontWeight.Medium) } .width('100%') .padding(16) .backgroundColor(Color.White) .borderRadius(12) .onClick(() => { this.selectedName = item.name this.selectedCount += 1 }) } }这段代码里lanes是关键,它控制列表是一列还是两列。isWide在 PC 宽屏下会自动变成true,实现窗口拉宽后列表自动换列。这就是桌面级应用比手机应用多出来的一个重要适配维度,布局不能写死单列。
3.3 交互与状态:点击、选中、计数
我在点击事件里做了两件事:更新选中的水果名称,同时把购买数量加一。这里@State修饰的变量一旦变化,对应 UI 会自动刷新。在真机上快速连续点击时,你会发现列表和顶部文字几乎是瞬时同步更新的,基本感受不到卡顿,这也说明 ArkUI 的声明式渲染在 PC 端的性能足够日常使用。
如果你想加“是否收藏”这种更复杂的交互,可以在 Fruit 类里加一个isFavorite: boolean字段,在点击时取反,再通过条件渲染切换图标或颜色。核心思路都一样:改状态,UI 自动跟着变。
3.4 PC 自适应:宽屏下自动变双列
PC 屏幕通常比手机宽得多,如果还保持一列,列表内容会拉得特别长,视觉效果很空洞。我第一次在 PC 真机上跑这个 demo 时就发现,单列卡片在 1080p 的显示器上显得又细又长,特别不协调。
解决方式是监听窗口宽度,超过某个阈值就把List的lanes改为 2。我用的是 ArkUI 的媒体查询能力,在aboutToAppear里注册监听:
import mediaquery from '@ohos.mediaquery' const listener = mediaquery.matchMediaSync('(width >= 840vp)') listener.on('change', (result: mediaquery.MediaQueryResult) => { this.isWide = result.matches }) // 页面销毁时记得注销 aboutToDisappear(): void { listener.off('change') }这里注意一点,vp是虚拟像素单位,窗口宽度在不同分辨率下表现会有差异。我在 2560x1440 的屏幕上测试,840vp 大概对应窗口接近一半宽度时触发双列。你实际设备上可以根据效果调整阈值,不必照搬。
4. 真机部署与调试:把 App 跑进 PC 真机
4.1 开发者模式与签名配置
模拟器里能跑通不代表真机没问题。水果 demo 写完后,我第一个动作就是连 PC 真机。真机调试前两部必须要做:打开开发者模式,配置调试签名。
PC 真机上打开开发者模式的方法和手机不太一样,但逻辑是类似的。进入系统设置的“关于本机”,找到版本号,连续点击 7 次,就能解锁开发者选项。然后在“开发者选项”里打开 USB 调试、网络调试,以及“不锁定屏幕”这种方便开发的开关。
接着回到 DevEco Studio,打开File > Project Structure > Signing Configs,勾选“Automatically generate signature”,然后登录华为账号让 IDE 自动生成调试证书和 Profile。这里有个经验:真机调试时如果遇到“设备未信任此电脑”之类的提示,一定要到真机弹窗上手动允许调试授权,否则 IDE 会一直卡在请求认证环节。
4.2 hdc 连接与首次部署
DevEco Studio 连接真机用的是 hdc 工具,类似安卓的 adb。连接前先确认 USB 线是数据传输线,不是只能充电的线。我第一次就栽在一条老旧的“只能充电”线缆上,设备一直识别不出来。
连接成功后,在 DevEco Studio 顶部选择设备列表,选中你的 PC 真机,然后点击 Run。首次编译时间会稍长一些,因为 HAP 包需要在真机上进行安装。整个流程走通后,真机桌面会出现水果列表的 App 图标,点击即可看到运行效果。
如果你更习惯命令行,也可以用 hdc 手动安装包。打包后的 HAP 文件在entry/build/default/outputs/default/entry-default-signed.hap,命令行执行:
hdc install entry-default-signed.hap安装完成后同样用 hdc 启动应用:
hdc shell aa start -b your.bundleName -a YourAbilityName用命令行的好处是能直接在真机上拿到更原始的报错信息,适合排查安装阶段的问题。
4.3 看日志与调试技巧
界面运行过程中如果有异常,第一件事就是看日志。DevEco Studio 自带 Log 面板,能实时显示真机上的应用日志。我在 PC 真机上跑 demo 时就发现一个问题:窗口拉宽后,列表左右两侧的留白非常大,但控制台没有任何报错。
日志能帮你定位异常,但布局问题还得自己在代码里加辅助信息。我的处理方式是在顶部信息栏临时显示当前的isWide状态和窗口宽度,这样拉窗口时就能直观看到断点是否触发,比盯着控制台猜更高效。调试完成后把这些临时 UI 删掉即可。
5. 桌面级运行体验:窗口、键鼠与应用形态
5.1 窗口缩放与断点变化
PC 应用和手机应用一个最大的不同,就是窗口可以随意拉扯。水果列表在 PC 真机上跑起来后,你可以直接把窗口从窄条拉成满屏,观察列表的双列切换效果。
实际体验下来的感觉是:窗口缩放过程非常顺滑,没有明显白屏或卡顿。但要注意lanes在窗口改变时只对后续加载的内容生效,如果滚动位置比较靠下,切换列数后可能会看到一段内容为空的区域。简单解决方式是切换断点后主动滚动到顶部,或者用ScrollToIndex回到一个固定位置。这个问题在列表很长时尤其明显,需要作为 PC 适配的隐藏坑记下来。
5.2 键鼠操作带来的交互差异
在 PC 真机上,鼠标滚轮滚动列表、点击卡片选中这些操作都很自然,但有一点和手机完全不同:PC 有 hover 悬停状态。
手机上的“点击”通常是手指按下后立刻触发,但 PC 用户习惯先移动鼠标、再按下、再抬起。如果应用里有按钮,最好给按钮加上 hover 效果,否则用户会感觉界面“死板”。我在水果卡片上做了一个简单的悬停阴影,体验立刻不一样。
另外,键盘操作也是一个容易被忽略的点。PC 用户习惯用 Tab 键切换焦点、Enter 键确认。虽然 ArkUI 默认支持焦点移动,但如果你用了自定义组件,要确认它的focusable属性和键盘事件是否正确传递。水果列表这个 demo 比较简单,不做键盘优化也能用,但一旦涉及表单或者复杂交互,键盘适配就是必须项。
5.3 运行稳定性与性能观察
我在 PC 真机上连续跑了将近一个小时,反复切换窗口大小、快速滚动列表、点击不同的水果,没有出现闪退或者内存暴涨的情况。不过在列表加长到 100 项后,快速滚动时能感觉到轻微的掉帧。
这个问题的根因在ForEach的 key 生成策略上。我的代码里用了item.name作为 key,在仅有 6 条水果数据时没问题,但列表变长后,重复或动态变化的 key 会影响复用效率。建议在生产环境使用具备唯一性的id字段作为 key,而不是名称这种业务字段。
5.4 PC 桌面的应用形态
应用安装并启动后,PC 真机上会把它当成一个原生桌面应用来管理。任务栏上会有应用图标,窗口标题栏支持最小化、最大化、关闭,还可以通过系统托盘切换窗口层级。
这一点比手机上的“全屏显示”体验更接近桌面端。所以如果后续把 demo 往产品化方向推进,需要提前考虑状态栏、菜单栏、右键菜单这些 PC 特有的 UI 元素。ArkUI 本身已经提供了不少面向 PC 的组件能力,但目前还不是所有移动端组件都自动适配,必须真机验证才靠谱。
6. 常见问题与避坑记录
6.1 真机调试提示“上传失败:网络请求错误”
这个报错我在第一次部署时遇到过,而且非常容易让新手误以为是代码问题。实际排查下来,问题往往不在代码本身,而是真机与电脑之间的调试通道没有建立好。
我当时的情况是:USB 线连接正常,设备管理器也能看到真机,但点 Run 之后进度条卡住,最后提示“真机调试 error: 上传失败:网络请求错误”。后来我把 USB 调试断开重新授权一遍,同时把 WiFi 连接到和电脑同一个局域网,问题就解决了。如果你是无线调试模式,尤其要注意设备与电脑是否在同一网络,网络隔离会导致 hdc 上报文件失败。
还有个容易被忽略的点:手机版、PC 版系统如果开了“隐私模式”或者拦截弹窗的开关,也会影响调试授权弹窗的弹出。先处理网络和授权问题,再去看代码日志,效率会高很多。
6.2 真机预览图片不显示,开发工具上却正常
这是典型的环境差异问题。你在 DevEco Studio 的 Previewer 里能看到图片,但到 PC 真机上图片变成空白,常见原因有三个。
第一,图片资源路径写错了,或者文件名大小写不一致。PC 真机对资源路径的解析比 Previewer 严格,大小写不对会直接加载失败。第二,图片放在了rawfile下面,但业务代码用了$r()去引用。第三,如果是网络图片,PC 真机的安全策略可能会拒绝加载未经处理的 HTTP 图片。
解决思路是:尽量使用本地资源,并且统一放在resources/base/media下;文件名全部使用小写,那样兼容性最好。
6.3 模拟器和真机环境差异
很多教程会教你用模拟器改真机环境,实际上效果非常有限。模拟器能跑通基本逻辑,但模拟不了 PC 真机那种窗口层级、键鼠事件、系统权限弹窗、以及不同硬件下的渲染表现。
尤其是 PC 真机,它的窗口管理跟平板模拟器差异很大。如果你要验证桌面级体验,强烈建议直接上真机。真有临时验证需求,也优先用 DevEco Studio 自带的 2in1 模拟器,而不是拿安卓模拟器改配置,后者容易出现环境误判,排查问题会多绕很多弯。
6.4 常见问题速查表
| 问题现象 | 可能原因 | 处理建议 |
|---|---|---|
| 设备列表看不到真机 | USB 线不支持数据传输 | 换一根数据线,重新插拔 |
| 上传失败:网络请求错误 | 调试授权未确认或网络不通 | 重新授权,确认设备与电脑在同一网段 |
| 安装后启动闪退 | 签名不一致或 HAP 包未更新 | 清理构建产物重新打包 |
| 列表图片空白 | 资源路径或文件名大小写问题 | 使用小写文件名,检查资源目录 |
| 窗口缩放后列表空白 | lanes 切换位置跳动 | 断点切换后主动滚回顶部 |
| 列表加长后滚动掉帧 | ForEach key 不唯一 | 使用唯一的 id 字段作为 key |
| 点击事件偶尔不响应 | 鼠标按下与抬起位置不一致 | 建议使用 onClick 而不是触摸事件 |
7. 从 demo 到 PC 真机开发,我的一些体会
这次把水果列表 demo 完整跑进 HarmonyOS 6.0 PC 真机之后,我对“原生开发”这件事有了更具体的感知。以前写手机页面时,很少会去考虑窗口宽度、hover 态、键盘焦点这些问题,但到了 PC 形态,这些全成了躲不开的功课。好在 ArkTS 的上手成本确实不高,从数据模型到列表渲染,思路和前端开发很像,只要是写过声明式 UI 的人,基本都能快速切入。
最后再分享一个小技巧:PC 真机上调试时,别舍不得用命令行。很多界面显示不出来的问题,通过查看 hdc shell 里的进程和资源文件路径,往往能直接看出原因。等哪天你的应用连 PC 真机都能稳稳跑起来,再回看手机端,你会发现很多所谓的“适配难题”其实只是没有找到合适的调试环境而已。