鸿蒙技术探索—“UIAbility组件”

84 阅读7分钟

UIAbility组件

组件概述

概述

UIAbility是一种包含UI的应用组件,是系统调用的基本单元,主要用于和用户交互。

声明配置

module.json5配置文件abilities标签中声明UIAbility的配置项

{
  "module": {
    ...
    "abilities": [
      {
        "name": "EntryAbility", // UIAbility组件的名称
        "srcEntry": "./ets/entryability/EntryAbility.ets", // 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组件启动页面背景颜色资源文件的索引
        ...
      }
    ]
  }
}

生命周期

  1. Create:在应用加载过程中,UIAbility实例创建完成时触发,系统会调用**onCreate()**回调。

    可以在该回调中进行页面初始化操作,例如变量定义资源加载等,用于后续的UI展示。

  2. Foreground:UIAbility实例切换至前台触发,对应**onForeground()**回调。

    可以申请系统需要的资源,或者重新申请在onBackground()中释放的资源。

  3. Background:UIAbility实例切换至后台触发,对应**onForeground()**回调。

    可以释放UI不可见时无用的资源,或者在此回调中执行较为耗时的操作,例如状态保存等。

  4. Destroy:UIAbility实例销毁时触发,onDestroy()回调。

    可以进行系统资源的释放、数据的保存等操作。

note:WindowStageCreate和WindowStageDestroy状态

  1. WindowStageCreate

    UIAbility实例创建完成之后,在进入Foreground之前,系统会创建一个WindowStage。WindowStage创建完成后会进入**onWindowStageCreate()**回调,可以在该回调中设置UI加载、设置WindowStage的事件订阅。

  2. WindowStageDestroy

    在UIAbility实例销毁之前,则会先进入onWindowStageDestroy()回调,可以在该回调中释放UI资源。

启动模式

module.json5配置文件中配置launchType字段

singleton(单实例模式)(默认)

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

multiton(多实例模式)

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

specified(指定实例模式)

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

eg:有两个UIAbility:A_Ability和B_Ability,需要从A_Ability的页面中启动B_Ability,B_Ability配置为指定实例模式。

步骤:

  1. module.json5的launchType字段配置为specified。

  2. 在创建B_UIAbility实例前,可以为该实例指定一个唯一的字符串Key,这样在调用startAbility()方法时,应用就可以根据指定的Key来识别响应请求的UIAbility实例。

    在A_Ability中,调用startAbility()方法时,可以在want参数中增加一个自定义参数,例如instanceKey,以此来区分不同的UIAbility实例。

    // 在启动指定实例模式的UIAbility时,给每一个UIAbility实例配置一个独立的Key标识
    // 例如在文档使用场景中,可以用文档路径作为Key标识
    import common from '@ohos.app.ability.common';
    import hilog from '@ohos.hilog';
    import Want from '@ohos.app.ability.Want';
    
    @Entry
    @Component
    struct Page_StartModel {
      private KEY_NEW = 'KEY';
      build() {
        Row() {
          Column() {
            Button()
              .onClick(() => {
                let context:common.UIAbilityContext = getContext(this) as common.UIAbilityContext;
                // context为调用方UIAbility的UIAbilityContext;
                let want: Want = {
                  deviceId: '', // deviceId为空表示本设备
                  bundleName: 'com.samples.stagemodelabilitydevelop',
                  abilityName: 'B_Ability',
                  moduleName: 'entry', // moduleName非必选
                  parameters: { // 自定义信息
                    instanceKey: this.KEY_NEW
                  }
                };
                context.startAbility(want).then(() => {
                  console.log('Succeeded in starting B_Ability.');
                }).catch((err) => {
                  console.error(`Failed to start B_Ability.`);
                })
                this.KEY_NEW = this.KEY_NEW + 'a';
              })
          }
          .width('100%')
        }
        .height('100%')
      }
    }
    
  3. 指定实例模式的B_Ability启动之前,会先进入对应的AbilityStage的startAbility()生命周期回调中,以获取该UIAbility实例的Key值。然后系统对Key匹配,若之前有相同的UIAbility会进入该UIAbility实例的onNewWant()回调函数,否则会创建一个新的UIAbility实例

    import AbilityStage from '@ohos.app.ability.AbilityStage';
    import type Want from '@ohos.app.ability.Want';
    
    export default class MyAbilityStage extends AbilityStage {
      onAcceptWant(want: Want): string {
        // 在被调用方的AbilityStage中,针对启动模式为specified的UIAbility返回一个UIAbility实例对应的一个Key值
        // 当前示例指的是B_Ability实例
        if (want.abilityName === 'B_Ability') {
          // 返回的字符串Key标识为自定义拼接的字符串内容
          if (want.parameters) {
            return `${want.parameters.instanceKey}`;
          }
        }
        return '';
      }
    }
    

