2024-09-18

HarmonyOS Next应用开发:UIAbility组件介绍

UIAbility是Stage模型中的组件类型名,包含UI,提供展示UI的能力,主要用于和用户交互。

UIAbility的设计理念:

1.原生支持应用组件级的跨端迁移和多端协同。
2.支持多设备和多窗口形态。

UIAbility划分原则与建议:

UIAbility组件是系统调度的基本单元,为应用提供绘制界面的窗口。一个应用可以包含一个或多个UIAbility组件。
每一个UIAbility组件实例都会在最近任务列表中显示一个对应的任务。
对于开发者而言,可以根据具体场景选择单个还是多个UIAbility,划分建议如下:
如果开发者希望在任务视图中看到一个任务,则建议使用一个UIAbility,多个页面的方式。


单UIAbility应用

如果开发者希望在任务视图中看到多个任务,或者需要同时开启多个窗口,则建议使用多个UIAbility开发不同的模块功能。


多UIAbility应用

UIAbility组件基本用法

创建一个UIAbility需要继承UIAbility,并且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) {
    windowStage.loadContent('pages/Index', (err, data) => {
      
    });
  }

}

为使应用能够正常使用UIAbility,需要在module.json5配置文件的abilities标签中声明UIAbility的名称、入口、标签等相关信息。

"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组件启动页面背景颜色资源文件的索引
        "exported": true,// UIAbility组件是否可以被其他应用调用
        "skills": [// UIAbility组件能够接收的Want特征集,为数组格式
          {
            "entities": [
              "entity.system.home"
            ],
            "actions": [
              "action.system.home"
            ]
          }
        ]
      }
    ]

Stage模型支持对组件配置入口图标和入口标签。入口图标和入口标签会显示在桌面上。

入口图标需要在 module.json5配置文件中配置,除了需要配置icon与label字段,还需要在skills标签下面的entities中添加"entity.system.home"、actions中添加"action.system.home"。同一个应用有多个UIAbility配置上述字段时,桌面上会显示出多个图标,分别对应各自的UIAbility。

如果abilities标签下声明的UIAbility的skills标签都为空,那么此应用为无图标应用,系统对无图标应用实施严格管控,防止一些恶意应用故意配置无桌面应用图标,导致用户找不到软件所在的位置,无法操作卸载应用,在一定程度上保证用户终端设备的安全。除预置应用外,其他应用不支持隐藏桌面图标。

UIAbility间的跳转和数据传递

启动应用内的UIAbility

创建两个UIAbility,分别为EntryAbility和SecondAbility。在EntryAbility下的页面Index中获取UIAbility实例的上下文信息,通过UIAbilityContext的startAbility()方法启动UIAbility,startAbility()方法中的Want携带了启动Ability的信息。

import Want from '@ohos.app.ability.Want';
import common from '@ohos.app.ability.common';
import hilog from [<u>'@ohos.hilog';</u>](mailto:'@ohos.hilog';)

const TAG: string = 'IndexPage';
const DOMAIN_NUMBER: number = 0xFF00;

@Entry
@Component
struct Index {

  build() {
    Row() {
      Column() {
        Button('点击')
          .margin({
            top: 20
          })
          .width('50%')
          .height('5%')
          .onClick(()=>{
            // context为Ability对象的成员,在非Ability对象内部调用需要
            let context = getContext(this) as common.UIAbilityContext;
            // 将Context对象传递过去
            let wantInfo: Want = {
              deviceId: '', // deviceId为空表示本设备
              bundleName: 'com.loop.hello',
              moduleName: 'entry', // moduleName非必选
              abilityName: 'SecondAbility',
              parameters: {
                // 自定义信息
                info: '来自EntryAbility Index页面'
              },
            };

            // context为调用方UIAbility的UIAbilityContext
            context.startAbility(wantInfo).then(() => {
              hilog.info(DOMAIN_NUMBER, TAG, 'startAbility success.');
            }).catch((error) => {
              hilog.error(DOMAIN_NUMBER, TAG, 'startAbility failed.');
            });
          })
      }
      .width('100%')
    }
    .height('100%')
  }
}

在SecondAbility的onCreate()或者onNewWant()生命周期回调文件中接收EntryAbility传递过来的参数。

import UIAbility from '@ohos.app.ability.UIAbility';
import hilog from [<u>'@ohos.hilog';</u>](mailto:'@ohos.hilog';)
export default class SecondAbility extends UIAbility {

