鸿蒙开发最近热度又上来了,尤其是PC版逐步放出来之后,问“做鸿蒙APP和PC应用到底怎么入手”的人越来越多。我基于最近一个从零到一的真实小项目,把整个链路完整跑了一遍:从DevEco Studio装环境、ArkTS写界面、状态管理、页面路由、本地存储,到多端适配思路、PC形态适配、调试与打包上架,甚至包括过程中踩过的几个典型的坑。这篇东西不求面面俱到,但求每一步都落地,拿过去能照着弄。

不管你是从Android/iOS转过来的移动端老手,还是写前端想蹭鸿蒙风口的新人,又或者是被公司派活做“鸿蒙化适配”的倒霉蛋,这篇都有参考价值。我会把技术选型背后的理由也讲清楚,避免你光抄代码、不懂为什么要这么干。

1. 整体设计与技术选型思路

1.1 这个项目要解决的到底是什么问题

先说背景。我这边的目标不是写一个Demo完事,而是要做一个真正能装到手机上、也适配PC窗口形态的鸿蒙应用,覆盖APP端和PC端两个入口。功能其实不复杂:一个带分类的日常事项记录工具,支持待办勾选、数据本地保存、统计展示。但正是这个“不复杂”的功能,把鸿蒙开发的骨架全串起来了。

如果你也是第一次做鸿蒙,建议先别急着上复杂业务,用这种“功能简单但链路完整”的项目练手最合适。因为鸿蒙开发最大的门槛不在业务逻辑,而在于整个开发范式和Android/iOS都不一样,这和前端、后端都不同:声明式UI、状态驱动、分布式能力、权限模型、多设备适配,这些基础概念没搞清楚,后面写稍微大一点的工程就会卡壳。

这个项目覆盖了几个关键链路:

  • 从工程创建到真机/模拟器运行
  • ArkTS声明式UI编写页面
  • 页面路由与参数传递
  • 状态管理与数据响应式更新
  • 本地持久化存储
  • 构建打包与签名配置
  • PC窗口形态的适配思路

这几条串起来,基本就是鸿蒙应用开发的“主血管”。

1.2 技术栈选择:为什么是ArkTS + ArkUI + DevEco Studio

鸿蒙应用开发目前主流技术栈是:DevEco Studio作为IDE,ArkTS作为主要开发语言,ArkUI作为声明式UI框架,配合Stage模型管理应用生命周期。

这里说说为什么选这个组合。

ArkTS是首选语言 。虽然系统支持Java(旧版)和C++(通过NDK),但新项目从官方推荐到社区落地,都是ArkTS为主。ArkTS本质上是TypeScript的超集,在TS基础上加了静态类型约束和UI开发相关的语法糖。如果你写过Vue3或者React+TS,上手会非常顺。我第一次打开ArkTS文件的感觉是“这不就是Vue的setup语法换个皮吗”,虽然实际细节有差异,但这个迁移成本确实很低。

ArkUI是UI层核心 。它属于声明式UI,结构上和SwiftUI、Jetpack Compose是同一类东西,但语法更接近前端习惯。用@State装饰器管理状态,用build方法描述界面,数据和视图自动双向绑定。刚开始不熟悉这套写法会有点不习惯,但等你习惯了状态驱动视图之后,再回头写命令式UI真的会觉得难受。

Stage模型 这个一定要提前搞懂。新版HarmonyOS应用的入口结构,从“一个Ability打天下”变成了“Module → Ability → UIAbility”的层级关系,每个UIAbility有独立的生命周期。这直接影响你怎么设计应用入口、怎么管理页面栈,所以开头就把它梳理清楚,后面少踩很多坑。

1.3 “一次开发,多端部署”的真实含义

鸿蒙宣传最多的一句话是“一次开发,多端部署”,但这句话容易被误解为“写一套代码到处跑,完全不用改”。真实情况是: 逻辑层可以复用,UI层必须做适配 。

