头图

前言

最近学习了一下HarmonyOS 第一课的基础课程,此篇以记录一些课程笔记。

系统环境:MacOS 14.6.1(Apple M2 Pro)

核心理念

  • 一次开发,多端部署:应用一次开发就能在多个设备上运行,软件实体能够从单一设备转移到其他设备上,降低针对不同设备和操作系统的重复开发和多套维护的成本
  • 可分可合,自由流转:软件实体在多个设备之间能够协同运行,给消费者提供全新的分布式体验;提供轻量化的服务,最小化资源消耗,一步直达,快速完成消费者特定场景的任务
  • 统一生态,原生智能:为消费者提供智慧场景服务,实现“服务找人”;提供软硬芯协同优化的原生AI能力,全面满足应用高性能诉求

DevEco Studio的使用

DevEco Studio 是HarmonyOS开发的集成开发环境(IDE),可以去官方下载页面去下载最新版本。安装完成之后,可以对IDE进行诊断和汉化。

诊断IDE

诊断主要是识别开发环境是否完备。操作路径:Help > Diagnostic Tools > Diagnose Development Environment。

Diagnostic.png

汉化

IDE默认的语种是英语,但自带了汉化包。操作路径:DevEco Studio > Preferences > Plugins,选择Installed标签,搜索「chinese」,然后点击右侧的「Enable」启用插件,重启IDE即可。

Windows 系统为:File > Settings > Plugins

image.png

本地调试

DevEco Studio 支持预览器、模拟器、真机三种本地调试方式,预览器同时支持手机、平板、可折叠设备等多种设备进行本地调试,操作路径:在工程的右侧点击 Previewer > 点击2所指的图标 > 打开Multi-profile preview,在下面能看到对应的设备列表:

image.png

但需要注意的是,预览器有一部分能力并不支持:

  • 不支持运行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不仅提供基础的系统布局组件和应用组件,例如TextImageRowColumn等,同时提供了与组件对应的属性、事件和子组件配置方法。看一个简单示例:

...
@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装饰的页面组件生命周期如下图所示:

life-circle

页面组件具备以下生命周期接口:

  • 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 })
      })
    }
  }
}

这样就可以把一组数据渲染到界面上:

image.png

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值,二者渲染的效果却不同:

difference.png

从上图可以看出,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有关,具体的键值生成规则判断逻辑如下图所示:

keyGenerator.png

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的生命周期包括CreateForegroundBackgroundDestroy四个状态:

  • Create状态:UIAbility实例创建完成时触发,系统会调用onCreate()回调,可以在该回调中进行页面初始化操作,例如变量定义资源加载等,用于后续的UI展示
  • Foreground状态:UIAbility切换至前台时触发onForeground回调,可以在该回调中申请系统需要的资源,或者重新申请在onBackground()中释放的资源
  • Background状态:UIAbility切换至后台时触发onBackground回调,可以在该回调中释放UI不可见时无用的资源,或者在此回调中执行较为耗时的操作,例如状态保存等
  • Destroy状态:UIAbility实例销毁时触发onDestroy回调,可以在该回调中进行系统资源的释放、数据的保存等操作

完整的周期示意图如下:

life-circle.png

启动模式

UIAbility的启动模式是指UIAbility实例在启动时的不同呈现状态。针对不同的业务场景,系统提供了三种启动模式:

  • singleton(单实例模式):默认的启动模式,每次调用startAbility()方法时,如果应用进程中该类型的UIAbility实例已经存在,则复用系统中的UIAbility实例。系统中只存在唯一一个该UIAbility实例,即在最近任务列表中只存在一个该类型的UIAbility实例
singleton启动模式时,再次调用startAbility()方法启动该UIAbility实例时,只会进入该UIAbility的onNewWant()回调,不会进入其onCreate()onWindowStageCreate()生命周期回调。应用可以在该回调中更新要加载的资源和数据等,用于后续的UI展示。
  • multiton(多实例模式):多实例模式下,每次调用startAbility()方法时,都会在应用进程中创建一个新的该类型UIAbility实例。即在最近任务列表中可以看到有多个该类型的UIAbility实例
  • specified(指定实例模式):指定实例模式下,每次调用startAbility()方法时,会先进入对应的AbilityStageonAcceptWant()生命周期回调中,以获取该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,也可以不包含
  • Library类型:用于实现代码和资源的共享。Library类型的Module分为StaticShared两种类型,编译后会生成共享包:

    • Static Library:静态共享库。编译后会生成一个以.har为后缀的文件,即静态共享包HAR(Harmony Archive)
    • Shared Library:动态共享库。编译后会生成一个以.hsp为后缀的文件,即动态共享包HSP(Harmony Shared Package)

对于同一个Library类型的Module,可以被其他的Module多次引用,二者的编译和运行差异如下:

image.png

从上图可以看出,HAR中的代码和资源跟随使用方编译,如果有多个使用方,它们的编译产物中会存在多份相同拷贝;而HSP中的代码和资源可以独立编译,运行时在一个进程中代码也只会存在一份。

应用模型

HarmonyOS支持两种应用模型:FA(Feature Ability)模型和Stage模型。目前主推且会长期演进的模型是Stage模型

两种模型的差异可见:应用模型概况

在DevEco Studio上创建的工程也默认采用Stage模型,其工程结构示意图如下:

工程结构示意图.png

  • AppScope目录由DevEco Studio自动生成,不可更改
  • Module目录名可以由DevEco Studio自动生成(比如entry、library等),也可以自定义。上图中的Module目录名为entry,也是一个entry类型的Module
注意:Module目录名可以自定义,Module的类型是配置文件module.json5中的type字段指定的,跟目录名无关。type可选值为:entryfeatureharshared,分别对应上文的Module类型。
  • 配置文件:

    • AppScope > app.json5app.json5配置文件用于声明应用的全局配置信息,比如应用Bundle名称、应用名称、应用图标、应用版本号等
    • entry > src > main > module.json5module.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等文件,开发态视图与编译态视图的对照关系如下:

image.png

(全文完)


我是星礼
12.7k 声望1.1k 粉丝

Front-end Dev Handbook: 前端开发者手册