鸿蒙中级课程笔记4—应用程序框架进阶1—Stage模型应用组成结构、UIAbility启动模式、启动应用内UIAbility

Stage模型应用组成结构

什么是Stage模型?

Stage模型是HarmonyoS设计的一种应用架构模型,旨在优化应用的生命周期管理、资源分配和多设备协同能力。

  • 每个UlAbility拥有自己独立的WindowStage和Window,该主窗口为ArkUI提供了绘制区域,并管理多个ArkUI页面。

什么是UlAbility?

UIAbility组件是一种包含UI的应用组件,主要用于和用户交互。

什么是ExtensionAbility?

ExtensionAbility是基于特定场景(例如服务卡片、输入法等)提供的应用组件。每一个具体场景对应一个ExtensionAbilityType,开发者只能使用系统已定义的类型。

场景介绍

桌面服务卡片:通过FormExtensionAbility创建桌面卡片,显示简化的天气信息(如当前温度和图标)。

什么是AbilityStage?

AbilityStage是一个Module级别的组件容器,应用某个模块在首次加载时会创建一个AbilityStage实例,可以对该Module进行初始化等操作。AbilityStage与Module一一对应,即一个Module拥有一个AbilityStage.

场景介绍

  • 当应用启动时,在AbilityStage实例的生命周期中初始化全局对象(如天气卡片的城市列表缓存、网络请求配置)
  • 当用户切换深色/浅色模式时,在AbilityStage实例的回调方法中统一更新所有UIAbility的主题配置.

什么是WindowStage?

每个UlAbility实例都会与一个WindowStage类实例绑定,该类起到了应用进程内窗口管理器的作用。它包含一个主窗口,也就是说UIAbility实例通过WindowStage持有了一个主窗口,该主窗口为ArkUI提供了绘制区域,可以加载不同的ArkUI页面。

场景介绍

  • 天气应用中,通过WindowStage设置窗口属性。
  • 通过WindowStage加载天气主界面布局到窗口。

应用上下文Context

Context是应用中对象的上下文,其提供了应用的一些基础信息,例如resourceManager (资源管理)、applicationInfo(当前应用信息)、dir(应用文件路径)、area(文件分区)等,以及应用的一些基本方法,例如getApplicationContext()等。

系统为Application、AbilityStage、UlAbility、ExtensionAbility分别提供了对应的上下文环境,分别是ApplicationContext、AbilityStageContext、UIAbilityContext、ExtensionAbilityContext。开发者可以通过上下文环境调用各种资源和能力。

场景介绍

  • 通过ApplicationContext获取应用全局资源,如获取天气应用的图标、名称等。
  • 通过UIAbilityContext获取当前UIAbility的窗口信息,如调整窗口大小或者位置.
  • 通过ExtensionAbilityContext管理服务卡片,如天气应用通常会在桌面上放置一个服务卡片,显示当前天气信息。
  • 通过AbilityStageContext 在HAP加载时初始化一些全局变量,如初始化如城市列表。

Stage模型

在开发中,一个项目可能需要包含1个或者多个可以单独运行的Module;对应到运行期,一个应用可能包含1个或者多个AbilityStage;对应到编译期,一个App可能包含1个或者多个HAP,HAP是应用安装和运行的基本单元;

在运行期,每个AbilityStage持有该Module上定义的UAbility组件和ExtensionAbility组件,当UAbility组件首次启动时,系统会为UAbility组件创建实例,并将该实例与持有它的AbilityStage实例关联;UAbility实例创建后,系统为该实例创建一个WindowStage实例,并一对一绑定;WindowStage提供了应用进程内窗口管理器的作用,它持有一个主窗口,为ArkUI提供绘制区域,并管理多个ArkUI页面。

同时,系统为Application、AbilityStage、UlAbility、ExtensionAbility分别提供了对应的上下文环境,来调用各种资源和能力,分别是ApplicationContext、AbilityStageContext、UIAbilityContext、ExtensionAbilityContext。

运行期和编译期的基本概念中,最重要的就是UlAbility组件,UlAbility组件的核心是生命周期,当用户启动使用和退出应用时,应用的UIAbility实例会在其生命周期的不同状态之间转换。

