前言
最近学习了一下HarmonyOS 第一课的基础课程,此篇以记录一些课程笔记。
系统环境:MacOS 14.6.1(Apple M2 Pro)
核心理念
- 一次开发,多端部署:应用一次开发就能在多个设备上运行,软件实体能够从单一设备转移到其他设备上,降低针对不同设备和操作系统的重复开发和多套维护的成本
- 可分可合,自由流转:软件实体在多个设备之间能够协同运行,给消费者提供全新的分布式体验;提供轻量化的服务,最小化资源消耗,一步直达,快速完成消费者特定场景的任务
- 统一生态,原生智能:为消费者提供智慧场景服务,实现“服务找人”;提供软硬芯协同优化的原生AI能力,全面满足应用高性能诉求
DevEco Studio的使用
DevEco Studio 是HarmonyOS开发的集成开发环境(IDE),可以去官方下载页面去下载最新版本。安装完成之后,可以对IDE进行诊断和汉化。
诊断IDE
诊断主要是识别开发环境是否完备。操作路径:Help > Diagnostic Tools > Diagnose Development Environment。
汉化
IDE默认的语种是英语,但自带了汉化包。操作路径:DevEco Studio > Preferences > Plugins,选择Installed标签,搜索「chinese」,然后点击右侧的「Enable」启用插件,重启IDE即可。
Windows 系统为:File > Settings > Plugins
本地调试
DevEco Studio 支持预览器、模拟器、真机三种本地调试方式,预览器同时支持手机、平板、可折叠设备等多种设备进行本地调试,操作路径:在工程的右侧点击 Previewer > 点击2所指的图标 > 打开Multi-profile preview,在下面能看到对应的设备列表:
但需要注意的是,预览器有一部分能力并不支持:
- 不支持运行Ability生命周期
- 不支持引用HSP(Harmony Shared Package),引用了HSP的模块不支持预览
- 不支持通过相对路径及绝对路径的方式访问resources目录下的文件
- 不支持Richtext、Web、Video、XComponent等组件,也不支持组件拖拽
- 不支持调用C++库的预览
- 不支持EventHub等方式进行数据同步
若工程包含有预览器不支持的能力时,建议用模拟器或真机进行调试。
模拟器所支持的能力可见:模拟器和真机调试
ArkTS & ArkUI
目前流行的编程语言TypeScript是在JavaScript基础上通过添加类型定义扩展而来的,而ArkTS在继承TypeScript语法的基础上进行进一步扩展和优化,以提供更高的性能和开发效率。所以,ArkTS的语法和TypeScript的语法非常类似,这对于前端开发者而言就非常友好了。
const t:string = 'aaa'
let b = 2
const c: number[] = [1,2,3]
...
enum ENUM {
ONE = 1
}
class A {
name: string = ''
getName(): string {
return this.name
}
}
interface B {}
ArkTS要求所有字段在声明时或者构造函数中显式初始化,这和TS中的strictPropertyInitialization模式一样。
ArkTS以声明方式组合和扩展组件来描述应用程序的UI,而ArkUI不仅提供基础的系统布局组件和应用组件,例如Text
、Image
、Row
、Column
等,同时提供了与组件对应的属性、事件和子组件配置方法。看一个简单示例:
...
@Entry
@Component
struct Index {
@State message: string = 'Hello World';
build() {
Column(){
// 以下都是子组件
// 文本组件
Text(this.message)
.fontSize(30)
.fontWeight(FontWeight.Bold)
// 图片组件
Image(src).width(100)
// 按钮组件
Button() {
Text('这是按钮') // 子组件
}
.type(ButtonType.Capsule)
.margin({ top: 40 })
.onClick(() => { console.log('按钮事件') })
}
.height('100%')
.width('100%')
}
}
从上述示例可以看出,对组件属性的配置和CSS的语法是非常像的:
- @Entry:
@Entry
装饰的自定义组件将作为UI页面的入口组件,即页面的根节点。一个页面有且仅能有一个@Entry
装饰的组件,这类组件也叫页面组件
- @Component:
@Component
装饰器仅能装饰struct
关键字声明的数据结构。struct
被@Component
装饰后具备组件化的能力,需要实现build
方法描述UI。一个struct
只能被一个@Component
装饰,被@Component
装饰的组件也叫自定义组件
- build()函数:
build()
函数用于定义自定义组件的声明式UI描述(作用类似于Vue
/React
等前端框架的render
函数),这是组件必须要声明的函数
组件的生命周期
和Vue
/React
等前端框架的组件一样,ArkUI组件也有自身的生命周期。被@Entry
装饰的页面组件生命周期如下图所示:
页面组件
具备以下生命周期接口:
onPageShow
:页面每次显示时触发一次,包括路由过程、应用进入前台等场景onPageHide
:页面每次隐藏时触发一次,包括路由过程、应用进入后台等场景onBackPress
:当用户点击返回按钮时触发
自定义组件
具备以下生命周期接口:
aboutToAppear
:组件即将出现时回调该接口,具体时机为在创建自定义组件的新实例后,在执行其build()
函数之前执行。onDidBuild
:组件build()
函数执行完成之后回调该接口。aboutToDisappear
:在自定义组件被销毁之前执行。组件的销毁是从组件树上直接摘下子树,对于嵌套组件而言,会先调用父组件的aboutToDisappear
,再调用子组件的aboutToDisappear
。
ArkUI组件的生命周期接口也支持自定义,具体示例可见声明式UI-页面和自定义组件-自定义组件监听页面生命周期
ForEach循环
循环渲染一组数据是业务开发中常见的场景,前端框架都提供了相关的指令或语法,ForEach
是ArkTS提供的基于数组类型数据来进行循环渲染的接口。看一个简单示例:
@Entry
@Component
struct Index{
arr: string[] = ['one', 'two', 'three']
build(){
Column(){
ForEach(this.arr, (item: string) => {
Text(item).margin({ bottom: 20 })
})
}
}
}
这样就可以把一组数据渲染到界面上:
ForEach
需要与容器组件配合使用,且接口返回的组件应当是允许包含在ForEach父容器组件中的子组件。例如,ListItem组件要求ForEach的父容器组件必须为List组件。它的语法如下:
ForEach(
Array<Object>, // 数据源
itemGenerator: (item: Object, index: number) => void, // 组件生成函数
keyGenerator?: (item: Object, index: number) => string // 键值生成函数
)
前两个参数是必须的,第三个keyGenerator
参数是可选的,这个是用于给组件生成键值的,会和生成的组件绑定。在React/Vue
等前端框架中,进行循环渲染时都会建议给组件设定key
值,以便于复用组件,提升渲染性能,ArkTS也是如此,但又存在不同。举个例子:
// 数据源
arr = ['one', 'two', 'three', 'two']
// vue
<div v-for="item in arr" :key="item" style="margin-top: 20px">
<div>{{ item }}</div>
</div>
// ArkUI
ForEach(this.arr, (item: string) => {
Text(item).margin({ bottom: 20 })
}, (item: string) => item)
同样是以arr
作为数据源,以arr
的数组项作为组件的key
值,二者渲染的效果却不同:
从上图可以看出,Vue
的渲染结果是符合预期的,ArkTS渲染的列表少了数据源的最后一项。这是因为ForEach
遍历数据源时将item
作为键值,遍历到索引为1的two时,生成键值为two的组件并进行标记;当遍历到索引为3的two时,当前项的键值也为two,此时不再创建新的组件。倘若组件生成的键值是相同的,那么ArkUI框架的行为也是未定义的,就会出现非预期渲染。而对于Vue
等前端框架,即使键值相同,依然会创建组件,只是会出现一个「键值重复」的警告。
ForEach
的第三个参数是可选的,若未提供则会使用默认的键值生成函数:
(item: Object, index: number) => { return index + '__' + JSON.stringify(item); }
ArkUI框架对于ForEach
的键值生成有一套特定的判断规则,这主要与itemGenerator
函数的第二个参数index
以及keyGenerator
函数的第二个参数index
有关,具体的键值生成规则判断逻辑如下图所示:
build()函数
上文说过,build()
函数的作用类似于Vue
/React
等前端框架的render
函数,负责组件的渲染。无论是页面组件还是自定义组件,都必须显示声明该函数。所有声明在build()
函数内的语句,统称为UI描述。UI描述需要遵循以下规则:
@Entry
装饰的自定义组件,其build()
函数下的根节点唯一且必要,且必须为容器组件,其中ForEach禁止作为根节点@Component
装饰的自定义组件,其build()
函数下的根节点唯一且必要,可以为非容器组件,其中ForEach禁止作为根节点- 不允许在该函数体内声明本地变量、创建本地作用域、使用
console
语句、使用switch
语句、使用表达式以及直接改变状态变量。
build(){
let a:number = 1 // 编译错误
console.log('aaa') // 编译错误
{
//创建本地作用域,编译错误
}
// 不允许,用if语句代替
switch(exp) {}
// 不允许使用表达式,用if语句代替
(this.aVar > 10) ? Text('...') : Image('...')
// 不允许改变状态变量
this.a = 1
}
- 不允许调用没有用
@Builder
装饰的方法,但允许系统组件的参数是TS方法的返回值。
@Component
struct ParentComponent {
doSomeCalculations() {
}
calcTextValue(): string {
return 'Hello World';
}
@Builder
doSomeRender() {
Text(`Hello World`)
}
build() {
Column() {
// 反例:不能调用没有用@Builder装饰的方法
this.doSomeCalculations();
// 正例:可以调用
this.doSomeRender();
// 正例:参数可以为调用TS方法的返回值
Text(this.calcTextValue())
}
}
}
UIAbility组件
UIAbility组件是一种包含UI的应用组件,是系统调度的基本单元,主要用于和用户交互,其主要特点有两个:
- 原生支持应用组件级的跨端迁移和多端协同
- 支持多设备和多窗口形态
一个应用可以包含一个或多个UIAbility组件,而每个UIAbility组件实例都会在最近任务列表中显示一个对应的任务。对于开发者而言,可以根据具体场景选择单个还是多个UIAbility,划分建议如下:
- 如果开发者希望在任务视图中看到一个任务,建议使用“一个UIAbility+多个页面”的方式,可以避免不必要的资源加载
- 如果开发者希望在任务视图中看到多个任务,或者需要同时开启多个窗口,建议使用多个UIAbility实现不同的功能
生命周期
UIAbility的生命周期包括Create
、Foreground
、Background
、Destroy
四个状态:
- Create状态:UIAbility实例创建完成时触发,系统会调用
onCreate()
回调,可以在该回调中进行页面初始化操作,例如变量定义资源加载等,用于后续的UI展示 - Foreground状态:UIAbility切换至前台时触发
onForeground
回调,可以在该回调中申请系统需要的资源,或者重新申请在onBackground()
中释放的资源 - Background状态:UIAbility切换至后台时触发
onBackground
回调,可以在该回调中释放UI不可见时无用的资源,或者在此回调中执行较为耗时的操作,例如状态保存等 - Destroy状态:UIAbility实例销毁时触发
onDestroy
回调,可以在该回调中进行系统资源的释放、数据的保存等操作
完整的周期示意图如下:
启动模式
UIAbility的启动模式是指UIAbility实例在启动时的不同呈现状态。针对不同的业务场景,系统提供了三种启动模式:
- singleton(单实例模式):默认的启动模式,每次调用
startAbility()
方法时,如果应用进程中该类型的UIAbility实例已经存在,则复用系统中的UIAbility实例。系统中只存在唯一一个该UIAbility实例,即在最近任务列表中只存在一个该类型的UIAbility实例
singleton启动模式时,再次调用startAbility()
方法启动该UIAbility实例时,只会进入该UIAbility的onNewWant()
回调,不会进入其onCreate()
和onWindowStageCreate()
生命周期回调。应用可以在该回调中更新要加载的资源和数据等,用于后续的UI展示。
- multiton(多实例模式):多实例模式下,每次调用
startAbility()
方法时,都会在应用进程中创建一个新的该类型UIAbility实例。即在最近任务列表中可以看到有多个该类型的UIAbility实例 - specified(指定实例模式):指定实例模式下,每次调用
startAbility()
方法时,会先进入对应的AbilityStage
的onAcceptWant()
生命周期回调中,以获取该UIAbility实例的Key
值。然后系统会自动匹配,如果存在与该UIAbility实例匹配的Key,则会启动与之绑定的UIAbility实例,并进入该UIAbility实例的onNewWant()
回调函数;否则会创建一个新的UIAbility实例,并进入该UIAbility实例的onCreate()
回调函数和onWindowStageCreate()
回调函数。
指定实例模式针对一些特殊场景使用,例如文档编辑,重复打开已保存的文档都希望是打开同一个UIAbility实例
应用程序
用户应用程序泛指运行在设备的操作系统之上,为用户提供特定服务的程序,简称“应用”。一个应用所对应的软件包文件,称为“应用程序包”。HarmonyOS为应用的开发提供了多Module设计机制,其特点是:
- 支持模块化开发:一个应用通常会包含多种功能,将不同的功能特性按模块来划分和管理是一种良好的设计方式。在开发过程中,我们可以将每个功能模块作为一个独立的Module进行开发,Module中可以包含源代码、资源文件、第三方库、配置文件等,每一个Module可以独立编译,实现特定的功能。这种模块化、松耦合的应用管理方式有助于应用的开发、维护与扩展
- 支持多设备适配: 一个应用往往需要适配多种设备类型,在采用多Module设计的应用中,每个Module都会标注所支持的设备类型。有些Module支持全部类型的设备,有些Module只支持某一种或几种类型的设备(比如平板),那么在应用市场分发应用包时,也能够根据设备类型做精准的筛选和匹配,从而将不同的包合理的组合和部署到对应的设备上
Module类型
Module按照使用场景可以分为两种类型:
Ability类型:用于实现应用的功能和特性。每一个Ability类型的Module编译后,会生成一个以
.hap
为后缀的文件,称为HAP(Harmony Ability Package)包。HAP包可以独立安装和运行,是应用安装的基本单位,一个应用中可以包含一个或多个HAP包,具体包含如下两种类型:- entry类型:应用的主模块,包含应用的入口界面、入口图标和主功能特性,编译后生成
entry类型
的HAP。每一个应用分发到同一类型的设备上的应用程序包,只能包含唯一一个entry类型的HAP - feature类型:应用的动态特性模块,编译后生成
feature类型
的HAP。一个应用中可以包含一个或多个feature类型的HAP,也可以不包含
- entry类型:应用的主模块,包含应用的入口界面、入口图标和主功能特性,编译后生成
Library类型:用于实现代码和资源的共享。Library类型的Module分为
Static
和Shared
两种类型,编译后会生成共享包:- Static Library:静态共享库。编译后会生成一个以
.har
为后缀的文件,即静态共享包HAR(Harmony Archive)
- Shared Library:动态共享库。编译后会生成一个以
.hsp
为后缀的文件,即动态共享包HSP(Harmony Shared Package)
- Static Library:静态共享库。编译后会生成一个以
对于同一个Library类型的Module,可以被其他的Module多次引用,二者的编译和运行差异如下:
从上图可以看出,HAR中的代码和资源跟随使用方编译,如果有多个使用方,它们的编译产物中会存在多份相同拷贝;而HSP中的代码和资源可以独立编译,运行时在一个进程中代码也只会存在一份。
应用模型
HarmonyOS支持两种应用模型:FA(Feature Ability)模型和Stage模型。目前主推且会长期演进的模型是Stage模型
。
两种模型的差异可见:应用模型概况
在DevEco Studio上创建的工程也默认采用Stage模型
,其工程结构示意图如下:
- AppScope目录由DevEco Studio自动生成,不可更改
- Module目录名可以由DevEco Studio自动生成(比如entry、library等),也可以自定义。上图中的Module目录名为
entry
,也是一个entry类型
的Module
注意:Module目录名可以自定义,Module的类型是配置文件module.json5
中的type
字段指定的,跟目录名无关。type
可选值为:entry
、feature
、har
和shared
,分别对应上文的Module类型。
配置文件:
- AppScope > app.json5:app.json5配置文件用于声明应用的全局配置信息,比如应用Bundle名称、应用名称、应用图标、应用版本号等
- entry > src > main > module.json5:module.json5配置文件,用于声明Module基本信息、支持的设备类型、所含的组件信息、运行所需申请的权限等
资源文件:
- AppScope > resources:用于存放应用需要用到的资源文件,如图形、多媒体、字符串、布局文件等
- entry > src > main > resources:用于存放该Module需要用到的资源文件,同上
entry:
- src > main > ets:用于存放Module的ArkTS源码文件(.ets文件)
- src > main > ets > pages:应用/服务包含的页面
- build-profile.json5:当前的模块信息、编译信息配置项
- hvigorfile.ts:模块级编译构建任务脚本
- obfuscation-rules.txt:混淆规则文件。混淆开启后,在使用Release模式进行编译时,会对代码进行编译、混淆及压缩处理
- oh-package.json5:用来描述包名、版本、入口文件(类型声明文件)和依赖项等信息,包括所依赖的三方库和共享包
- oh_modules:用于存放三方库依赖信息
- build-profile.json5:工程级配置信息,包括签名signingConfigs、产品配置products等。其中products中可配置当前运行环境,默认为HarmonyOS
- hvigorfile.ts:工程级编译构建任务脚本
- oh-package.json5:主要用来描述全局配置,如:依赖覆盖(overrides)、依赖关系重写(overrideDependencyMap)和参数化配置(parameterFile)等
如上文所述,一个应用可以包含多个Module,不同类型的Module编译后会生成对应的HAP、HAR、HSP等文件,开发态视图与编译态视图的对照关系如下:
(全文完)
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。