手机屏幕窄长,触控为主;PC窗口宽大,鼠标键盘为主;折叠屏和Pad介于两者之间。同样一个列表页面,手机上是全屏卡片流,PC上可能是左侧导航+右侧内容的三栏布局。ArkUI提供的解决方案是栅格布局(GridRow/GridCol)、断点监听(breakpoints)、自适应拉伸(Flex自适应、百分比)这几种机制的组合。

所以我在设计项目时,从一开始就把“多端适配”当成核心需求来写,而不是功能做完再补适配。鸿蒙的响应式布局不是在PC上简单把窗口拉大就行,而是要利用系统能力做布局重构。这点我会在第4节单独展开。

2. 环境准备与首个工程搭建

2.1 DevEco Studio安装与版本坑

去官网下载DevEco Studio,装完之后会带一个HarmonyOS SDK Manager,类似Android Studio的SDK Manager。这里有几个版本层面的坑需要提前说:

  • 新版本API(比如API 12+)老设备不支持,需要根据目标机型选择合适的compileSdkVersion。
  • 模拟器方面,手机上装模拟器还好,PC上跑模拟器对硬件要求不低,不过开发阶段用Previewer看改版效果也够用。
  • 首次新建工程时需要自动下载SDK和构建工具,国内网络环境有时候会卡住,出现“下载超时”别慌,多试几次,或者手动配置镜像源。

这个环节看起来琐碎,但一定别跳过,我把好几个人问过的问题都列出来:创建工程后提示“SDK component missing”、Previewer不能实时刷新、模拟器启动黑屏。这些大概率是SDK版本不匹配或者预览器缓存问题。处理方式分别对应:打开SDK Manager重新安装对应版本组件、删除Previewer缓存重启IDE、冷启动一次模拟器。

2.2 创建工程与目录结构理解

新建工程时选择 Empty Ability模板 ,然后选择Stage模型、ArkTS语言。工程建好后,目录结构大概长这样:

AppScope/
  app.json5          // 全局配置,应用图标、名称等
entry/
  src/main/
    ets/
      entryability/
        EntryAbility.ets   // 页面入口能力,管理生命周期
      pages/
        Index.ets          // 首页
    resources/
      base/
        element/            // 字符串、颜色等资源
        media/              // 图片资源
    module.json5            // 模块配置,权限声明等
build-profile.json5        // 构建配置
oh-package.json5           // 依赖声明,类似package.json

这里面最需要理解的是 EntryAbility.ets 和 pages 目录的关系。 EntryAbility.ets 相当于应用的入口Activity,它负责加载首页并管理整个应用的生命周期; pages 下面就是实际的页面组件,每个 .ets 文件就是一个可独立路由的页面。

对于前端转过来的朋友,我做个对照:

  • module.json5 类似 app.json (小程序)或 AndroidManifest.xml ,负责全局配置。
  • pages 目录类似 pages 路由目录。
  • oh-package.json5 类似 package.json ,管理三方依赖。
  • EntryAbility.ets 类似 App.vue + main.js 的入口角色。

搞清这个映射关系,你的第一反应就不会是“这什么鬼”,而是“哦,这不就那啥嘛”。

2.3 快速跑通Hello World

新建工程默认的 Index.ets 已经有一个页面示例,先别改,直接点击顶部工具栏的运行按钮,选模拟器或Previewer看一眼效果。如果这一步能正常跑通,你的鸿蒙开发环境就基本OK了。

跑通之后,可以试着改一下页面的标题和文本,体验一把热更新。改文件保存后,Previewer一般自动刷新,模拟器上则需要点击重新运行。

在开始写业务代码前,我建议你先做一个“十分钟小练习”:写一个页面,放一个TextField输入框和一个Button按钮,点击按钮后把输入内容显示在页面下方。这个练习涉及@State状态、TextInput组件、Button组件、@Builder自定义构建函数,练完你对ArkUI的基础写法就有手感了。

我当时的练习代码长这样,你也可以直接复制跑一下:

@Entry
@Component
struct Index {
  @State inputValue: string = ''
  @State displayValue: string = ''