基本用法

指定UIAbility的启动页面

在UIAbility的onWindowStageCreate()生命周期回调中,通过WindowStage对象的loadContent()方法设置启动页面。

import UIAbility from '@ohos.app.ability.UIAbility';
import window from '@ohos.window';

export default class EntryAbility extends UIAbility {
  onWindowStageCreate(windowStage: window.WindowStage): void {
    // Main window is created, set main page for this ability
    windowStage.loadContent('pages/Index', (err, data) => {
    });
  }
}

获取UIAbility的上下文信息

  • 在UIAbility中可以通过this.context获取UIAbility实例的上下文信息。

    export default class EntryAbility extends UIAbility {
      onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
        // 获取UIAbility实例的上下文
        let context = this.context;
        // ...
      }
    }
    
  • 通过getContext()在页面中获取UIAbility实例的上下文信息。

    import common from '@ohos.app.ability.common';
    import Want from '@ohos.app.ability.Want';
    @Entry
    @Component
    struct Index {
      private context = getContext(this) as common.UIAbilityContext;
      startAbilityTest() {
        let want: Want = {
          // Want参数信息
        };
        this.context.startAbility(want);
      }
      build() {
        // ...
      }
    }
    

UIAbility组件与UI的数据同步

EventHub进行数据通信

基类Context中,提供了EventHub对象,EventHub为UIAbility组件提供了事件机制,使它们能够进行订阅、取消订阅和触发事件等数据通信能力。

  1. 在UIAbility中调用EventHub.on()方法注册一个自定义事件“event1”。

    import hilog from '@ohos.hilog';
    import UIAbility from '@ohos.app.ability.UIAbility';
    import type window from '@ohos.window';
    import type { Context } from '@ohos.abilityAccessCtrl';
    import Want from '@ohos.app.ability.Want'
    import type AbilityConstant from '@ohos.app.ability.AbilityConstant';
    
    const DOMAIN_NUMBER: number = 0xFF00;
    const TAG: string = '[EventAbility]';
    
    export default class EntryAbility extends UIAbility {
      onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
        // 获取UIAbility实例的上下文
        let context = this.context;
        // 获取eventHub
        let eventhub = this.context.eventHub;
        // 执行订阅操作
        eventhub.on('event1', this.eventFunc);
        eventhub.on('event1', (data: string) => {
          // 触发事件,完成相应的业务操作
        });
      }
      eventFunc(argOne: Context, argTwo: Context): void {
        hilog.info(DOMAIN_NUMBER, TAG, '1. ' + `${argOne}, ${argTwo}`);
        return;
      }
    }
    
  2. 在UI中通过EventHub.emit()方法触发该事件,在触发事件的同时,根据需要传入参数信息。

    import common from '@ohos.app.ability.common';
    import promptAction from '@ohos.promptAction'
    
    @Entry
    @Component
    struct Page_EventHub {
      private context = getContext(this) as common.UIAbilityContext;
      eventHubFunc() : void {
        // 不带参数触发自定义“event1”事件
        this.context.eventHub.emit('event1');
        // 带1个参数触发自定义“event1”事件
        this.context.eventHub.emit('event1', 1);
        // 带2个参数触发自定义“event1”事件
        this.context.eventHub.emit('event1', 2, 'test');
        // 开发者可以根据实际的业务场景设计事件传递的参数
      }
    
      build() {
        Column() {
          Text()
            .onClick(() => {
              this.eventHubFunc();
              promptAction.showToast({
                message: 'emit'
              });
            })
          Text()
            .onClick(() => {
              this.context.eventHub.off('event1');
              promptAction.showToast({
                message: 'off'
              });
            })
    
        }
        .width('100%')
        .margin({ top: 8 })
      }
    }
    
  3. 在UIAbility的注册事件回调中可以得到对应的触发事件结果,运行日志结果如下所示。

    [Example].[Entry].[EntryAbility] 1. []
    [Example].[Entry].[EntryAbility] 1. [1]
    [Example].[Entry].[EntryAbility] 1. [2,"test"]
    
  4. 在自定义事件“event1”使用完成后,可以根据需要调用EventHub.off()方法取消该事件的订阅。

    // context为UIAbility实例的AbilityContext
    this.context.eventHub.off('event1');
    