应用组件和窗口生命周期分离

在初级课程中,已经学习了UIAlbility组件的四种生命周期状态和其回调函数,下面我们来深入学习UlAbility组件、AbilityStage以及WindowStage间生命周期的关系。

在应用程序框架中,UIAbility组件和WindowStage各自拥有一套生命周期,在设备上首次启动某个UIAbility组件时,AbilityStage、UIAbility组件和Windowstage的生命周期执行顺序如图所示。

系统首先创建持有该UIlAbility组件的AbilityStage实例,创建成功后执行其onCreate生命周期回调函数,开发者可以通过重写该函数,再进行一些Module级别的资源初始化或临时全局共享数据的设置工作。

接下来,系统会为该UIAbility组件创建实例,创建成功后执行其onCreate生命周期回调函数,可以在该函数中初始化业务逻辑,例如变量定义、资源预加载等,用于后续的UI展示。onCreate函数执行完后,系统将为UlAbility实例创建一个WindowStage实例,创建成功后,执行UlAbility实例的onWindowStageCreate回调函数,可以通过该函数获取主窗口信息,设置UI加载页面以及监听WindowStage的生命周期变化。相应的在UIlAbility实例销毁之前,将先进入UIlAbility实例的onWindowStageDestroy回调函数,开发者可以在该函数中释放UI界面资源,例如在onWindowStageDestroy中注销WindowStage的事件。

从图中可以看出,应用组件和窗口的生命周期是松耦合的,这种设计有如下好处:

  • 业务逻辑和UI逻辑分离:开发者可以在UIAbility中处理与界面无关的业务逻辑,如打开蓝牙、连接数据库等,在WindowStage持有的窗口上,通过ArkUI处理界面相关的业务逻辑
  • 便于系统对应用组件进行裁剪(无屏设备可以裁剪窗口):对于无屏设备,系统在运行应用时,不会创建窗口模块
  • 在多设备(如桌面设备和移动设备)上,应用组件可使用同一套生命周期。应用运行时,系统会自动判断设备,并根据不同设备的窗回形态变化执行不同的生命周期变化流程。接下来以在单窗回形态上和多窗回形态上任务切换的场景为例说明切换过程:

UIAbility启动模式

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

说明

standard是multiton的曾用名,效果与多实例模式一致。

singleton启动模式

singleton启动模式为单实例模式,也是默认情况下的启动模式。

每次调用startAbility()方法时,如果应用进程中该类型的UIAbility实例已经存在,则复用系统中的UIAbility实例。系统中只存在唯一一个该UIAbility实例,即在最近任务列表中只存在一个该类型的UIAbility实例。

单实例模式启动过程

单实例模式演示效果

说明

应用的UIAbility实例已创建,该UIAbility配置为单实例模式,再次调用startAbility()方法启动该UIAbility实例。由于启动的还是原来的UIAbility实例,并未重新创建一个新的UIAbility实例,此时只会进入该UIAbility的onNewWant()回调,不会进入其onCreate()onWindowStageCreate()生命周期回调。如果已经创建的实例仍在启动过程中,调用startAbility()方法启动该实例,将收到错误码16000082。

如果需要使用singleton启动模式,在module.json5配置文件中的launchType字段配置为singleton即可。

TypeScript 复制代码
{
  "module": {
    // ···
    "abilities": [
    // ···
      {
        "launchType": "singleton",
        // ···
      }
    // ···
    ]
  }
}

multiton启动模式

multiton启动模式为多实例模式,每次调用startAbility()方法时,都会在应用进程中创建一个新的该类型UIAbility实例。即在最近任务列表中可以看到有多个该类型的UIAbility实例。这种情况下可以将UIAbility配置为multiton(多实例模式)。

多实例模式执行过程

多实例模式演示效果

multiton启动模式的开发使用,在module.json5配置文件中的launchType字段配置为multiton即可。

TypeScript 复制代码
{
  "module": {
    // ···
    "abilities": [
    // ···
      {
        "launchType": "multiton",
        // ···
      }
    // ···
    ]
  }
}

specified启动模式

specified启动模式为指定实例模式,针对一些特殊场景使用(例如文档应用中每次新建文档希望都能新建一个文档实例,重复打开一个已保存的文档希望打开的都是同一个文档实例)。