  build() {
    Column({ space: 16 }) {
      TextInput({ placeholder: '请输入内容', text: this.inputValue })
        .onChange((value: string) => {
          this.inputValue = value
        })
        .height(48)
        .backgroundColor('#f5f5f5')

      Button('显示内容')
        .onClick(() => {
          this.displayValue = this.inputValue
        })

      Text(this.displayValue)
        .fontSize(20)
        .fontWeight(FontWeight.Bold)
    }
    .width('100%')
    .padding(16)
  }
}

这段代码看起来简单,但已经把ArkUI最核心的机制走了一遍: @State 声明响应式状态、 build 声明式描述UI、组件事件绑定。你只要把这三个点练熟,后面写复杂页面就是“复制粘贴+调整结构”的事。

3. APP端实战:一个带统计功能的事项管理应用

3.1 页面结构与路由设计

我这次做的是一个事项管理应用,整体有两类核心页面:

  • 首页列表页:展示事项列表,支持新增、勾选、删除、分类筛选
  • 统计页:展示完成率、各分类数量等统计图表(用基础组件拼,不引三方库)

页面结构不复杂,但路由跳转、参数传递是必练的点。

鸿蒙路由使用 router 模块,跳转方式有两种:

// 方式一:通过router模块跳转
import { router } from '@kit.ArkUI'

router.pushUrl({
  url: 'pages/StatisticsPage',
  params: {
    category: 'work'
  }
})

// 方式二:Navigation组件,类似Flutter的Navigator

如果做的是单模块中小型应用,用 router 就够了 ,简单直接;但如果你要做底部Tab+多页面栈的大型应用,建议用 Navigation 组件来做,因为它的页面栈管理更灵活,支持 Navigation 嵌套、转场动画自定义等高级特性。

页面间传参时,接收方的写法:

import { router } from '@kit.ArkUI'

@Entry
@Component
struct StatisticsPage {
  @State category: string = ''

  aboutToAppear(): void {
    const params = router.getParams() as Record<string, string>
    if (params) {
      this.category = params.category
    }
  }
}

这里有个坑要注意: router.getParams() 返回的是 Object 类型,最好先判空再断言类型,不然编译时可能报空指针相关警告 。我刚写的时候直接强制断言类型,传参没问题,但不传参进入页面时会拿到 undefined ,后面一用就崩。

3.2 ArkTS状态管理:@State、@Prop、@Link、@Observed

ArkUI的状态管理是整个框架的魂。理解好这几个装饰器,你的页面就越写越顺手。

  • @State :组件内部的状态,变化时自动刷新当前组件。
  • @Prop :父组件传给子组件的值,单向同步,子组件内的修改不会影响父组件。
  • @Link :父子组件双向同步,子组件修改会同步回父组件。
  • @Provide / @Consume :跨多层级传值,类似Vue的 provide/inject 。
  • @Observed + @ObjectLink :用于观察对象内部属性变化,适合复杂数据模型。

我用一个实际场景说明区别。在事项列表应用里,条目组件 TodoItem 需要显示待办内容,并支持点击勾选。如果我直接传一个 Todo 对象给子组件,子组件内修改 todo.checked ,父组件的列表数据不会感知到变化,因为普通对象不是响应式的。解决方案就是:

方案A:把选中状态提升到父组件,父组件用@State数组保存,子组件通过@Link绑定当前项的选中值。

方案B:给Todo类加上@Observed装饰器,子组件用@ObjectLink接收,这样对象内部属性变化也会触发刷新。

我实际选的是方案B,代码大概是这个样子:

@Observed
class Todo {
  id: number = 0
  title: string = ''
  checked: boolean = false
  category: string = ''
  constructor(id: number, title: string, category: string) {
    this.id = id
    this.title = title
    this.category = category
  }
}

@Component
struct TodoItem {
  @ObjectLink todo: Todo

  build() {
    Row({ space: 12 }) {
      Checkbox()
        .select(this.todo.checked)
        .onChange((value: boolean) => {
          this.todo.checked = value
        })
      Text(this.todo.title)
        .strikethrough(this.todo.checked)
    }
  }
}

注意 @Observed 只能观察“对象内部已定义的属性”,如果给对象动态加新属性,不会触发UI刷新。所以 定义模型时要把可能用到的字段都声明好 ,别学JS那套动态挂属性的用法。