AppStorage/LocalStorage通信

UIAbility组件间交互

启动本应用UIAbility

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

  1. 在A_Ability中,通过调用startAbility()方法启动UIAbility,want为UIAbility实例启动的入口参数,parameters为自定义信息参数

    import common from '@ohos.app.ability.common';
    import Want from '@ohos.app.ability.Want';
    
    @Entry
    @Component
    struct A_UIAbilityPage {
      private context = getContext(this) as common.UIAbilityContext;
    
      build() {
        Button()
          .onClick(() => {
            let wantInfo: Want = {
              deviceId: '', // deviceId为空表示本设备
              bundleName: 'com.samples.myapplication',
              moduleName: 'entry', // moduleName非必选
              abilityName: 'B_Ability',
              parameters: { // 自定义信息
                info: '来自A_Ability的A_UIAbilityPage页面'
              },
            }
            // context为调用方UIAbility的UIAbilityContext
            this.context.startAbility(wantInfo).then(……).catch(……);
          })
      }
    }
    
  2. 在B_Ability的onCreat()或者onNewWant()生命周期回调文件中接收A_Ability传递过来的参数。

    import UIAbility from '@ohos.app.ability.UIAbility';
    import AbilityConstant from '@ohos.app.ability.AbilityConstant';
    import Want from '@ohos.app.ability.Want';
    
    export default class B_Ability extends UIAbility {
      onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
        // 接收调用方UIAbility传过来的参数
        let B_AbilityWant = want;
        let info = B_AbilityWant?.parameters?.info;
        // ...
      }
    }
    
  3. 在B_Ability业务完成之后,如需要停止当前UIAbility实例,在B_Ability中通过调用terminateSelf()方法实现。

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

    import common from '@ohos.app.ability.common';
    
    @Entry
    @Component
    struct B_UIAbilityPage {
      build() {
        ...
        Button()
          .onClick(() => {
            let context: common.UIAbilityContext = getContext(this) as common.UIAbilityContext; // UIAbilityContext
            // context为需要停止的UIAbility实例的AbilityContext
            context.terminateSelf((err) => {
              if (err.code) {
                 ……
                return;
              }
            });
          })
      }
    }
    
  4. 如需要关闭应用所有的UIAbility实例,可以调用ApplicationContext的killAllProcesses()方法实现关闭应用所有的进程。

  5. 若要在A_Ability获取B_Ability返回的信息

    • 在B_Ability停止自身时,需要调用terminateSelfWithResult()方法,入参abilityResult为B_Ability需要返回给A_Ability的信息。

      import common from '@ohos.app.ability.common';
      
      @Entry
      @Component
      struct B_UIAbilityPage {
        build() {
          Button()
            .onClick(() => {
              let context: common.UIAbilityContext = getContext(this) as common.UIAbilityContext;
              const RESULT_CODE: number = 1001;
              let abilityResult: common.AbilityResult = {
                resultCode: RESULT_CODE,
                want: {
                  bundleName: 'com.samples.stagemodelabilitydevelop',
                  moduleName: 'entry', // moduleName非必选
                  abilityName: 'B_Ability',
                  parameters: {
                    info: '来自B_Ability的B_UIAbilityPage页面'
                  },
                },
              };
              context.terminateSelfWithResult(abilityResult, (err) => {
                if (err.code) {
                  ...
                  return;
                }
              });
            })
        }
      }
      
    • B_Ability停止自身后,A_Ability通过startAbilityForResult()方法回调接收被B_Ability返回的信息,RESULT_CODE需要与前面的数值保持一致

      Button()
          .onClick(() => {
          let context: common.UIAbilityContext = getContext(this) as common.UIAbilityContext; // UIAbilityContext
          const RESULT_CODE: number = 1001;
      
          let want: Want = {
              deviceId: '', // deviceId为空表示本设备
              bundleName: 'com.samples.stagemodelabilitydevelop',
              moduleName: 'entry', // moduleName非必选
              abilityName: 'B_Ability',
              parameters: { // 自定义信息
                  info: '来自A_Ability的A_UIAbilityPage页面'
              }
          };
          context.startAbilityForResult(want).then((data) => {
              if (data?.resultCode === RESULT_CODE) {
                  // 解析被调用方UIAbility返回的信息
                  let info = data.want?.parameters?.info;
                  if (info !== null) {
                      promptAction.showToast({
                          message: JSON.stringify(info)
                      });
              	}
          	}
          }).catch((err: BusinessError) => {
              ...
          });
      })
        
      

