Stage应用模型
应用模型是HarmonyOS为开发者提供的应用程序所需能力的抽象提炼,它提供了应用程序必备的组件和运行机制。有了应用模型,开发者可以基于一套统一的模型进行应用开发,使应用开发更简单、高效。
HarmonyOS应用模型概况
随着系统的演进发展,HarmonyOS先后提供了两种应用模型:
- FA(Feature Ability)模型:HarmonyOS早期版本开始支持的模型,已经不再主推。
- Stage模型:HarmonyOS 3.1 Developer Preview版本开始新增的模型,是目前主推且会长期演进的模型 。
在该模型中,由于提供了AbilityStage、WindowStage等类作为应用组件和Window窗口的"舞台",因此称这种应用模型为Stage模型。
developer.huawei.com/consumer/cn...
Stage模型基本概念
Ability Module 与 Library Module
我们新建的项目内部,默认包含一个entry子模块,entry模块内部包含有源代码、资源、配置文件等等。
entry模块这种包含应用内特殊能力的,我们称之为为Ability Module,即有特殊能力的模块。
在DevEco Studio中新建Module时,也内置了一些Module:比如网格、列表、登录、开屏页等
除了有特殊能力的Module之外;在多个Module之间,它们存在使用公共类、函数等资源的情况,那么就需要另外一种类型的Module来存放公共使用的资源、函数等,所以具有这种公共能力的模块,称之为library Module。
编译期
- 每个ability Module都支持单独编译,并且在编译时,会被编译成HAP(Harmony Ability Package)文件。
- 而library Module会被编译成HSP(Harmony Shared Package)文件,即共享包。
- 每个HAP包在运行过程中都可以引用和依赖多个HSP包。
- 一个应用中往往会由多个Module构成,所以会有多个HAP文件;而一个应用只能指定一个HAP文件作为程序的入口,所以作为入口的HAP就是应用主模块,我们称为Entry类型的HAP;其他则是Feature类型的HAP。
- HAP有很多文件,最终还是要合并到一起组成APP的,所以多个HAP合并在一起后我们称之为bundle,bundle有自己的名字属性 name,这个name会作为bundle的唯一标识,最终生成一个APP安装包。
- 在我们的应用开发调试时,我们可以只编译一个Enrty类型HAP作为主入口,如果要调试其他Feature类型的HAP,那么则单独添加该HAP进行编译调试,这样调试效率也会更高。
运行期
- 每个HAP都可以独立运行,为了展示页面,所以它们会各自创建一个名为 AbilityStage 的类实例,即"应用组件的舞台"。
-
- 每个Entry类型或者Feature类型的HAP在运行期都有一个AbilityStage类实例,当HAP中的代码首次被加载到进程中的时候,系统会先创建AbilityStage实例。
- Stage模型提供UIAbility和ExtensionAbility两种类型的组件。
- UIAbility:包含UI界面的应用组件,是系统调度的基本单元。
- ExtensionAbility:比如应用卡片、输入法实现,都是基于ExtensionAbility实现。
- 我们常规应用页面最终都是通过UIAbility组件来展示的,但并不是直接通过UIAbility来展示的。
-
- UIAbility持有一个WindowStage实例,即组件内窗口的"舞台"。
- 每个UIAbility类实例都会与一个WindowStage类实例绑定。
- 而WindowStage持有Window对象"窗口"。
- 而Windows则是用来绘制UI界面的窗口,搭载ArkUI-Page。
总结
- 项目中大致分为两种类型Module,分别是 Ability Module 和 Library Module。
- 在编译期,每个Ability Module都会各自生成一个HAP,每个 Library Module 都会各自生成一个HSP。
-
- HAP在运行时可以引用和依赖多个HSP。
- 一个应用程序中,只存在一个主入口HAP,称为Entry类型的HAP,其他称为Feature类型的HAP。
- 在运行期,每个HAP都会各自创建一个 AbilityStage 实例;
-
- Stage模型提供了UIAbility和ExtensionAbility两种类型;
- Stage模型在对跨端的UI兼容更加优秀。
- 正是因为提供了AbilityStage、WindowStage等类作为应用组件和Window窗口的"舞台",所以这个应用模型称为Stage模型。
- 通过 Stage舞台 的机制,使得Ability 和 Window窗口 分隔开了,实现了两者的解耦;所以在将来开发跨设备的软件时,就可以针对不同设备来对窗口做单独的裁剪等设置,更好的实现跨端适配。
UIAbility概述
UIAbility组件是一种包含UI界面的应用组件,主要用于和用户交互。
UIAbility组件是系统调度的基本单元,为应用提供绘制界面的窗口;一个UIAbility组件中可以通过多个页面来实现一个功能模块。每一个UIAbility组件实例,都对应于一个最近任务列表中的任务。
UIAbility的声明配置
为使应用能够正常使用UIAbility,需要在module.json5配置文件的abilities标签中声明UIAbility的名称、入口、标签等相关信息。
kotlin
{
"module": {
// ...
"abilities": [
{
"name": "EntryAbility", // UIAbility组件的名称
"srcEntry": "./ets/entryability/EntryAbility.ts", // UIAbility组件的代码路径
"description": "$string:EntryAbility_desc", // UIAbility组件的描述信息
"icon": "$media:icon", // UIAbility组件的图标
"label": "$string:EntryAbility_label", // UIAbility组件的标签
"startWindowIcon": "$media:icon", // UIAbility组件启动页面图标资源文件的索引
"startWindowBackground": "$color:start_window_background", // UIAbility组件启动页面背景颜色资源文件的索引
// ...
}
]
}
}
UIAbility组件生命周期
当用户打开、切换和返回到对应应用时,应用中的UIAbility实例会在其生命周期的不同状态之间转换。UIAbility类提供了一系列回调,通过这些回调可以知道当前UIAbility实例的某个状态发生改变,会经过UIAbility实例的创建和销毁,或者UIAbility实例发生了前后台的状态切换。
UIAbility的生命周期包括Create、Foreground、Background、Destroy四个状态,如下图所示。
OnCreate
Create状态为在应用加载过程中,UIAbility实例创建完成时触发,系统会调用onCreate()回调。以在该回调中进行页面初始化操作,例如变量定义资源加载等,用于后续的UI界面展示。
scala
import UIAbility from '@ohos.app.ability.UIAbility';
import Window from '@ohos.window';
export default class EntryAbility extends UIAbility {
onCreate(want, launchParam) {
// 页面初始化
}
// ...
}
WindowStageCreate 和 WindowStageDestroy
UIAbility实例创建完成之后,在进入Foreground之前,系统会创建一个WindowStage。
WindowStage创建完成后会进入onWindowStageCreate()回调,我们可以在该回调中设置UI界面加载、设置WindowStage的事件订阅。
UI界面加载 与 WindowStage事件订阅
我们可以在onWindowStageCreate()回调中通过loadContent()方法设置应用要加载的页面并根据需要订阅WindowStage的事件(获焦/失焦、可见/不可见)。
javascript
import UIAbility from '@ohos.app.ability.UIAbility';
import Window from '@ohos.window';
export default class EntryAbility extends UIAbility {
onWindowStageCreate(windowStage: Window.WindowStage) {
// 设置WindowStage的事件订阅(获焦/失焦、可见/不可见)
windowStage.on(
'windowStageEvent',//监听事件,固定为'windowStageEvent',即WindowStage生命周期变化事件。
(windowStageEvent)//回调函数。返回当前的WindowStage生命周期状态。
=>{
switch (windowStageEvent) {
case window.WindowStageEventType.SHOWN:
//切换到前台
break
case window.WindowStageEventType.ACTIVE:
//获取到焦点
break
case window.WindowStageEventType.INACTIVE:
//失去焦点
break
case window.WindowStageEventType.HIDDEN:
//切到后台
break
}
})
// 设置UI界面加载,这里加载的是'pages/Index'文件、
windowStage.loadContent('pages/Index', (err, data) => {
// ...
});
}
}
对应于onWindowStageCreate()回调。在UIAbility实例销毁之前,则会先进入onWindowStageDestroy()回调,可以在该回调中释放UI界面资源。例如在onWindowStageDestroy()中注销获焦/失焦等WindowStage事件。
scala
import UIAbility from '@ohos.app.ability.UIAbility';
import Window from '@ohos.window';
export default class EntryAbility extends UIAbility {
// ...
onWindowStageDestroy() {
// 释放UI界面资源
}
}
Foreground与Background状态
- Foreground和Background状态分别在UIAbility实例切换至前台和切换至后台时触发,对应于onForeground()回调和onBackground()回调。
- onForeground()回调,在UIAbility的UI界面可见之前,如UIAbility切换至前台时触发。可以在onForeground()回调中申请系统需要的资源,或者重新申请在onBackground()中释放的资源。
- onBackground()回调,在UIAbility的UI界面完全不可见之后,如UIAbility切换至后台时候触发。可以在onBackground()回调中释放UI界面不可见时无用的资源,或者在此回调中执行较为耗时的操作,例如状态保存等。
- 例如应用在使用过程中需要使用用户定位时,假设应用已获得用户的定位权限授权。在UI界面显示之前,可以在onForeground()回调中开启定位功能,从而获取到当前的位置信息。
- 当应用切换到后台状态,可以在onBackground()回调中停止定位功能,以节省系统的资源消耗。
scala
import UIAbility from '@ohos.app.ability.UIAbility';
export default class EntryAbility extends UIAbility {
onForeground() {
// 申请系统需要的资源,或者重新申请在onBackground中释放的资源
}
onBackground() {
// 释放UI界面不可见时无用的资源,或者在此回调中执行较为耗时的操作
// 例如状态保存等
}
}
Destroy
Destroy状态在UIAbility实例销毁时触发。可以在onDestroy()回调中进行系统资源的释放、数据的保存等操作。
例如调用terminateSelf()方法停止当前UIAbility实例,从而完成UIAbility实例的销毁;或者用户使用最近任务列表关闭该UIAbility实例,完成UIAbility的销毁。
scala
import UIAbility from '@ohos.app.ability.UIAbility';
import Window from '@ohos.window';
export default class EntryAbility extends UIAbility {
onDestroy() {
// 系统资源的释放、数据的保存等
}
}
应用周期流程
- UIAbility实例创建完成后,会调用onCreate。
- onCreate之后,UIAbility需要先去创建WindowStage才能得到WIndow来展示UI;
- 当WindowStage完成实例创建后会调用onWindowStageCreate。
- WindowsStage创建完成后,接下来UIAbility会被移到前台,即 onForeground。
- 之后由WindowStage控制Window实例的可见性,使页面可见,即Visible。
- 接着是走Active,表示页面得到焦点。
- 之后如果将应用关闭,WindowStage会先将页面失去焦点,走InActive。
- 接着使页面不可见,即 InVisible;
- UIAbility将自身至于后台,调用onBackground;
- UIAbility先销毁WindowStage实例,触发onWindowStageDestroy。
- 最后Ability销毁自己,走onDestroy。
hilog
我们在刚刚看的Ability中,发现鸿蒙的log日志使用不是常见的console.log,而是使用的hilog。
hilog是鸿蒙提供的日志打印工具,他支持不同类型的log:error、info、debug等等。
它的第一个参数是数字,表示一个域,我们可以通过这个域的值来筛选日志。
第二个参数是字符串,它是TAG,也是标记,和第一个参数有些类似。
第三个参数是日志输出内容:%s 是一个占位符,表示输出一个占位符,在后面的参数值进行补充,这个和c很像。
而%s中间的 {public} 用来修饰当前占位符对应的参数是否要公开;private则是不公开,当正式环境下,输出的日志中对应参数会不公开。
在我们debug环境中,{private}无效,日志参数始终会显示。54
页面与组件生命周期
页面与其子组件的销毁关系
下图中,我们定义了一个@Enrty入口页面组件PageA 和 普通组件ComponentA。
我们在PageA的build函数中创建了ComponentA;那么ComponentA组件会先被创建实例,之后是执行Build函数,最终被渲染到页面中。
- 当我们页面执行跳转其他页面时,有两种方式,一种是pushUrl(),此时当前页面不会出栈,所以 ComponentA 不一定会被销毁。
- 如果使用replaceUrl(),那么当前页面会被目标页面替换,当前页面会出栈,所以自然页面内部的组件就被销毁了。
- 同时,我们还创建了 @State show变量,用来控制ComponentA的创建和移除。当show为true时,ComponentA被创建;反之,ComponentA实例会被销毁回收;所以,即使页面没有被销毁,但其内部的组件仍有可能被销毁。
综上所述:页面中子组件是否销毁 并不完全取决于 页面是否销毁。当页面销毁时,其内部子组件必然被销毁;当页面未被销毁时,也有可能因为代码对视图控制而导致子组件被销毁。
组件提供的钩子函数
同时,组件还提供了几个钩子函数。
- aboutToAppear:在组件实例被创建后,调用Build函数之前,会先执行当前aboutToAppear函数,我们可以在此进行一些数据的初始化。
- onPageShow:页面组件函数,表示页面被展示。
- onBackPress:页面组件函数,表示当前页按下了返回键。
- onPageHide:页面组件函数,表示当前页面被隐藏。
- aboutToDisappear:在组件销毁之前(onPageHide之后),会执行此aboutToDisappear函数,我们可以在此时对数据进行保存,做持久化操作。
注意: 页面生命周期函数只能在作为 @Entry 入口组件的组件内才会有效; 所以普通组件的数据预处理尽可能放在aboutToAppear中执行。
页面组件跳转时的生命周期流程
- 页面组件A跳转页面组件B时:
-
- 首先组件B先被创建,触发组件B的aboutToAppear函数。
- 第二步隐藏组件A,执行了组件A的onPageHide函数。
- 第三步组件B显示出来,执行了组件B的onPageShow函数。
- 第四步,组件B内部的组件被创建,触发aboutToAppear函数后,执行Build函数。
FOREACH函数对组件生命周期的影响 (API 9 以上没有这个问题)
在FOREACH函数中,如果通过FOREACH函数来生成多个组件;那么当@State 数组元素被修改删除后,会重新触发FOREACH函数执行;而之前通过FOREACH生成的所有组件都会被回收销毁,从而执行aboutToAppear;接着重新执行FOREACH生成新的组件,并执行aboutToAppear函数。
所以,对FOREACH所遍历数组 (做了数据绑定的情况下) 的其中任一条数据做删除/替换/新增操作时,会导致FOREACH函数内的其他所有组件被重新创建。
解决方法
那么如何控制FOREACH函数对其他组件影响呢?
这时就提到了FOREACH函数的第三个参数,该参会要返回组件ID作为唯一标识。通过这个唯一标识,FOREACH就能够在数据变化时,判断出哪些组件唯一标识不变,从而不触发重走生命周期。
scss
FOREACH(
arr:Array,//要遍历的数据数组
(item:any,index?:number)=>{
//页面组件生成的函数回调
Row(){
Image(item.image)
Column(){
Text(item.name)
Text(item.price)
}
}
},
keyGenerator?:(item:any,index?:number):string => {
//定义每个item项的ID,必须唯一,用于列表内部的渲染判断
}
)
ArkTS声明式UI更新机制优化
ArkTS声明式UI在API 9上优化了相关UI更新机制,当自定义组件的某个状态变量发生变化导致自定义组件重新渲染时,仅执行该自定义组件build函数中的部分UI描述(使用了该状态变量的内置UI组件的UI描述)来实现更高性能的UI更新。而API8及以前在状态变量发生变化时会执行build函数中的全量UI描述来实现UI更新。
developer.huawei.com/consumer/cn...
UIAbility的启动模式
UIAbility的启动模式是指UIAbility实例在启动时的不同呈现状态。针对不同的业务场景,系统提供了三种启动模式:
1. singleton(单实例模式)
- 系统中只存在唯一一个该UIAbility实例,即在最近任务列表中只存在一个该类型的UIAbility实例。
是默认启动模式。
每次调用startAbility()方法时,如果应用进程中该类型的UIAbility实例已经存在,则复用系统中的UIAbility实例。
应用的UIAbility实例已创建,该UIAbility配置为单实例模式,再次调用startAbility()方法启动该UIAbility实例,此时只会进入该UIAbility的onNewWant()回调,不会进入其onCreate()和onWindowStageCreate()生命周期回调。
使用:如果需要使用singleton启动模式,在module.json5配置文件中的"launchType"字段配置为"singleton"即可。
json
{
"module": {
// ...
"abilities": [
{
"launchType": "singleton",
// ...
}
]
}
}
2. multiton(多实例模式)
- multiton启动模式为多实例模式,每次调用startAbility()方法时,都会在应用进程中创建一个新的该类型UIAbility实例。即在最近任务列表中可以看到有多个该类型的UIAbility实例。
使用:multion也是将在module.json5配置文件中的"launchType"字段配置为"multion"即可。
3. specified(指定实例模式)
- specified启动模式为指定实例模式,在目标UIAbility实例创建之前,即我们调用startAbility跳转目标UIAbility之前,我们在want参数中添加一个自定义参数字符串Key作为唯一标识,之后在被调起方的UIAbility启动之前,会先进入其对应的AbilityStage的onAcceptWant()生命周期回调中,我们解析传入的want参数,获取对应的参数。并根据业务需要在onAcceptWant()中返回自定义的唯一Key。
- 如果我们返回的Key对应了一个已启动的UIAbility,系统则会将之前的UIAbility拉回前台并获焦,而不创建新的实例,否则创建新的实例并启动。
注意:
应用的UIAbility实例已创建,该UIAbility配置为指定实例模式,再次调用startAbility()方法启动该UIAbility实例,且AbilityStage的onAcceptWant()回调匹配到一个已创建的UIAbility实例。此时,再次启动该UIAbility时,只会进入该UIAbility的onNewWant()回调,不会进入其onCreate()和onWindowStageCreate()生命周期回调。
使用方法:
例如有两个UIAbility:EntryAbility和FuncAbility,FuncAbility配置为specified启动模式,需要从EntryAbility的页面中启动FuncAbility。
- 在FuncAbility中,将module.json5配置文件的"launchType"字段配置为"specified"。
- 在EntryAbility中,调用startAbility()方法时,在want参数中,增加一个自定义参数来区别UIAbility实例,例如增加一个"instanceKey"自定义参数。
注:如果在page页面中,那么要通过let context= (getContext(this) as common.UIAbilityContext)
来获取UIAbility的context。
javascript
function getInstance() {
// ...
}
//构建要跳转的目标Ability相关信息
let want = {
deviceId: '', // deviceId为空表示本设备
bundleName: 'com.example.myapplication',
abilityName: 'FuncAbility',//目标Ability的name,在module.json5->abilities->name中。
moduleName: 'module1', // moduleName非必选
parameters: { // 自定义信息
instanceKey: getInstance(),
},
}
// 如果在page页面中,那么要通过 let context= (getContext(this) as common.UIAbilityContext),来获取context。
// context为调用方UIAbility的AbilityContext
this.context.startAbility(want).then(() => {
// ...
}).catch((err) => {
// ...
})
- 在被调用端对应的AbilityStoge中,解析传入的want参数,获取"instanceKey"自定义参数;并在其onAcceptWant()生命周期回调返回一个字符串Key标识。之后的跳转逻辑则不需要我们处理。(别忘了在配置文件中声明当前的AbilityStage)
scala
import AbilityStage from '@ohos.app.ability.AbilityStage';
export default class MyAbilityStage extends AbilityStage {
onAcceptWant(want): string {
// 在被调用方的AbilityStage中,针对启动模式为specified的UIAbility返回一个UIAbility实例对应的一个Key值
// 当前示例指的是module1 Module的FuncAbility
if (want.abilityName === 'FuncAbility') {
// 返回的字符串Key标识为自定义拼接的字符串内容
return `ControlModule_EntryAbilityInstance_${want.parameters.instanceKey}`;
}
return '';
}
}
注意:
在我们配置启动模式时,ide联想出了四个启动模式,而官方文档只有三个启动模式。
此时,不要纠结,就当它是给FA应用模型使用的。
AbilityStage组件容器
在指定UIAbility的启动模式时,我们学习了 specified(指定实例模式) ,在使用 specified 启动模式时,我们需要在 AbilityStage 的onAcceptWant()函数中返回Key值。
AbilityStage是一个Module级别的组件容器,应用的HAP在首次加载时会创建一个AbilityStage实例,可以对该Module进行初始化等操作。
AbilityStage与Module一一对应,即一个Module拥有一个AbilityStage。
DevEco Studio默认工程中未自动生成AbilityStage,如需要使用AbilityStage的能力,可以手动新建一个AbilityStage文件,步骤如下:
- 在工程Module对应的ets目录下,右键选择"New > Directory",新建一个目录并命名为myabilitystage。
- 在myabilitystage目录,右键选择"New > TypeScript File",新建一个TypeScript文件并命名为MyAbilityStage.ts。
- 创建一个类,并继承自AbilityStage,重写onCreate、onAcceptWant。
scala
import AbilityStage from '@ohos.app.ability.AbilityStage';
import Want from '@ohos.app.ability.Want';
export default class MyAbilityStage extends AbilityStage {
onCreate(){
// 应用的HAP在首次加载的时,为该Module初始化操作
}
onAcceptWant(want:Want):string{
// 仅specified模式下触发
return "0"
}
}
export ****default :表示默认导出当前类。
- 配置自定义的AbilityStage;在module.json5配置文件中,通过配置srcEntry参数来指定模块对应的代码路径,以作为HAP加载的入口。
json
{
"module": {
"name": "entry",
"type": "entry",
"srcEntry": "./ets/myabilitystage/MyAbilityStage.ts",
...
}
}
AbilityStage生命周期
AbilityStage拥有onCreate()生命周期回调和onAcceptWant()、onConfigurationUpdated()、onMemoryLevel()事件回调。
- onCreate()生命周期回调:在开始加载对应Module的第一个UIAbility实例之前会先创建AbilityStage,并在AbilityStage创建完成之后执行其onCreate()生命周期回调。
AbilityStage模块提供在Module加载的时候,通知开发者,可以在此进行该Module的初始化(如资源预加载,线程创建等)能力。 - onAcceptWant()事件回调:UIAbility指定实例模式(specified)启动时候触发的事件回调。
- onConfigurationUpdated()事件回调:当系统全局配置发生变更时触发的事件,系统语言、深浅色等,配置项目前均定义在Configuration类中。
- onMemoryLevel()事件回调:当系统调整内存时触发的事件。
应用被切换到后台时,系统会将在后台的应用保留在缓存中。即使应用处于缓存中,也会影响系统整体性能。当系统资源不足时,系统会通过多种方式从应用中回收内存,必要时会完全停止应用,从而释放内存用于执行关键任务。为了进一步保持系统内存的平衡,避免系统停止用户的应用进程,可以在AbilityStage中的onMemoryLevel()生命周期回调中订阅系统内存的变化情况,释放不必要的资源。
如何考鸿蒙线上能力认证以及如何在官网学习
- 开发文档目录:developer.huawei.com/consumer/cn...
在这里,你可以查看Harmony OS应用开发相关的相关能力使用。 - 搜索文档资料:developer.huawei.com/consumer/cn...
在这里,我们可以全局的搜索一些功能解决方案和接口文档。 - Openharmoney三方库地址:ohpm.openharmony.cn/#/cn/home
在这里,可以找到我们应用所能使用的三方库框架。 - 关于应用上架
上架教程:developer.huawei.com/consumer/cn...
上架AGC地址:developer.huawei.com/consumer/cn... - 考证位置:
建议先过基础认证,再去学习高级认证。
HarmonyOS应用开发者认证是线上考试;还有一种是线下的,需要费用:200~300USD。
我们应该是考线上认证就行了,在这基础 认证考试的链接里,是有提供配套的学习课程。
高级认证则没有提供,需要我们自己去查找,根据考试大纲来学习相对应知识。
每个等级考试,每个账户,每个月有三次考试机会。
注意 :线上认证考试,考完之后他只有分数,不会给你看错题,也不会告诉你哪题错了。
HarmonyOS应用开发者基础 认证:developer.huawei.com/consumer/cn...
HarmonyOS应用开发者高级 认证:developer.huawei.com/consumer/cn...