3.3 数据持久化:首选项(Preferences)与关系型数据库的取舍

事项数据不能一重启就丢,所以必须做本地持久化。HarmonyOS提供了两种基础方案:

  • 首选项(Preferences) :类似Android的SharedPreferences,适合轻量键值对存储,比如用户设置、配置信息。
  • 关系型数据库(RDB) :基于SQLite封装,适合结构化数据、查询需求复杂的数据。

对事项列表这种数据,最合理的做法是用RDB存数据、用Preferences存“当前选中的分类”这类轻量状态。

RDB的建表和插入操作,代码流程如下:

import { relationalStore } from '@kit.ArkData'
import { common } from '@kit.AbilityKit'

const STORE_CONFIG: relationalStore.StoreConfig = {
  name: 'todo_app.db',
  securityLevel: relationalStore.SecurityLevel.S1
}

let store: relationalStore.RdbStore | null = null

async function getStore(context: common.UIAbilityContext): Promise<relationalStore.RdbStore> {
  if (store) return store
  store = await relationalStore.getRdbStore(context, STORE_CONFIG)
  await store.executeSql(
    'CREATE TABLE IF NOT EXISTS todo (id INTEGER PRIMARY KEY AUTOINCREMENT, title TEXT NOT NULL, checked INTEGER NOT NULL DEFAULT 0, category TEXT NOT NULL)'
  )
  return store
}

写入数据用 store.insert ,查询用 store.querySql ,更新用 store.update 。SQL语法和SQLite基本一致,写过Android数据库的会非常熟悉。

这里有个真实踩坑: 第一次调 getRdbStore 必须在UIAbilityContext下执行 ,不能在组件里直接调。如果你的页面里突然发现拿不到context,检查一下是不是忘了从 getContext(this) 获取。

写入数据后,页面要刷新,通常的做法是把数据库查询结果放到 @State 数组中。但要注意, @State 在初始化时赋值一次,后面手动修改需要直接替换整个数组引用 ,不能只调 push 或 splice 就指望UI跟着变。

3.4 列表渲染:ForEach的key生成规则

列表是APP应用最核心的组件之一。ArkUI里用 ForEach 渲染列表,但它和普通的前端 v-for 有一个很大区别: ForEach必须要给 keyGenerator ,用来唯一标识每一条数据 。

ForEach(
  this.todoList,
  (todo: Todo) => {
    TodoItem({ todo: todo })
  },
  (todo: Todo) => todo.id.toString()
)

第三个参数就是keyGenerator。如果不传或者乱传(比如传index),在删除某条数据时可能会出现界面错乱,因为ArkUI的diff算法依赖key来判断哪条是新增、哪条是删除。

key不要用index的主要原因是:当列表插入或删除一条数据后,index会整体后移或前移,系统会误以为原来的条目被删了、新条目加进来了,导致不必要的组件重建,严重时还会产生动画错乱。 用数据库自增id做key是最稳的 。

4. PC应用方向:多端适配与桌面形态实战

4.1 鸿蒙PC应用到底是个什么形态

这个节骨眼上,鸿蒙PC版已经不只是概念了,开发版的PC窗口形态应用已经能跑起来。从开发者的视角看,PC应用不是独立的一套技术栈,而是“同一个Ability在不同窗口尺寸下的差异化呈现”。

也就是说,你的手机APP和PC应用可以共用一套工程,用系统提供的断点去判断窗口宽度,进而切换不同的布局策略。这也是“从APP到PC应用实战”这个标题真正的含金量所在。

我做了几个适配点,原则是移动端单栏、PC端多栏:

4.2 用断点和栅格布局做响应式结构

ArkUI提供 GridRow 和 GridCol 来实现栅格布局。以手机竖屏(小于600vp)为单列布局,平板或PC宽屏(大于840vp)为双栏或三栏布局。

我做的适配逻辑是:

@State currentBreakpoint: string = 'sm'

aboutToAppear(): void {
  this.updateBreakpoint()
}