启动其他应用UIAbility

启动UIAbility有显示Want启动和隐式Want启动两种方式。而此时需要隐式启动Want。

例如从应用A的UIAbility跳到到应用B或C或等等应用的UIAbility

  1. 将B、C等应用安装到设备,在其对应UIAbility的module.json5配置文件中,配置skills标签的entities字段和actions字段。

    {
       "module": {
         "abilities": [
           {
             ...
             "skills": [
               {
                 "entities": [
                   ...
                   "entity.system.default"
                 ],
                 "actions": [
                   ...
                   "ohos.want.action.viewData"
                 ]
               }
             ]
           }
         ]
       }
     }
    
  2. 在应用A的want参数中的entities和action需要被包含在待匹配UIAbility的skills配置的entities和actions中。系统匹配到符合entities和actions参数条件的UIAbility后,会弹出选择框展示匹配到的UIAbility实例列表供用户选择使用。

    Button()
        .onClick(() => {
        let context: common.UIAbilityContext = getContext(this) as common.UIAbilityContext; // UIAbilityContext
        let want: Want = {
            deviceId: '',
            action: 'ohos.want.action.viewData',
            entities: ['entity.system.default']
        };
        // context为调用方UIAbility的UIAbilityContext
        context.startAbility(want).then(() => {
            ...
        }).catch((err: BusinessError) => {
            ...
        });
    })
    
  3. 在文档应用使用完成之后,如需要停止当前UIAbility实例,通过调用terminateSelf()方法实现。

  4. 若要在应用A的UIAbility获取B、C的UIAbility返回的信息

    方法与之前近乎相同。

    • 在B、C应用的UIAbility返回时,需要调用terminateSelfWithResult()方法实现停止自身,并将abilityResult参数信息返回给调用方。

    • 应用A需要使用startAbilityForResult()方法启动支付应用的UIAbility,RESULT_CODE需要与前面terminateSelfWithResult()返回的数值保持一致。异步回调中的data用于后续接收支付UIAbility停止自身后返回给调用方的信息。系统匹配到符合entities和actions参数条件的UIAbility后,会弹出选择框展示匹配到的UIAbility实例列表供用户选择使用。

启动UIAbility的指定页面

调用方UIAbility指定启动页面

此时需要在传入的want参数中配置指定的页面路径信息,可以通过want中的parameters参数增加一个自定义参数传递页面跳转信息。

let want: Want = {
    deviceId: '', // deviceId为空表示本设备
    bundleName: 'com.samples.stagemodelabilityinteraction',
    moduleName: 'entry', // moduleName非必选
    abilityName: 'FuncAbility',
    parameters: { // 自定义参数传递页面信息
        router: 'FuncA'
    }
}
目标UIAbility冷启动
  • 指的是UIAbility实例处于完全关闭状态下被启动,这需要完整地加载和初始化UIAbility实例的代码、资源等。

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

    export default class FuncAbility extends UIAbility {
        funcAbilityWant: Want | undefined = undefined;
        onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
            // 接收调用方UIAbility传过来的参数
            this.funcAbilityWant = want;
        }
        onWindowStageCreate(windowStage: window.WindowStage) {
            // Main window is created, set main page for this ability
            let url = 'pages/Index';
            if (this.funcAbilityWant?.parameters?.router && this.funcAbilityWant.parameters.router === 'funcA') {
                url = 'pages/Page_ColdStartUp';
                }
            windowStage.loadContent(url, (err, data) => {
                // ...
            });
        }
    }
    
目标UIAbility热启动
  • 指的是UIAbility实例已经启动并在前台运行过,由于某些原因切换到后台,再次启动该UIAbility实例,这种情况下不会重新走初始化逻辑,只会直接触发onNewWant()生命周期方法。
  • 为了实现跳转到指定页面,需要在onNewWant()中解析参数进行处理。
onNewWant(want: Want, launchParam: AbilityConstant.LaunchParam): void {
    if (want?.parameters?.router && want.parameters.router === 'funcB') {
        let funcAUrl = 'pages/Page_HotStartUp';
        if (this.uiContext) {
            let router: Router = this.uiContext.getRouter();
            router.pushUrl({
                url: funcAUrl
            }).catch((err: BusinessError) => {
                hilog.error(DOMAIN_NUMBER, TAG, `Failed to push url. Code is ${err.code}, message is ${err.message}`);
            });
        }
    }
}