指定实例启动模式原理

假设应用有两个UIAbility实例,即EntryAbility和SpecifiedAbility。EntryAbility以specified模式启动SpecifiedAbility。基本原理如下:

  1. EntryAbility调用startAbility()方法,并在Want的parameters字段中设置唯一的Key值,用于标识SpecifiedAbility。
  2. 系统在拉起SpecifiedAbility之前,会先进入对应的AbilityStageonAcceptWant()生命周期回调,获取用于标识目标UIAbility的Key值。
  3. 系统会根据获取的Key值来匹配UIAbility。
    • 如果匹配到对应的UIAbility,则会启动该UIAbility实例,并进入onNewWant()生命周期回调。
    • 如果无法匹配对应的UIAbility,则会创建一个新的UIAbility实例,并进入该UIAbility实例的onCreate()生命周期回调和onWindowStageCreate()生命周期回调。

指定实例模式执行过程

指定实例模式演示效果

指定实例模式开发步骤

TypeScript 复制代码
{
  "module": {
    // ···
    "abilities": [
      {
        "launchType": "specified",
        // ···
      }
    // ···
    ]
  }
}
  • 在调用方页面上开发启动UIAbility组件的逻辑函数。在该逻辑函数中,包含下图3个步骤。 调用startAbility()方法时,可以在want参数中传入了自定义参数modelDataId作为唯一标识符,以此来区分不同的UIAbility实例。代码参考UIAbility组件的启动模式
  • 开发者在被调起的UIAbility组件所对应的AbilityStage中重写onAcceptWant()回调函数。在该场景中重写被调用方SpecifiedAbility所在的SpecifiedAbilityStage.etc文件的onAcceptWant()函数,在该函数中返回一个字符串,系统根据返回的唯一key值判断当前进程中对应的UIAbility是否存在。代码参考UIAbility组件的启动模式

说明

  1. 当应用的UIAbility实例已经被创建,并且配置为指定实例模式时,如果再次调用startAbility()方法启动该UIAbility实例,且AbilityStageonAcceptWant()回调匹配到一个已创建的UIAbility实例,则系统会启动原来的UIAbility实例,并且不会重新创建一个新的UIAbility实例。此时,该UIAbility实例的onNewWant()回调会被触发,而不会触发onCreate()和onWindowStageCreate()生命周期回调。
  2. DevEco Studio默认工程中未自动生成AbilityStage,AbilityStage文件的创建请参见AbilityStage开发步骤

启动应用内的UIAbility组件

UIAbility是系统调度的最小单元。在设备内的功能模块之间跳转时,会涉及到启动特定的UIAbility,包括应用内的其他UIAbility、或者其他应用的UIAbility(例如启动三方支付UIAbility)。

本文主要介绍启动应用内的UIAbility组件的方式。应用间的组件跳转详见应用间跳转

信息传递载体Want

Want是一种对象,用于在应用组件之间传递信息。Want中可以包含指定的启动目标,以及启动时需携带的相关数据。

Want一种常见的使用场景是作为startAbility()方法的参数。例如,当UIAbilityA需要启动UIAbiltyB并向UIAbilityB传递一些数据时,可以使用Want作为一个载,UIAbilityB.

启动应用内的UIAbility

当一个应用内包含多个UIAbility时,存在应用内启动UIAbility的场景。例如在支付应用中从入口UIAbility启动收付款UIAbility。

假设应用中有两个UIAbility:EntryAbility和FuncAbility(可以在同一个Module中,也可以在不同的Module中),需要从EntryAbility的页面中启动FuncAbility。

  • 在EntryAbility中,调用方通过调用startAbility()方法启动UIAbility,want为UIAbility实例启动的入口参数,其中bundleName为待启动应用的Bundle名称,abilityName为待启动的Ability名称,moduleName在待启动的UIAbility属于不同的Module时添加,parameters为自定义信息参数。示例中的context的获取方式请参见获取UIAbility的上下文信息
TypeScript 复制代码
import { common, Want } from '@kit.AbilityKit';
import { hilog } from '@kit.PerformanceAnalysisKit';
import { BusinessError } from '@kit.BasicServicesKit';