// 通过窗口宽度判断当前断点
private updateBreakpoint(): void {
  const windowWidth = this.getWindowWidth()
  if (windowWidth >= 840) {
    this.currentBreakpoint = 'lg'
  } else if (windowWidth >= 600) {
    this.currentBreakpoint = 'md'
  } else {
    this.currentBreakpoint = 'sm'
  }
}

@Builder
ListPage() {
  // 列表内容
}

@Builder
SideBar() {
  // 侧边栏内容
}

build() {
  if (this.currentBreakpoint === 'lg') {
    Row() {
      this.SideBar()
      Divider().vertical(true)
      this.ListPage()
      Divider().vertical(true)
      this.StatisticsPanel()
    }
    .width('100%')
    .height('100%')
  } else {
    Column() {
      this.ListPage()
    }
    .width('100%')
    .height('100%')
  }
}

这段代码反映了核心思想: UI布局可以按断点拆成多个Builder函数,在大屏组合,在小屏堆叠 。逻辑层(数据获取、状态管理)完全不用改,UI层换一套组合方式。

4.3 输入与交互适配:鼠标、键盘、触控

PC端和移动端最大的差异是输入方式。手机靠触摸、滑动手势;PC端,用户首先会Expect鼠标悬停反馈、右键菜单、滚轮滑动、快捷键操作。这几个点如果不做,在PC上就是“能看但不好用”。

我在项目里做了三件小事:

  • 列表项加入 hoverEffect 属性,鼠标悬停时有高亮反馈。
  • 长按操作在PC上换成右键菜单,用 bindContextMenu 绑定。
  • 支持键盘上下键在列表间快速选择,回车切换勾选状态。

这些适配工作量不大,但对PC端的体验提升非常明显。尤其是 右键菜单 ,手机端用户完全无感,但PC端用户下意识就会右键,这个不做会觉得“这应用不像原生的”。

4.4 多窗口能力和元服务扩展

PC端还有两个值得关注的点: 自由调整窗口大小 和 后台任务/元服务全局能力 。

自由调整窗口方面,手机上的应用窗口基本是固定比例,PC上窗口可以被用户拉到很窄或很宽,所以上述的断点适配需要在窗口尺寸变化时实时响应。ArkUI提供了 onWindowSizeChange 这类回调,也可以监听窗口变化事件重新计算断点,这个必须做,否则用户一拉窗口布局就乱。

至于元服务和卡片,属于另一个大的扩展方向。元服务可以做成桌面卡片、负一屏卡片等轻量形态,PC端也可以放桌面小组件。我的项目里做了一版简单的卡片,显示今天待办数量,点击跳进主应用。这个扩展方向很适合工具类应用,因为用户不需要打开完整APP就能看到关键信息,符合“快”的需求。

5. 调试、打包与常见问题排查实录

5.1 日志与调试工具链

鸿蒙开发的日志打印用的是 hilog ,类似Android的 Logcat 。基本用法:

import { hilog } from '@kit.PerformanceAnalysisKit'

const DOMAIN = 0x0001
const TAG = 'TodoApp'

hilog.info(DOMAIN, TAG, '当前事项数量: %{public}d', this.todoList.length)

注意 %{public}d 这个写法,鸿蒙的hilog要求显式标记“是否隐私”,公共信息用 public ,隐私信息用 private 。如果直接写 %d 会遇到格式无法正确打印的问题。

DevEco Studio内置的调试功能比较全,包括断点调试、性能分析器(Profiler)、内存检测。性能分析器在PC模拟器上比较吃资源,实测下来启动会比较慢,但分析问题确实很好用,比如卡顿定位可以用帧率分析。

5.2 真机调试与签名配置

模拟器跑了没问题之后,一定要拿真机试一遍。真机调试需要:

  • 手机上开启开发者模式,USB连接电脑。
  • DevEco Studio里配置自动签名。首次需要登录华为开发者账号并同意相关协议,系统自动生成调试证书和Profile。
  • 如果是商用发布,还需要申请签名证书和发布Profile,走AGC(AppGallery Connect)流程。

真机调试有个我印象很深的坑: 应用签名不一致导致安装失败 。现象是卸载旧包后仍然提示签名冲突,这是因为系统缓存了旧的授权指纹。处理方式是去手机设置里把“应用热修复”相关的开发者授权清掉,或者手动重启开发手机。