  onCreate(want, launchParam) {
    // 接收调用方UIAbility传过来的参数]
    let info = want?.parameters?.info;
    hilog.info(0x0000, 'SecondAbility', '%{public}s', `want param: ${info}`);
  }

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

在一个EntryAbility启动另外一个SecondAbility时,希望在被启动的SecondAbility完成相关业务后,能将结果返回给调用方。在EntryAbility中,调用startAbilityForResult()接口启动SecondAbility,异步回调中的data用于接收SecondAbility停止自身后返回给EntryAbility的信息。

import Want from '@ohos.app.ability.Want';
import common from '@ohos.app.ability.common';
import hilog from '@ohos.hilog';
import promptAction from '@ohos.promptAction';

const TAG: string = 'IndexPage';
const DOMAIN_NUMBER: number = 0xFF00;

@Entry
@Component
struct Index {
    build() {
    Row() {
      Column() {
        Button('点击')
          .margin({
            top: 20
          })
          .width('50%')
          .height('5%')
          .onClick(()=>{
            // context为Ability对象的成员,在非Ability对象内部调用需要
            let context = getContext(this) as common.UIAbilityContext;
            const RESULT_CODE: number = 1001;
            // 将Context对象传递过去
            let wantInfo: Want = {
              deviceId: '', // deviceId为空表示本设备
              bundleName: 'com.loop.hello',
              moduleName: 'entry', // moduleName非必选
              abilityName: 'SecondAbility',
              parameters: {
                // 自定义信息
                info: '来自EntryAbility Index页面'
              },
            };
            // context为调用方UIAbility的UIAbilityContext
            context.startAbilityForResult(wantInfo).then((data) => {
              if (data?.resultCode === RESULT_CODE) {
                // 解析被调用方UIAbility返回的信息
                let info = data.want?.parameters?.info;
                hilog.info(DOMAIN_NUMBER, TAG, JSON.stringify(info) ?? '');
                if (info !== null) {
                  promptAction.showToast({
                    message: JSON.stringify(info)
                  });
                }
              }
              hilog.info(DOMAIN_NUMBER, TAG, 'startAbility success.');
            }).catch((error) => {
              hilog.error(DOMAIN_NUMBER, TAG, 'startAbility failed.');
            });
          })
      }
      .width('100%')
    }
    .height('100%')
  }
}

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

import common from '@ohos.app.ability.common'
import hilog from '@ohos.hilog'

const TAG: string = 'SecondPage';
const DOMAIN_NUMBER: number = 0xFF00;

@Entry
@Component
struct SecondPage {
  @State message: string = 'SecondPage'

  build() {
    Row() {
      Column() {
        Text(this.message)
          .fontSize(50)
          .fontWeight(FontWeight.Bold)
          .onClick(()=>{
            let context: common.UIAbilityContext = getContext(this) as common.UIAbilityContext; // UIAbilityContext
            const RESULT_CODE: number = 1001;
            let abilityResult: common.AbilityResult = {
              resultCode: RESULT_CODE,
              want: {
                bundleName: 'com.loop.hello',
                moduleName: 'entry', // moduleName非必选
                abilityName: 'EntryAbility',
                parameters: {
                  info: '来自SecondAbility SecondPage页面'
                },
              },
            };
            context.terminateSelfWithResult(abilityResult, (err) => {
              if (err.code) {
                hilog.error(DOMAIN_NUMBER, TAG, `Failed to terminate self with result. Code is ${err.code}, message is ${err.message}`);
                return;
              }
            });

          })
      }
      .width('100%')
    }
    .height('100%')
  }
}

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时,通常需要跳转到指定的页面,此时需要在传入的want参数中配置指定的页面路径信息,可以通过want中的parameters参数增加一个自定义参数传递页面跳转信息。

import Want from '@ohos.app.ability.Want';
import common from '@ohos.app.ability.common';
import hilog from [<u>'@ohos.hilog';</u>](mailto:'@ohos.hilog';)

const TAG: string = 'IndexPage';
const DOMAIN_NUMBER: number = 0xFF00;

@Entry
@Component
struct Index {

  build() {
    Row() {
      Column() {
        Button('点击')
          .margin({
            top: 20
          })
          .width('50%')
          .height('5%')
          .onClick(()=>{
            // context为Ability对象的成员,在非Ability对象内部调用需要
            let context = getContext(this) as common.UIAbilityContext;
            // 将Context对象传递过去
            let wantInfo: Want = {
              deviceId: '', // deviceId为空表示本设备
              bundleName: 'com.loop.hello',
              moduleName: 'entry', // moduleName非必选
              abilityName: 'SecondAbility',
              parameters: {
                // 自定义参数传递页面信息
                router: 'SecondAnotherPage'
              },
            };
            // context为调用方UIAbility的UIAbilityContext
            context.startAbility(wantInfo).then(() => {
              hilog.info(DOMAIN_NUMBER, TAG, 'startAbility success.');
            }).catch((error) => {
              hilog.error(DOMAIN_NUMBER, TAG, 'startAbility failed.');
            });
          })
      }
      .width('100%')
    }
    .height('100%')
  }
}

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

import UIAbility from '@ohos.app.ability.UIAbility';
import hilog from '@ohos.hilog';
import window from '@ohos.window';
import Want from '@ohos.app.ability.Want';
import AbilityConstant from '@ohos.app.ability.AbilityConstant';
import router from '@ohos.router';

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

export default class SecondAbility extends UIAbility {
  secondAbilityWant : Want|undefined = undefined;
  
