ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

从零开发鸿蒙PC原生应用:ArkTS与ArkUI实战指南

从零开发鸿蒙PC原生应用:ArkTS与ArkUI实战指南 最近这半年我听得最多的一个词就是“鸿蒙原生应用”。HarmonyOS NEXT正式发布之后很多人的第一反应是鸿蒙设备上还能不能看安卓版本还能不能跑安卓应用答案已经很清楚——系统层不再兼容安卓生态了。这个变化对PC端开发者来说其实是个不折不扣的机会窗口过去你在鸿蒙电脑上能用的原生应用凤毛麟角现在谁先上手ArkTS和ArkUI谁就能吃到第一波红利。这篇不是什么官方文档的复读是我从零开始把一个完整应用跑上PC模拟器之后的复盘。我会把环境搭建、工程创建、Stage模型、ArkTS语言基础、ArkUI界面编写、状态管理、模拟器调试一直到打包签名的完整链路都过一遍。目标读者很明确零基础或者只有Web前端、安卓、iOS经验想切入HarmonyOS PC应用开发的新人。只要你愿意跟着敲一遍这篇文章足够你构建出第一个能跑的“原生应用”。1. 为什么现在切入HarmonyOS PC应用开发是合适时机1.1 HarmonyOS NEXT之后生态发生了什么变化先聊一个很多人没想透的问题HarmonyOS NEXT之前鸿蒙设备可以靠安卓兼容层跑APK所以开发者普遍不着急做原生适配。你随便在社区搜一下“怎么在鸿蒙上查看安卓版本”“安卓应用能不能装”这类问题到处都是各种兼容方案也层出不穷。但注意这些方案依赖的底层通道已经没了。HarmonyOS NEXT从内核到系统服务都是鸿蒙自己的不包含安卓运行时APK安装不了“查看安卓版本”这个需求直接失去了存在的意义。这意味着什么意味着鸿蒙应用市场里你的应用不会再有“免费安卓应用换皮”这类对手。用户打开电脑上的鸿蒙应用商店看到的就是原生应用之间的真刀真枪比拼。对于零基础或者小团队来说这是一个很微妙的窗口期大厂还在观望专业开发者还在从安卓/iOS迁移但市场已经开始需要大量鸿蒙原生产品了。1.2 “原生应用”对开发者意味着什么现在很多文章喜欢拿“原生”当噱头但在鸿蒙的语境下“原生应用”有非常明确的定义用ArkTS语言配合ArkUI声明式框架直接调用鸿蒙系统API编译成HAP格式安装包的应用。它不依赖任何中间壳不内置浏览器内核不走兼容层启动速度、内存占用、权限控制都是系统级的水准。最容易让新手懵的是HarmonyOS PC应用和Windows PC应用完全是两码事。鸿蒙PC应用跑在鸿蒙生态的设备上而不是Windows电脑上。但这并不妨碍它拥有完整的PC级体验——窗口管理、键盘鼠标操作、多任务调度、跨设备流转这些能力在HarmonyOS NEXT里都是原生的。我做第一个PC应用时印象最深的就是窗口自适应同样的代码运行在手机上是一个全屏竖屏界面运行在PC模拟器上自动变成了自由调整大小的窗口布局这套基础能力是微软和苹果给不了的。1.3 零基础需要准备哪些基础我说“零基础”但不是说你连代码都没碰过。最理想的起点是有一点前端经验比如HTML/CSS/JavaScript或者接触过Vue、React你上手ArkTS会非常顺因为声明式UI的思维模型几乎一致。如果你完全是零基础也没关系ArkTS本质上是TypeScript的严格化超集你把它当成一门带装饰器语法的新型语言就行不需要先学C或者Java那种命令式GUI开发。我自己切入前的体验是先花半天弄懂了ArkTS的基本语法和状态管理的几个装饰器然后就直接开始写界面了。先跑起来再回头抠细节这个节奏对新人友好得多也比先啃三个月文档再动手高效得多。2. 搭建开发环境DevEco Studio的安装与工程创建2.1 DevEco Studio版本选择与下载HarmonyOS开发官方IDE是 DevEco Studio基于IntelliJ平台如果你用过Android Studio或者WebStorm界面风格几乎零学习成本。下载时注意选正式版千万别图新鲜装Beta版Beta版经常伴随SDK版本和模拟器组件不匹配的问题新手碰到这类问题会非常打击信心。我建议直接选跟当前最新稳定API版本匹配的DevEco Studio正式版安装时顺手把SDK、模拟器镜像、hvigor构建工具一起勾上。这一套组件是美国时间打完一个勾等下载的同时你就能干别的事情去了。2.2 从新建项目开始走一遍完整向导打开DevEco Studio之后点击“Create Project”默认会进入一个应用模板选择界面。给新手唯一的建议第一个项目选“Empty Ability”空模板不要选“List”“Tab”这些带样板的模板。样板代码读起来很爽但里面夹带的路由、组件封装反而会让新人分不清哪些是必需的、哪些是模板附赠的。创建时的几个关键点项目名称用英文小写例如first-pc-app别用中文和驼峰。Project Type保持默认的Application。Device Type这里一定要勾上PC和2in1否则后面模拟器跑不起来PC形态。Compatible SDK选默认的API版本不要往上调高兼容范围新人不清楚API差异时最容易在这一步埋雷。工程师创建一个空的“Empty Ability”项目把项目结构铺开的那一瞬间你就进入真正的鸿蒙开发世界了。2.3 首次打开工程别被一堆目录吓到用DevEco Studio新建一个项目后左侧Project面板会自动打开此时你会看到一堆文件和目录别慌。真正需要你盯住的只有三块目录/文件作用AppScope应用全局配置包括图标、标签和应用级信息entry模块你的应用入口模块几乎所有的代码都写在这里build-profile.json5项目级编译配置一般不需要手动改entry里面才是主战场entry/src/main/ets/是代码目录entry/src/main/resources是资源目录entry/src/main/module.json5是模块配置。你在IDE里打开entry/src/main/ets/pages/Index.ets就是官方模板给你生成的第一个页面。咱们先不求看懂先在里面找到那行Text(Hello World)把它改成Text(你好鸿蒙PC)然后准备好你的第一个运行调度。3. 动手写代码前必须搞懂的Stage模型和工程结构3.1 AppScope、entry与module.json5HarmonyOS的应用模型叫Stage模型这是API 9之后唯一推荐的模型新项目默认就用它。Stage模型的核心思想是“一个应用由若干个Ability组成Ability是系统调度单元页面是Ability内的展示单元”。工程里的AppScope/app.json5管的是应用级配置对应整个HAP包的“门面”entry模块则是实际的可执行模块对应一个HAP。entry/src/main/module.json5是模块级配置里面非常重要的一段是deviceTypes它直接决定了这个应用能安装在哪些形态的设备上{ module: { name: entry, type: entry, srcEntry: ./ets/entryability/EntryAbility.ets, description: $string:module_desc, mainElement: EntryAbility, deviceTypes: [phone, tablet, pc, 2in1], deliveryWithInstall: true, installationFree: false, pages: $profile:main_pages, abilities: [ { name: EntryAbility, srcEntry: ./ets/entryability/EntryAbility.ets, description: $string:EntryAbility_desc, icon: $media:icon, label: $string:EntryAbility_label, startWindowIcon: $media:startIcon, startWindowBackground: $color:start_window_background, exported: true, skills: [ { entities: [entity.system.home], actions: [action.system.home] } ] } ] } }新手最容易忽略的就是deviceTypes默认模板通常只勾phone或者phonetablet。你要是忘了加pc后面连PC模拟器都不给你装。我最初做PC适配时就是没勾PC类型真机调试时老提示“设备不匹配”折腾了半天才反应过来是配置问题。3.2 EntryAbility启动逻辑拆解EntryAbility是整个应用的入口Ability它继承自UIAbility。UIAbility是什么你可以把它理解成“一个带界面的后台进程单元”它管理着一个或多个页面窗口。我强烈建议你把EntryAbility.ets里的代码从头到尾读一遍官网模板虽然短但五脏俱全import { AbilityConstant, UIAbility, Want } from kit.AbilityKit; import { window } from kit.ArkUI; import { hilog } from kit.PerformanceAnalysisKit; export default class EntryAbility extends UIAbility { onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void { hilog.info(0x0000, testTag, %{public}s, Ability onCreate); } onWindowStageCreate(windowStage: window.WindowStage): void { hilog.info(0x0000, testTag, %{public}s, Ability onWindowStageCreate); windowStage.loadContent(pages/Index); } }这里面值得注意的就是onWindowStageCreate。窗口舞台WindowStage创建完成后loadContent(pages/Index)把页面资源加载进窗口。如果你看懂了这一行就明白为什么路径字符串是pages/Index而不是pages/Index.ets——它对应的是main_pages.json里的页面映射不是文件系统路径。这段代码也是你以后做窗口定制的地方比如在PC端设置默认窗口宽高、设置窗口模式都是在onWindowStageCreate里通过windowStage.getMainWindowSync()拿主窗口对象来操作的。3.3 页面的加载流程从Ability到页面很多前端转过来的朋友会拿HarmonyOS的Ability类比Android的Activity或者iOS的ViewController大方向没错但有个重要区别一个UIAbility可以承载多个页面页面页面之间通过路由跳转。模块里到底有哪些页面都在entry/src/main/resources/base/profile/main_pages.json里声明{ src: [ pages/Index, pages/Detail ] }你每新建一个页面如果不想手动加路由IDE会在New Page向导里自动帮你写进去。记住一个原则只有声明在这个文件里的页面才能被loadContent和router.pushUrl访问到。新同学经常犯“文件创建了但没注册路由一运行白屏”的错就是这个原因。4. ArkTS与ArkUI换一种方式思考UI开发4.1 ArkTS到底改了什么ArkTS是TypeScript的严格子集目的是为了性能和安全做静态约束。最明显的变化有几点禁止any类型所有变量必须有明确类型官方给出的理由是静态类型有助于编译期优化。推理一切object字面量都得用类或者interface来约束不允许未声明的字段。禁止解构赋值解构在ArkTS里不支持习惯了ES6解构的人一开始会很不习惯。结构化并发并发场景下用TaskPool和Worker不能直接裸跑线程。看起来限制很多但实际写起来你会发现这些限制反而让代码更规整了。它的目标不是让你用TS写一遍而是让你用“鸿蒙风格”去写。4.2 声明式UI组件的基本写法ArkUI的语法用的是“声明式”和React、Vue的组件化很像。你定义一个组件就是一个Component装饰器修饰的struct对象用build()方法来描述UI结构Entry Component struct HelloPage { build() { Column({ space: 16 }) { Text(你好HarmonyOS PC) .fontSize(30) .fontWeight(FontWeight.Bold) Button(点我) .onClick(() { console.info(按钮被点击了); }) } .padding(24) .width(100%) .height(100%) } }要注意的是组件配置参数和属性方法的区别非常关键。Text(你好)是构造函数.fontSize()、.fontWeight()是通用属性.onClick()是事件方法链式调用让UI代码读起来很像“描述一个控件长什么样”但本质上是一组配置命令的堆积。第一次用ArkUI时我最不习惯的是没有CSS类名和选择器每个组件都要单独写样式。不过等写多了你会发现它其实比CSS更直观你看到Text就知道是文本看到.fontSize(30)就知道字多大不需要来回翻样式表。4.3 状态管理的核心State/Prop/Link状态管理是ArkUI和ArkTS里最值得下功夫的地方因为它直接关系到一个经典问题用户点击了按钮界面怎么自动更新以State为核心。State修饰的数据一旦变化所有依赖它的UI组件会自动重新渲染。举个例子Entry Component struct CounterPage { State count: number 0; build() { Column({ space: 20 }) { Text(当前计数${this.count}) .fontSize(24) Button(增加) .onClick(() { this.count; }) } .padding(24) } }你不需要手动调用任何“刷新”方法只要改变this.count界面上的文本立刻刷新。这种响应式思想跟Vue的ref和reactive很像。当组件拆分之后父子组件之间的通信靠Prop和LinkProp单向同步父组件变子组件跟着变子组件自己改不影响父组件。Link双向同步父子之间任一端修改另一端都会刷新。Component struct ChildCounter { Link count: number; build() { Button(子组件加一) .onClick(() { this.count; }) } } Entry Component struct ParentPage { State count: number 0; build() { Column() { ChildCounter({ count: $count }) } } }重点在ChildCounter({ count: $count })这个用法$count的$前缀就是“引用传递”的意思把父组件的状态和子组件的Link绑定起来。如果你要传一个普通值就用count: this.count要传引用就用count: $count。这个区别一旦搞混状态同步就会出问题。4.4 常用布局组件Column、Row、ListArkUI布局组件开始不像CSS那么复杂因为核心就几个Column垂直排列相当于flex-direction: columnRow水平排列相当于flex-direction: rowList高性能滚动列表配合ListItem使用Grid宫格布局Stack层叠布局适合做悬浮元素如果你理解CSS Flexbox这些布局组件上手就是半天的事。justifyContent和alignItems是每个布局组件的标配用来控制主轴和交叉轴的对齐。PC端的窗口宽则可以用layoutWeight实现比例缩放Child组件设置layoutWeight(1)后会占据除其他固定尺寸组件外的剩余空间这个概念相当于Flex flex-grow。看一个比较实用的布局写法Column() { Text(顶部标题).fontSize(20) List() { ForEach([1, 2, 3, 4, 5], (item: number) { ListItem() { Text(第${item}项) .width(100%) .padding(16) } }) } .layoutWeight(1) .width(100%) }这里List配合.layoutWeight(1)实现了“中间列表填满剩余空间”的效果这在PC窗口拉伸时会非常自然。5. 完整实现第一个原生应用待办清单5.1 拆解需求并设计数据模型理论聊太多容易飘我拿一个实际的“待办清单”应用来演示从空工程到可交互应用的完整过程。需求很简单共三个操作输入一条待办内容点击“添加”按钮把内容加入列表点击“完成”按钮标记这一条为已完成状态在动手写界面之前先定义数据模型。ArkTS对类型约束很严我建议直接用class而不是interface因为class在状态管理里的行为更明确class TodoItem { id: string ; text: string ; completed: boolean false; constructor(id: string, text: string, completed: boolean false) { this.id id; this.text text; this.completed completed; } }你可能会问一个简单的待办何必定义一个class因为ArkUI的ForEach特别依赖一个稳定唯一的key来追踪列表项如果你使用index作为key当删除、排序后就会出现组件复用混乱的问题。用id这种唯一标来识别才是列表状态安全的保证。5.2 编写入口页面与组件接下来进入entry/src/main/ets/pages/Index.ets把整个页面写出来。我直接给出核心代码注释都写在关键行里Entry Component struct Index { State todoList: TodoItem[] []; State inputText: string ; private inputController: TextInputController new TextInputController(); build() { Column({ space: 16 }) { Text(我的待办清单) .fontSize(28) .fontWeight(FontWeight.Bold) .width(100%) Row({ space: 8 }) { TextInput({ placeholder: 输入一条待办, text: this.inputText }) .layoutWeight(1) .height(48) .onChange((value: string) { this.inputText value; }) Button(添加) .height(48) .onClick(() { this.addTodo(); }) } .width(100%) List({ space: 12 }) { ForEach(this.todoList, (item: TodoItem) { ListItem() { Row({ space: 12 }) { Text(item.text) .fontSize(18) .layoutWeight(1) if (item.completed) { Text(已完成) .fontSize(14) .fontColor(#999999) } else { Button(完成) .fontSize(14) .backgroundColor(#007AFF) .onClick(() { this.toggleTodo(item.id); }) } } .padding(16) .backgroundColor(item.completed ? #EDEDED : #F5F5F5) .borderRadius(12) } }, (item: TodoItem) item.id) } .layoutWeight(1) .width(100%) } .padding(24) .width(100%) .height(100%) } addTodo(): void { const text this.inputText.trim(); if (text) { this.todoList [...this.todoList, new TodoItem(Date.now().toString(), text)]; this.inputText ; this.inputController.caretPosition(0); } } toggleTodo(id: string): void { this.todoList this.todoList.map(item { if (item.id id) { return new TodoItem(item.id, item.text, !item.completed); } return item; }); } }这段代码看下来你应该能感受到什么叫做“声明式UI”了。界面结构本身就是代码的样子状态一变UI自动陪你。特别说明一下第8行的TextInputController它是用来控制输入框行为的“遥控器”。因为我们把text: this.inputText传给了输入框输入框的显示内容会受到状态管理的约束如果你只用this.inputText 清空状态部分版本下输入框可能不会自动清空所以配合控制器手动让光标回到起点才能稳定触发刷新。5.3 添加交互逻辑新增与完成上面的addTodo()和toggleTodo()就是完整的交互逻辑。注意两个容易踩坑的细节用Date.now().toString()作为临时id虽然每次不一定保证严格全局唯一但对于本地记内存的演示应用足够这种方式也能避免用index做key带来的列表错乱。更新状态时我用的方式是“生成新数组”this.todoList [...this.todoList, ...]。ArkUI的State对数组的检测依赖引用变化如果你直接调用this.todoList.push()很可能不触发UI更新。所以一定要用展开运算符或者filter/map这类返回新数组的方式。这是一个新手最容易碰到的“我明明改了数据界面为什么不动”的经典坑。5.4 PC端点窗口尺寸与布局适配刚才的代码在手机上能跑在PC上跑出来也基本可用但PC窗口通常比较宽全屏拉伸后“顶栏输入框列表”挤在左上角会有点奇怪。这时我们可以利用ResponsiveContainer或者GridRow做响应式适配也可以简单地限制内容区最大宽度Column() { // ... } .padding(24) .width(100%) .height(100%) .alignItems(HorizontalAlign.Center)然后给列表中每个ListItem设置constraintSize({ maxWidth: 800 })同时让列表居中。这样PC上窗口再宽列表内容也不会被拉成“一条横带”整体视觉更像一个PT了。更进阶的适配方案是用GridRow的栅格系统不同断点给不同列数比如窄屏一列、宽屏两列。这里点到为止等你在PC端项目里真正碰到布局失衡再回头深入研究就行。6. 跑通全流程模拟器调试、构建与发布准备6.1 模拟器的创建与使用代码写完之后激动人心的时刻就是点右上角的“Run”按钮。不过在此之前需要确保有一个可以运行的模拟器。DevEco Studio右侧工具栏找到Device Manager点击Local Emulator第一次会让你下载系统镜像这个大取决于你的网络但这是一个一次性投入。镜像下载完成后新建一个虚拟设备设备类型选择包含PC或2in1的配置。如果你在项目创建时没勾选PC设备类型这里会直接告诉你“当前工程不支持该设备”不用怀疑回module.json5加上pc和2in1重新sync。模拟器启动后再点RunIDE会自动编译HAP并安装到模拟器整个过程首测大概两三分钟。第一次跑起来的那一刻你会看到一个真正的PC窗口出现在模拟器桌面里可以拖拽、可以缩放和普通Windows程序没区别但底层是鸿蒙系统。6.2 用日志快速定位问题真到运行阶段最常用的调试手段已不是断点而是日志。HarmonyOS打印日志的工具是hilog调试时在代码里随便打一条hilog.info(0x0000, MyAppTag, addTodo called, inputText%{public}s, this.inputText);注意%{public}s这样的格式占位符这是安全日志规范。你用console.info也能打日志但运行时在HiLog面板里能看到更清晰的结构。当应用崩溃时打开IDE底部的Log面板切到HiLog模式搜FATAL或者Exception通常错误堆栈会直接告诉你具体是哪一行出了问题。以后碰到“页面空白”“点击没反应”的灵异问题先看日志这比在代码里瞎猜高效得多这是我从实践中得到的最大一条教训。6.3 真机/PC部署注意什么模拟器跑通了下一步往往是想装到真机上。真机部署需要两样东西一台鸿蒙NEXT的设备一个开发者签名证书。开发者证书和Profile文件要去AppGallery Connect网站申请个人开发者账号免费但需要实名认证。创建应用时Bundle Name填工程的app.json5里那个bundleName比如com.example.firstpcapp。签名配置在File Project Structure Signing Configs里勾选“Automatically generate signature”后DevEco Studio会自动引导你登录华为账号、生成调试证书和Profile。这里最容易踩坑的是调试证书和Profile都绑定设备UDID所以你需要在Device Manager里连接真机并开启USB调试IDE会自动帮你把设备注册进去。如果只做本地开发测试用自动签名就够了。真机连接后点Run看到桌面图标长出来的那一刻你会觉得前面踩的雷全都值。6.4 签名、打包和上线前检查最后一个环节是把应用从一个调试用的HAP变成一个可以上架的分发包。鸿蒙应用主要交付形态有HAP单个模块包和APPApp Pack含一个或多个HAP。在DevEco Studio里Build Build App Bundle(s)...即可生成.app包。上架前务必检查这几件事检查项说明bundleName唯一性应用商店里全局唯一上线后不能改命名格式推荐反向域名图标和标签在AppScope/resources里替换默认图标尺寸按规范准备隐私声明如果用到位置、相机等权限必须在module.json5声明并在应用内说明用途版本号在app.json5的versionCode和versionName里维护上架后只能递增HarmonyOS应用市场的审核周期亲测比安卓短不少尤其PC端应用还处于抢夺早期市场阶段审核侧面对新形态产品会比较宽容。但有一点验证别跳过务必用模拟器和真机各跑一遍完整流程尤其是窗口缩放的布局表现。最后的最后再分享一个我动手过程中的体会写HarmonyOS PC应用和写传统桌面软件的思维完全不同它更像是“用Web前端的姿势做操作系统级应用”。尤其ArkUI声明式写法和状态管理一旦习惯了你的开发速度会非常快比之前在安卓上用XML写布局快一个量级。如果你看完这篇已经在电脑前打开了DevEco Studio我的建议是先把那个“待办清单”完整敲出来哪怕照抄都行。敲完这一个应用你对ArkTS语法、ArkUI组件、状态管理、工程结构这四件事就会建立完整的肌肉记忆下一步无论要做什么PC应用道路都会通畅很多。
返回列表