另外, HarmonyOS NEXT版本对权限管理非常严格 ,如果你的应用用到了敏感权限(比如读取位置、读取通讯录),首次申请时要配合 onPermissionRequest 回调,建议在代码里做“申请弹窗被拒绝后的引导跳转设置页”逻辑。

5.3 常见问题速查表

我把开发过程中遇到的比较典型的问题整理成了一个表格,希望对你有直接帮助。

问题现象 可能原因 解决方式
Previewer不显示内容 Previewer缓存异常或组件不兼容 删掉预览器缓存,重启IDE;或改用模拟器验证
编译报错“Cannot find module” 三方库未安装或路径不对 在oh-package.json5中检查依赖,执行 ohpm install
状态更新但UI不刷新 @State引用未替换;@Observed未正确标记属性 替换整个数组引用;确保对象属性事先声明
数据库打开失败 没有正确传入Context 使用getContext(this)而不是全局静态Context
真机安装失败签名冲突 自动签名和缓存签名不一致 删除工程签名配置,重新自动签名;重启手机
PC窗口拉伸布局错乱 没有监听窗口尺寸变化并更新断点 增加窗口尺寸监听并动态刷新breakpoint
应用在PC上触摸与鼠标操作冲突 触控和鼠标事件未区分 使用hoverEffect、右键菜单等PC专属交互
hilog日志不输出 没有打tag或过滤条件不对 检查TAG和DOMAIN,在DevEco过滤栏搜TAG

5.4 PC端升级刷机场景的一个补充提醒

评论区可能有人问“把开发电脑升级成鸿蒙PC版后进不了系统怎么办”。这属于设备升级场景,不是应用开发问题,但我也提一嘴:如果你是拿现役主力设备折腾开发者预览版,风险确实存在,升级前务必备份数据,并且确认你的设备在官方支持列表里。做开发的人可以准备一台专门的测试设备,不要拿工作主力机去刷,这是我从嵌入式开发那边习得的教训:开发环境归开发环境,生产环境归生产环境,混在一起迟早出事。

6. 从这往后怎么走:一些来自实跑过程的建议

如果你完整跟着上面的路子走完一遍,那你已经具备了一个鸿蒙开发者的基本盘。但离真正能在团队里独当一面,还有几个方向值得继续深挖。

第一个方向是 元服务和卡片开发 。这算鸿蒙生态里最“原汁原味”的能力,Android和iOS都没有对等的开发范式。卡片不只是桌面小组件,还能嵌入到负一屏、锁屏、甚至系统搜索里,很适合工具类App做用户触达。

第二个方向是 分布式能力 。鸿蒙的多设备协同是其他平台没有的核心差异化能力,跨设备流转、分布式数据管理,这些在智能家居、车机、办公场景都有应用空间。我下一步计划就是给这个事项应用加上跨设备同步能力,让手机上的待办在PC上也能实时看到。

第三个方向是 AI应用开发 。热搜里也能看到智能体开发、大模型微调之类的词越来越热,鸿蒙生态现在也提供了端侧AI能力和大模型接入接口,比如可以把意图识别、语音转文字这类能力接进应用。这个方向建议先了解系统AI接口,再逐步接入自己的业务场景。

最后一个建议,也是我每次带新人都会强调的: 开发联调时,手边一定要有真机和模拟器两个环境 。Previewer看布局效率高,模拟器跑完整流程,真机最后验收所有细节。不要图省事只在预览器里改改就交付,真机上的字体渲染、性能表现和输入体验,跟模拟器还是有差别的。

我做这个项目最深的一个体会是,鸿蒙开发其实已经相当成熟了,问题在于国内的中文教程和社区积累还没有Android那么庞大,很多问题要靠翻官方文档和调试源码自己解决。但也正因为这样,现在入局的人能吃到技术红利——会鸿蒙的人越来越多,但真正能把APP和PC形态都做好的人,在各行各业里都还是稀缺资源。你如果能把这篇里的每一步都亲手做一遍,就已经超过了绝大多数只停留在“看教程”阶段的人了。

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