  onCreate(want, launchParam) {
    // 接收调用方UIAbility传过来的参数]
    this.secondAbilityWant = want;
  }

  onWindowStageCreate(windowStage: window.WindowStage) {
    let url = 'pages/SecondPage';
    if(this.secondAbilityWant?.parameters?.router && this.secondAbilityWant.parameters.router === 'SecondAnotherPage') {
      url = 'pages/SecondAnotherPage';
    }
    windowStage.loadContent(url, (err, data) => {
      if (err.code) {
        hilog.error(0x0000, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err) ?? '');
        return;
      }
      hilog.info(0x0000, 'testTag', 'Succeeded in loading the content. Data: %{public}s', JSON.stringify(data) ?? '');
    });
  }

}

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

import common from '@ohos.app.ability.common'
import Want from '@ohos.app.ability.Want'
import hilog from '@ohos.hilog'

const TAG: string = 'SecondAnotherPage';
const DOMAIN_NUMBER: number = 0xFF00;

@Entry
@Component
struct SecondAnotherPage {
  @State message: string = 'SecondAnotherPage'

  build() {
    Row() {
      Column() {
        Text(this.message)
          .fontSize(50)
          .fontWeight(FontWeight.Bold)
          .onClick(() => {
            let context = getContext(this) as common.UIAbilityContext;
            // 将Context对象传递过去
            let wantInfo: Want = {
              deviceId: '', // deviceId为空表示本设备
              bundleName: 'com.loop.hello',
              moduleName: 'entry', // moduleName非必选
              abilityName: 'SecondAbility',
              parameters: {
                // 自定义信息
                router: 'SecondPage'
              },
            };
            // context为调用方UIAbility的UIAbilityContext
            context.startAbility(wantInfo).then(()=>{
              hilog.info(DOMAIN_NUMBER, TAG, 'startAbility success.');
            }).catch((error) => {
              hilog.error(DOMAIN_NUMBER, TAG, 'startAbility failed.');
            });
          })
      }
      .width('100%')
    }
    .height('100%')
  }
}

在SecondAbility的onNewWant()回调中(而不会再走onCreate()和onWindowStageCreate()等初始化逻辑)解析调用方传递过来的want参数,并进行指定页面的跳转。此时再次启动该SecondAbility实例时,即可跳转到该UIAbility实例的指定页面。

import UIAbility from '@ohos.app.ability.UIAbility';
import hilog from '@ohos.hilog';
import window from '@ohos.window';
import Want from '@ohos.app.ability.Want';
import AbilityConstant from '@ohos.app.ability.AbilityConstant';
import router from '@ohos.router';

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

export default class SecondAbility extends UIAbility {
  secondAbilityWant : Want|undefined = undefined;
  
  onCreate(want, launchParam) {
    // 接收调用方UIAbility传过来的参数]
    this.secondAbilityWant = want;
  }

  onWindowStageCreate(windowStage: window.WindowStage) {
    let url = 'pages/SecondPage';
    if(this.secondAbilityWant?.parameters?.router && this.secondAbilityWant.parameters.router === 'SecondAnotherPage') {
      url = 'pages/SecondAnotherPage';
    }
    windowStage.loadContent(url, (err, data) => {
      if (err.code) {
        hilog.error(0x0000, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err) ?? '');
        return;
      }
      hilog.info(0x0000, 'testTag', 'Succeeded in loading the content. Data: %{public}s', JSON.stringify(data) ?? '');
    });
  }

  onNewWant(want: Want, launchParams: AbilityConstant.LaunchParam) {
    hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onNewWant');
    if (want?.parameters?.router && want.parameters.router === 'SecondPage') {
      let url = 'pages/SecondPage';
        router.pushUrl({
          url: url
        }).catch((err) => {
          hilog.error(DOMAIN_NUMBER, TAG, `Failed to push url. Code is ${err.code}, message is ${err.message}`);
        });
    }
  }

}

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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 220,367评论 6 512
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,959评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 166,750评论 0 357
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,226评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,252评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,975评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,592评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,497评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 46,027评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,147评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,274评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,953评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,623评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,143评论 0 23
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,260评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,607评论 3 375
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,271评论 2 358

推荐阅读更多精彩内容