const TAG: string = '[MainPage]';
const DOMAIN_NUMBER: number = 0xFF00;

@Entry
@Component
struct MainPage {
  private context = this.getUIContext().getHostContext() as common.UIAbilityContext;

  build() {
    Column() {
      List({ initialIndex: 0, space: 8 }) {
        ListItem() {
          Row() {
            // ···
          }
          .onClick(() => {
            // context为Ability对象的成员,在非Ability对象内部调用需要
            // 将Context对象传递过去
            let wantInfo: Want = {
              deviceId: '', // deviceId为空表示本设备
              bundleName: 'com.samples.uiabilityinteraction',
              moduleName: 'entry', // moduleName非必选
              abilityName: 'FuncAbilityA',
              parameters: {
                // 自定义信息
                // app.string.main_page_return_info资源文件中的value值为'来自EntryAbility MainPage页面'
               info: $r('app.string.main_page_return_info')
              },
            };
            // context为调用方UIAbility的UIAbilityContext
            this.context.startAbility(wantInfo).then(() => {
              hilog.info(DOMAIN_NUMBER, TAG, 'startAbility success.');
            }).catch((error: BusinessError) => {
              hilog.error(DOMAIN_NUMBER, TAG, 'startAbility failed.');
            });
          })
        }
        // ···
      }
    // ···
    }
    // ···
  }
}
  • 被调用方,在FuncAbility的onCreate()或者onNewWant()生命周期回调文件中接收EntryAbility传递过来的Want参数。
TypeScript 复制代码
import { AbilityConstant, UIAbility, Want } from '@kit.AbilityKit';
// ···

export default class FuncAbilityA extends UIAbility {
  onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
    // 接收调用方UIAbility传过来的参数
    let funcAbilityWant = want;
    let info = funcAbilityWant?.parameters?.info;
    // ···
  }

// ···
}

说明

在被拉起的FuncAbility中,可以通过获取传递过来的want参数的parameters来获取拉起方UIAbility的PID、Bundle Name等信息。

说明

调用terminateSelf()方法停止当前UIAbility实例时,默认会保留该实例的快照(Snapshot),即在最近任务列表中仍然能查看到该实例对应的任务。如不需要保留该实例的快照,可以在其对应UIAbility的module.json5配置文件中,将abilities标签的removeMissionAfterTerminate字段配置为true。

启动应用内的UIAbility并获取返回结果

在一个EntryAbility启动另外一个FuncAbility时,希望在被启动的FuncAbility完成相关业务后,能将结果返回给调用方。例如在应用中将入口功能和账号登录功能分别设计为两个独立的UIAbility,在账号登录UIAbility中完成登录操作后,需要将登录的结果返回给入口UIAbility。

  1. 在EntryAbility中,调用startAbilityForResult()接口启动FuncAbility,异步回调中的data用于接收FuncAbility停止自身后返回给EntryAbility的信息。示例中的context的获取方式请参见获取UIAbility的上下文信息。 代码参考启动应用内的UIAbility组件

  2. 在FuncAbility停止自身时,需要调用terminateSelfWithResult()方法,入参abilityResult为FuncAbility需要返回给EntryAbility的信息。 代码参考启动应用内的UIAbility组件

  3. FuncAbility停止自身后,EntryAbility通过startAbilityForResult()方法回调接收被FuncAbility返回的信息,RESULT_CODE需要与前面的数值保持一致。

启动UIAbility的指定页面

概述

一个UIAbility可以对应多个页面,在不同的场景下启动该UIAbility时需要展示不同的页面,例如从一个UIAbility的页面中跳转到另外一个UIAbility时,希望启动目标UIAbility的指定页面。

UIAbility的启动分为两种情况:UIAbility冷启动和UIAbility热启动。

  • UIAbility冷启动:指的是UIAbility实例处于完全关闭状态下被启动,这需要完整地加载和初始化UIAbility实例的代码、资源等。
  • UIAbility热启动:指的是UIAbility实例已经启动并在前台运行过,由于某些原因切换到后台,再次启动该UIAbility实例,这种情况下可以快速恢复UIAbility实例的状态。

本文主要讲解目标UIAbility冷启动目标UIAbility热启动两种启动指定页面的场景,以及在讲解启动指定页面之前会讲解到在调用方如何指定启动页面。

调用方UIAbility指定启动页面

调用方UIAbility启动另外一个UIAbility时,通常需要跳转到指定的页面。例如FuncAbility包含两个页面(Index对应首页,Second对应功能A页面),此时需要在传入的want参数中配置指定的页面路径信息,可以通过want中的parameters参数增加一个自定义参数传递页面跳转信息。示例中的context的获取方式请参见获取UIAbility的上下文信息。 代码参考启动应用内的UIAbility组件

目标UIAbility冷启动

目标UIAbility冷启动时,在目标UIAbility的onCreate()生命周期回调中,接收调用方传过来的参数。然后在目标UIAbility的onWindowStageCreate()生命周期回调中,解析调用方传递过来的want参数,获取到需要加载的页面信息url,传入windowStage.loadContent()方法。 代码参考启动应用内的UIAbility组件

目标UIAbility热启动

在应用开发中,会遇到目标UIAbility实例之前已经启动过的场景,这时再次启动目标UIAbility时,不会重新走初始化逻辑,只会直接触发onNewWant()生命周期方法。为了实现跳转到指定页面,需要在onNewWant()中解析参数进行处理。

例如短信应用和联系人应用配合使用的场景。

  1. 用户先打开短信应用,短信应用的UIAbility实例启动,显示短信应用的主页。
  2. 用户将设备回到桌面界面,短信应用进入后台运行状态。
  3. 用户打开联系人应用,找到联系人张三。
  4. 用户点击联系人张三的短信按钮,会重新启动短信应用的UIAbility实例。
  5. 由于短信应用的UIAbility实例已经启动过了,此时会触发该UIAbility的onNewWant()回调,而不会再走onCreate()onWindowStageCreate()等初始化逻辑。

图1 目标UIAbility热启动

开发步骤如下所示。 代码参考启动应用内的UIAbility组件

  1. 冷启动短信应用的UIAbility实例时,在onWindowStageCreate()生命周期回调中,通过调用getUIContext()接口获取UI上下文实例UIContext对象。

  2. 在短信应用UIAbility的onNewWant()回调中通过AppStorage设置全局变量nameForNavi的值,并进行指定页面的跳转。此时再次启动该短信应用的UIAbility实例时,即可跳转到该短信应用的UIAbility实例的指定页面。

    1. 导入相关模块,并在onNewWant()生命周期回调中设置全局变量nameForNavi的值。

    2. 在Index页面显示时触发onPageShow回调,获取全局变量nameForNavi的值,并进行执行页面的跳转。

    3. 实现Navigation子页面。

    4. 在系统配置文件route_map.json中配置子页信息(参考系统路由表)。

    复制代码

    module.json5配置文件中配置routerMap路由映射。

    说明

    当被调用方UIAbility组件启动模式设置为multiton启动模式时,每次启动都会创建一个新的实例,那么onNewWant()回调就不会被用到。

相关推荐
四维碎片2 小时前
QSettings + INI 笔记
笔记·qt·算法
zzcufo3 小时前
多邻国第5阶段17-18学习笔记
笔记·学习
摘星编程3 小时前
React Native鸿蒙版:StackNavigation页面返回拦截
react native·react.js·harmonyos
中屹指纹浏览器3 小时前
指纹浏览器性能优化实操——多实例并发与资源占用管控
经验分享·笔记
Miguo94well4 小时前
Flutter框架跨平台鸿蒙开发——植物养殖APP的开发流程
flutter·华为·harmonyos·鸿蒙
九 龙4 小时前
Flutter框架跨平台鸿蒙开发——电影拍摄知识APP的开发流程
flutter·华为·harmonyos·鸿蒙
了一梨4 小时前
SQLite3学习笔记5:INSERT(写)+ SELECT(读)数据(C API)
笔记·学习·sqlite
星辰徐哥4 小时前
鸿蒙APP开发从入门到精通:ArkUI组件库详解与常用组件实战
华为·app·harmonyos·组件·arkui·组件库
九 龙4 小时前
Flutter框架跨平台鸿蒙开发——如何养花APP的开发流程
flutter·华为·harmonyos·鸿蒙