HarmonyOS 应用开发之UIAbility组件间交互(设备内)

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

本文将从如下场景分别介绍设备内UIAbility间的交互方式。对于跨设备的应用组件交互,请参见 应用组件跨设备交互(流转)。

  • 启动应用内的UIAbility
  • 启动应用内的UIAbility并获取返回结果
  • 启动其他应用的UIAbility
  • 启动其他应用的UIAbility并获取返回结果
  • 启动UIAbility指定窗口模式(仅对系统应用开放)
  • 启动UIAbility的指定页面
  • 通过Call调用实现UIAbility交互(仅对系统应用开放)

启动应用内的UIAbility

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

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

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

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

@Entry
@Component
struct Page_UIAbilityComponentsInteractive {
  private context = getContext(this) as common.UIAbilityContext;

  build() {
    ...
    Button()
      .onClick(() => {
    // context为Ability对象的成员,在非Ability对象内部调用需要
        // 将Context对象传递过去
        let wantInfo: Want = {
          deviceId: '', // deviceId为空表示本设备
          bundleName: 'com.samples.myapplication',
          moduleName: 'entry', // moduleName非必选
          abilityName: 'FuncAbilityA',
          parameters: { // 自定义信息
            info: '来自EntryAbility Page_UIAbilityComponentsInteractive页面'
          },
        }
        // 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.');
        });
      })
  }
}
  1. 在FuncAbility的 onCreate() 生命周期回调文件中接收EntryAbility传递过来的参数。
import UIAbility from '@ohos.app.ability.UIAbility';
import AbilityConstant from '@ohos.app.ability.AbilityConstant';
import Want from '@ohos.app.ability.Want';

export default class FuncAbility 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等信息。

  1. 在FuncAbility业务完成之后,如需要停止当前UIAbility实例,在FuncAbility中通过调用 terminateSelf() 方法实现。
import common from '@ohos.app.ability.common';
import hilog from '@ohos.hilog';

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

@Entry
@Component
struct Page_UIAbilityComponentsInteractive {
  build() {
    ...
    Button()
      .onClick(() => {
        let context: common.UIAbilityContext = getContext(this) as common.UIAbilityContext; // UIAbilityContext
        // context为需要停止的UIAbility实例的AbilityContext
        context.terminateSelf((err) => {
          if (err.code) {
            hilog.error(DOMAIN_NUMBER, TAG, `Failed to terminate Self. Code is ${err.code}, message is ${err.message}`);
            return;
          }
        });
      })
  }
}

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

  1. 如需要关闭应用所有的UIAbility实例,可以调用 ApplicationContext 的 killAllProcesses() 方法实现关闭应用所有的进程。

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

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

  1. 在EntryAbility中,调用 startAbilityForResult() 接口启动FuncAbility,异步回调中的data用于接收FuncAbility停止自身后返回给EntryAbility的信息。
import common from '@ohos.app.ability.common';
import hilog from '@ohos.hilog';
import Want from '@ohos.app.ability.Want';
import { BusinessError } from '@ohos.base';

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

@Entry
@Component
struct Page_UIAbilityComponentsInteractive {
  build() {
    Button()
      .onClick(() => {
        let context: common.UIAbilityContext = getContext(this) as common.UIAbilityContext; // UIAbilityContext

        let want: Want = {
          deviceId: '', // deviceId为空表示本设备
          bundleName: 'com.samples.stagemodelabilitydevelop',
          moduleName: 'entry', // moduleName非必选
          abilityName: 'FuncAbilityA',
          parameters: { // 自定义信息
            info: '来自EntryAbility UIAbilityComponentsInteractive页面'
          }
        };
        context.startAbilityForResult(want).then((data) => {
          // ...
        }).catch((err: BusinessError) => {
          hilog.error(DOMAIN_NUMBER, TAG, `Failed to start ability for result. Code is ${err.code}, message is ${err.message}`);
        });
      })
  }
}
  1. 在FuncAbility停止自身时,需要调用terminateSelfWithResult() 方法,入参abilityResult为FuncAbility需要返回给EntryAbility的信息。
import common from '@ohos.app.ability.common';
import hilog from '@ohos.hilog';

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

@Entry
@Component
struct Page_FuncAbilityA {
  build() {
    Button()
      .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.samples.stagemodelabilitydevelop',
            moduleName: 'entry', // moduleName非必选
            abilityName: 'FuncAbilityB',
            parameters: {
              info: '来自FuncAbility Index页面'
            },
          },
        };
        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;
          }
        });
      })
  }
}
  1. FuncAbility停止自身后,EntryAbility通过 startAbilityForResult() 方法回调接收被FuncAbility返回的信息,RESULT_CODE需要与前面的数值保持一致。
import common from '@ohos.app.ability.common';
import hilog from '@ohos.hilog';
import Want from '@ohos.app.ability.Want';
import { BusinessError } from '@ohos.base';
import promptAction from '@ohos.promptAction';

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

@Entry
@Component
struct Page_UIAbilityComponentsInteractive {
  build() {
    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: 'FuncAbilityA',
          parameters: { // 自定义信息
            info: '来自EntryAbility UIAbilityComponentsInteractive页面'
          }
        };
        context.startAbilityForResult(want).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, JSON.stringify(data.resultCode) ?? '');
        }).catch((err: BusinessError) => {
          hilog.error(DOMAIN_NUMBER, TAG, `Failed to start ability for result. Code is ${err.code}, message is ${err.message}`);
        });
      })
  }
}

启动其他应用的UIAbility

启动其他应用的UIAbility,通常用户只需要完成一个通用的操作(例如需要选择一个文档应用来查看某个文档的内容信息)。系统会根据调用方的want参数来识别和启动匹配到的应用UIAbility。

启动UIAbility有 显式Want启动和隐式Want启动 两种方式。

  • 显式Want启动:启动一个确定应用的UIAbility,在want参数中需要设置该应用bundleName和abilityName,当需要拉起某个明确的UIAbility时,通常使用显式Want启动方式。

  • 隐式Want启动:根据匹配条件由用户选择启动哪一个UIAbility,即不明确指出要启动哪一个UIAbility(abilityName参数未设置),在调用 startAbility() 方法时,其入参want中指定了一系列的entities字段(表示目标UIAbility额外的类别信息,如浏览器、视频播放器)和actions字段(表示要执行的通用操作,如查看、分享、应用详情等)等参数信息,然后由系统去分析want,并帮助找到合适的UIAbility来启动。当需要拉起其他应用的UIAbility时,开发者通常不知道用户设备中应用的安装情况,也无法确定目标应用的bundleName和abilityName,通常使用隐式Want启动方式。

本文主要讲解如何通过隐式Want启动其他应用的UIAbility。

  1. 将多个待匹配的文档应用安装到设备,在其对应UIAbility的 module.json5配置文件 中,配置skills标签的entities字段和actions字段。
 {
   "module": {
     "abilities": [
       {
         ...
         "skills": [
           {
             "entities": [
               ...
               "entity.system.default"
             ],
             "actions": [
               ...
               "ohos.want.action.viewData"
             ]
           }
         ]
       }
     ]
   }
 }
  1. 在调用方want参数中的entities和action需要被包含在待匹配UIAbility的skills配置的entities和actions中。系统匹配到符合entities和actions参数条件的UIAbility后,会弹出选择框展示匹配到的UIAbility实例列表供用户选择使用。
import common from '@ohos.app.ability.common';
import Want from '@ohos.app.ability.Want';
import { BusinessError } from '@ohos.base';
import hilog from '@ohos.hilog';

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

@Entry
@Component
struct Page_UIAbilityComponentsInteractive {
  build() {
    Button()
      .onClick(() => {
        let context: common.UIAbilityContext = getContext(this) as common.UIAbilityContext; // UIAbilityContext
        let want: Want = {
          deviceId: '', // deviceId为空表示本设备
          // uncomment line below if wish to implicitly query only in the specific bundle.
          // bundleName: 'com.samples.stagemodelabilityinteraction',
          action: 'ohos.want.action.viewData',
          // entities can be omitted.
          entities: ['entity.system.default']
        };
        // context为调用方UIAbility的UIAbilityContext
        context.startAbility(want).then(() => {
          hilog.info(DOMAIN_NUMBER, TAG, 'Succeeded in starting FuncAbility.');
        }).catch((err: BusinessError) => {
          hilog.error(DOMAIN_NUMBER, TAG, `Failed to start FuncAbility. Code is ${err.code}, message is ${err.message}`);
        });
      })
  }
}

效果示意如下图所示,点击“打开PDF文档”时,会弹出选择框供用户选择。

  1. 在文档应用使用完成之后,如需要停止当前UIAbility实例,通过调用 terminateSelf() 方法实现。
import common from '@ohos.app.ability.common';
import hilog from '@ohos.hilog';

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

@Entry
@Component
struct Page_FromStageModel {
  @State message: string = 'Hello World'

  build() {
    Button()
      .onClick(() => {
        let context: common.UIAbilityContext = getContext(this) as common.UIAbilityContext; // UIAbilityContext
        // context为需要停止的UIAbility实例的AbilityContext
        context.terminateSelf((err) => {
          if (err.code) {
            hilog.error(DOMAIN_NUMBER, TAG, `Failed to terminate self. Code is ${err.code}, message is ${err.message}`);
            return;
          }
        });
      })
  }
}

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

当使用隐式Want启动其他应用的UIAbility并希望获取返回结果时,调用方需要使用 startAbilityForResult() 方法启动目标UIAbility。例如主应用中需要启动三方支付并获取支付结果。

  1. 在支付应用对应UIAbility的 module.json5配置文件 中,配置skills的entities字段和actions字段。
{
  "module": {
    "abilities": [
      {
        ...
        "skills": [
          {
            "entities": [
              ...
              "entity.system.default"
            ],
            "actions": [
              ...
              "ohos.want.action.editData"
            ]
          }
        ]
      }
    ]
  }
}
  1. 调用方使用 startAbilityForResult() 方法启动支付应用的UIAbility,在调用方want参数中的entities和action需要被包含在待匹配UIAbility的skills标签配置的entities和actions中。异步回调中的data用于后续接收支付UIAbility停止自身后返回给调用方的信息。系统匹配到符合entities和actions参数条件的UIAbility后,会弹出选择框展示匹配到的UIAbility实例列表供用户选择使用。
import common from '@ohos.app.ability.common';
import hilog from '@ohos.hilog';
import Want from '@ohos.app.ability.Want';
import { BusinessError } from '@ohos.base';

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

@Entry
@Component
struct Page_UIAbilityComponentsInteractive {
  build() {
    Button()
      .onClick(() => {
        let context: common.UIAbilityContext = getContext(this) as common.UIAbilityContext; // UIAbilityContext

        let want: Want = {
          deviceId: '', // deviceId为空表示本设备
          bundleName: 'com.samples.stagemodelabilitydevelop',
          moduleName: 'entry', // moduleName非必选
          abilityName: 'FuncAbilityA',
          parameters: { // 自定义信息
            info: '来自EntryAbility UIAbilityComponentsInteractive页面'
          }
        };
        context.startAbilityForResult(want).then((data) => {
          // ...
        }).catch((err: BusinessError) => {
          hilog.error(DOMAIN_NUMBER, TAG, `Failed to start ability for result. Code is ${err.code}, message is ${err.message}`);
        });
      })
  }
}
  1. 在支付UIAbility完成支付之后,需要调用 terminateSelfWithResult() 方法实现停止自身,并将abilityResult参数信息返回给调用方。
import common from '@ohos.app.ability.common';
import hilog from '@ohos.hilog';

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

@Entry
@Component
struct Page_FuncAbilityA {
  build() {
    Button()
      .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.samples.stagemodelabilitydevelop',
            moduleName: 'entry', // moduleName非必选
            abilityName: 'FuncAbilityB',
            parameters: {
              info: '来自FuncAbility Index页面'
            },
          },
        };
        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;
          }
        });
      })
  }
}
  1. 在调用方 startAbilityForResult() 方法回调中接收支付应用返回的信息,RESULT_CODE需要与前面 terminateSelfWithResult() 返回的数值保持一致。
import common from '@ohos.app.ability.common';
import hilog from '@ohos.hilog';
import Want from '@ohos.app.ability.Want';
import { BusinessError } from '@ohos.base';
import promptAction from '@ohos.promptAction';

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

@Entry
@Component
struct Page_UIAbilityComponentsInteractive {
  build() {
    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: 'FuncAbilityA',
          parameters: { // 自定义信息
            info: '来自EntryAbility UIAbilityComponentsInteractive页面'
          }
        };
        context.startAbilityForResult(want).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, JSON.stringify(data.resultCode) ?? '');
        }).catch((err: BusinessError) => {
          hilog.error(DOMAIN_NUMBER, TAG, `Failed to start ability for result. Code is ${err.code}, message is ${err.message}`);
        });
      })
  }
}

启动UIAbility指定窗口模式(仅对系统应用开放)

当用户打开应用时,应用程序会以不同的窗口模式进行展示,即启动UIAbility的窗口模式。应用程序可以启动为全屏模式,悬浮窗模式或分屏模式。

全屏模式是指应用程序启动后,占据整个屏幕,用户无法同时查看其他窗口或应用程序。全屏模式通常适用于那些要求用户专注于特定任务或界面的应用程序。

悬浮窗模式是指应用程序启动后,以浮动窗口的形式显示在屏幕上,用户可以轻松切换到其他窗口或应用程序。悬浮窗通常适用于需要用户同时处理多个任务的应用程序。

分屏模式允许用户在同一屏幕上同时运行两个应用程序,其中一个应用程序占据屏幕左侧/上侧的一部分,另一个应用程序占据右侧/下侧的一部分。分屏模式主要用于提高用户的多任务处理效率。

使用 startAbility() 方法启动UIAbility时,可以通过在入参中增加 StartOptions 参数的windowMode属性来配置启动UIAbility的窗口模式。

说明:

  1. 如果在使用 startAbility() 方法启动UIAbility时,入参中未指定 StartOptions 参数的windowMode属性,那么UIAbility将以系统默认的窗口展示形态启动。
  2. 为了确保启动的UIAbility展示形态能够被支持,需要在该UIAbility对应的 module.json5配置文件中 abilities标签的supportWindowMode字段确认启动的展示形态被支持。

以下是具体的操作步骤,以悬浮窗模式为例,假设需要从EntryAbility的页面中启动FuncAbility:

  1. 在调用 startAbility() 方法时,增加 StartOptions 参数。
  2. 在 StartOptions 参数中设置windowMode字段为WINDOW_MODE_FLOATING,表示启动的UIAbility将以悬浮窗的形式展示。
  3. windowMode属性仅适用于系统应用,三方应用可以使用displayId属性。
import AbilityConstant from '@ohos.app.ability.AbilityConstant';
import common from '@ohos.app.ability.common';
import hilog from '@ohos.hilog';
import StartOptions from '@ohos.app.ability.StartOptions';
import Want from '@ohos.app.ability.Want';
import { BusinessError } from '@ohos.base';

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

@Entry
@Component
struct Page_UIAbilityComponentsInteractive {
  build() {
    Button()
      .onClick(() => {
        let context: common.UIAbilityContext = getContext(this) as common.UIAbilityContext; // UIAbilityContext
        let want: Want = {
          deviceId: '', // deviceId为空表示本设备
          bundleName: 'com.samples.stagemodelabilitydevelop',
          moduleName: 'entry', // moduleName非必选
          abilityName: 'FuncAbilityB',
          parameters: { // 自定义信息
            info: '来自EntryAbility Index页面'
          }
        };
        let options: StartOptions = {
          windowMode: AbilityConstant.WindowMode.WINDOW_MODE_FLOATING
        };
        // context为调用方UIAbility的UIAbilityContext
        context.startAbility(want, options).then(() => {
          hilog.info(DOMAIN_NUMBER, TAG, 'Succeeded in starting ability.');
        }).catch((err: BusinessError) => {
          hilog.error(DOMAIN_NUMBER, TAG, `Failed to start ability. Code is ${err.code}, message is ${err.message}`);
        });
      })
  }
}

效果示意如下图所示。

启动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参数增加一个自定义参数传递页面跳转信息。

import common from '@ohos.app.ability.common';
import hilog from '@ohos.hilog';
import Want from '@ohos.app.ability.Want';
import { BusinessError } from '@ohos.base';

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

@Entry
@Component
struct Page_UIAbilityComponentsInteractive {
  build() {
    Button()
      .onClick(() => {
        let context: common.UIAbilityContext = getContext(this) as common.UIAbilityContext; // UIAbilityContext
        let want: Want = {
          deviceId: '', // deviceId为空表示本设备
          bundleName: 'com.samples.stagemodelabilityinteraction',
          moduleName: 'entry', // moduleName非必选
          abilityName: 'FuncAbility',
          parameters: { // 自定义参数传递页面信息
            router: 'FuncA'
          }
        };
        // context为调用方UIAbility的UIAbilityContext
        context.startAbility(want).then(() => {
          hilog.info(DOMAIN_NUMBER, TAG, 'Succeeded in starting ability.');
        }).catch((err: BusinessError) => {
          hilog.error(DOMAIN_NUMBER, TAG, `Failed to start ability. Code is ${err.code}, message is ${err.message}`);
        });
      })
  }
}

目标UIAbility冷启动

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

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

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()中解析参数进行处理。

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

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

图1 目标UIAbility热启动

开发步骤如下所示。

  1. 冷启动短信应用的UIAbility实例时,在onWindowStageCreate()生命周期回调中,通过调用 getUIContext() 接口获取UI上下文实例 UIContext 对象。
import hilog from '@ohos.hilog';
import UIAbility from '@ohos.app.ability.UIAbility';
import Want from '@ohos.app.ability.Want';
import window from '@ohos.window';

import { UIContext } from '@ohos.arkui.UIContext';

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

export default class EntryAbility extends UIAbility {
  funcAbilityWant: Want | undefined = undefined;
  uiContext: UIContext | undefined = undefined;

  // ...

  onWindowStageCreate(windowStage: window.WindowStage): void {
    // 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) => {
      if (err.code) {
        return;
      }

      let windowClass: window.Window;
      windowStage.getMainWindow((err, data) => {
        if (err.code) {
          hilog.error(DOMAIN_NUMBER, TAG, `Failed to obtain the main window. Code is ${err.code}, message is ${err.message}`);
          return;
        }
        windowClass = data;
        this.uiContext = windowClass.getUIContext();
      })
    });
  }
}
  1. 在短信应用UIAbility的onNewWant()回调中解析调用方传递过来的want参数,通过调用UIContext中的getRouter() 方法获取 Router 对象,并进行指定页面的跳转。此时再次启动该短信应用的UIAbility实例时,即可跳转到该短信应用的UIAbility实例的指定页面。
import AbilityConstant from '@ohos.app.ability.AbilityConstant';
import hilog from '@ohos.hilog';
import UIAbility from '@ohos.app.ability.UIAbility';
import Want from '@ohos.app.ability.Want';
import { BusinessError } from '@ohos.base';

import { Router, UIContext } from '@ohos.arkui.UIContext';

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

export default class EntryAbility extends UIAbility {
  funcAbilityWant: Want | undefined = undefined;
  uiContext: UIContext | undefined = undefined;

  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}`);
        });
      }
    }
  }
  // ...
}

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

通过Call调用实现UIAbility交互(仅对系统应用开放)

Call调用是UIAbility能力的扩展,它为UIAbility提供一种能够被外部调用并与外部进行通信的能力。Call调用支持前台与后台两种启动方式,使UIAbility既能被拉起到前台展示UI,也可以在后台被创建并运行。Call调用在调用方与被调用方间建立了IPC通信,因此应用开发者可通过Call调用实现不同UIAbility之间的数据共享。

Call调用的核心接口是startAbilityByCall()方法,与startAbility()接口的不同之处在于:

  • startAbilityByCall支持前台与后台两种启动方式,而startAbility()仅支持前台启动。
  • 调用方可使用startAbilityByCall()所返回的Caller对象与被调用方进行通信,而startAbility()不具备通信能力。

Call调用的使用场景主要包括:

  • 需要与被启动的UIAbility进行通信。
  • 希望被启动的UIAbility在后台运行。

表1 Call调用相关名词解释

名词 描述
CallerAbility 进行Call调用的UIAbility(调用方)。
CalleeAbility 被Call调用的UIAbility(被调用方)。
Caller 实际对象,由startAbilityByCall接口返回,CallerAbility可使用Caller与CalleeAbility进行通信。
Callee 实际对象,被CalleeAbility持有,可与Caller进行通信。

Call调用示意图如下所示。

图1 Call调用示意图

  • CallerAbility调用startAbilityByCall接口获取Caller,并使用Caller对象的call方法向CalleeAbility发送数据。
  • CalleeAbility持有一个Callee对象,通过Callee的on方法注册回调函数,当接收到Caller发送的数据时将会调用对应的回调函数。

说明:

  1. 当前仅支持系统应用使用Call调用。
  2. CalleeAbility的启动模式需要为单实例。
  3. Call调用既支持本地(设备内)Call调用,也支持跨设备Call调用,下面介绍设备内Call调用方法。

接口说明

Call功能主要接口如下表所示。具体的API详见 接口文档。

表2 Call功能主要接口

接口名 描述
startAbilityByCall(want: Want): Promise<Caller> 启动指定UIAbility并获取其Caller通信接口,默认为后台启动,通过配置want可实现前台启动,详见 接口文档。AbilityContext与ServiceExtensionContext均支持该接口。
on(method: string, callback: CalleeCallBack): void 通用组件Callee注册method对应的callback方法。
off(method: string): void 通用组件Callee解注册method的callback方法。
call(method: string, data: rpc.Parcelable): Promise<void> 向通用组件Callee发送约定序列化数据。
callWithResult(method: string, data: rpc.Parcelable): Promise<rpc.MessageSequence> 向通用组件Callee发送约定序列化数据, 并将Callee返回的约定序列化数据带回。
release(): void 释放通用组件的Caller通信接口。
on(type: "release", callback: OnReleaseCallback): void 注册通用组件通信断开监听通知。

设备内通过Call调用实现UIAbility交互,涉及如下两部分开发:

  • 创建Callee被调用端
  • 访问Callee被调用端

开发步骤(创建Callee被调用端)

在Callee被调用端,需要实现指定方法的数据接收回调函数、数据的序列化及反序列化方法。在需要接收数据期间,通过on接口注册监听,无需接收数据时通过off接口解除监听。

  1. 配置UIAbility的启动模式。

例如将CalleeAbility配置为单实例模式singleton

  1. 导入UIAbility模块。
import UIAbility from '@ohos.app.ability.UIAbility';
  1. 定义约定的序列化数据。 调用端及被调用端发送接收的数据格式需协商一致,如下示例约定数据由number和string组成。
import type rpc from '@ohos.rpc';

class MyParcelable {
  num: number = 0;
  str: string = '';

  constructor(num: number, string: string) {
    this.num = num;
    this.str = string;
  };

  mySequenceable(num, string): void {
    this.num = num;
    this.str = string;
  };

  marshalling(messageSequence: rpc.MessageSequence): boolean {
    messageSequence.writeInt(this.num);
    messageSequence.writeString(this.str);
    return true;
  };

  unmarshalling(messageSequence: rpc.MessageSequence): boolean {
    this.num = messageSequence.readInt();
    this.str = messageSequence.readString();
    return true;
  };
};
  1. 实现Callee.on监听及Callee.off解除监听。

被调用端Callee的监听函数注册时机,取决于应用开发者。注册监听之前的数据不会被处理,取消监听之后的数据不会被处理。如下示例在UIAbility的onCreate注册'MSG_SEND_METHOD'监听,在onDestroy取消监听,收到序列化数据后作相应处理并返回,应用开发者根据实际需要做相应处理。具体示例代码如下:

import type AbilityConstant from '@ohos.app.ability.AbilityConstant';
import UIAbility from '@ohos.app.ability.UIAbility';
import type Want from '@ohos.app.ability.Want';
import hilog from '@ohos.hilog';
import Logger from '../utils/Logger';
import type rpc from '@ohos.rpc';
import type { Caller } from '@ohos.app.ability.UIAbility';

const MSG_SEND_METHOD: string = 'CallSendMsg';
const DOMAIN_NUMBER: number = 0xFF00;
const TAG: string = '[CalleeAbility]';

class MyParcelable {
  num: number = 0;
  str: string = '';

  constructor(num: number, string: string) {
    this.num = num;
    this.str = string;
  }

  mySequenceable(num, string): void {
    this.num = num;
    this.str = string;
  }

  marshalling(messageSequence: rpc.MessageSequence): boolean {
    messageSequence.writeInt(this.num);
    messageSequence.writeString(this.str);
    return true;
  };

  unmarshalling(messageSequence: rpc.MessageSequence): boolean {
    this.num = messageSequence.readInt();
    this.str = messageSequence.readString();
    return true;
  };
};

function sendMsgCallback(data: rpc.MessageSequence): rpc.Parcelable {
  hilog.info(DOMAIN_NUMBER, TAG, '%{public}s', 'CalleeSortFunc called');

  // 获取Caller发送的序列化数据
  let receivedData: MyParcelable = new MyParcelable(0, '');
  data.readParcelable(receivedData);
  hilog.info(DOMAIN_NUMBER, TAG, '%{public}s', `receiveData[${receivedData.num}, ${receivedData.str}]`);
  let num: number = receivedData.num;

  // 作相应处理
  // 返回序列化数据result给Caller
  return new MyParcelable(num + 1, `send ${receivedData.str} succeed`) as rpc.Parcelable;
}

export default class CalleeAbility extends UIAbility {
  caller: Caller | undefined;
  onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
    try {
      this.callee.on(MSG_SEND_METHOD, sendMsgCallback);
    } catch (error) {
      hilog.error(DOMAIN_NUMBER, TAG, '%{public}s', `Failed to register. Error is ${error}`);
    };
  }
  releaseCall(): void {
    try {
      if (this.caller) {
        this.caller.release();
        this.caller = undefined;
      }
      Logger.info('caller release succeed');
    } catch (error) {
      Logger.info(`caller release failed with ${error}`);
    };
  }
  onDestroy(): void {
    try {
      this.callee.off(MSG_SEND_METHOD);
      hilog.info(DOMAIN_NUMBER, TAG, '%{public}s', 'Callee OnDestroy');
      this.releaseCall();
    } catch (error) {
      hilog.error(DOMAIN_NUMBER, TAG, '%{public}s', `Failed to register. Error is ${error}`);
    };
  }
}

开发步骤(访问Callee被调用端)

  1. 导入UIAbility模块。
import UIAbility from '@ohos.app.ability.UIAbility';
  1. 获取Caller通信接口。 UIAbilityContext属性实现了startAbilityByCall方法,用于获取指定通用组件的Caller通信接口。如下示例通过this.context获取UIAbility实例的context属性,使用startAbilityByCall拉起Callee被调用端并获取Caller通信接口,注册Caller的onRelease监听。应用开发者根据实际需要做相应处理。
import { Caller } from '@ohos.app.ability.UIAbility';
import { BusinessError } from '@ohos.base';
import common from '@ohos.app.ability.common';
import promptAction from '@ohos.promptAction';
import hilog from '@ohos.hilog';
import Want from '@ohos.app.ability.Want';

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

@Entry
@Component
struct Page_UIAbilityComponentsInteractive {
  caller: Caller | undefined = undefined;

  // 注册caller的release监听
  private regOnRelease(caller: Caller): void {
    hilog.info(DOMAIN_NUMBER, TAG, `caller is ${caller}`);
    try {
      caller.on('release', (msg: string) => {
        hilog.info(DOMAIN_NUMBER, TAG, `caller onRelease is called ${msg}`);
      })
      hilog.info(DOMAIN_NUMBER, TAG, 'succeeded in registering on release.');
    } catch (err) {
      let code = (err as BusinessError).code;
      let message = (err as BusinessError).message;
      hilog.error(DOMAIN_NUMBER, TAG, `Failed to caller register on release. Code is ${code}, message is ${message}`);
    }
    ;
  }

  build() {
    Button()
      .onClick(() => {
        let context: common.UIAbilityContext = getContext(this) as common.UIAbilityContext; // UIAbilityContext
        let want: Want = {
          bundleName: 'com.samples.stagemodelabilityinteraction',
          abilityName: 'CalleeAbility',
          parameters: { // 自定义信息
            info: 'CallSendMsg'
          }
        };

        context.startAbilityByCall(want).then((caller: Caller) => {
          hilog.info(DOMAIN_NUMBER, TAG, `Succeeded in starting ability.Code is ${caller}`);
          if (caller === undefined) {
            hilog.info(DOMAIN_NUMBER, TAG, 'get caller failed');
            return;
          }
          else {
            hilog.info(DOMAIN_NUMBER, TAG, 'get caller success');
            this.regOnRelease(caller);
            promptAction.showToast({
              message: $r('app.string.CallerSuccess')
            });
          }
        }).catch((err: BusinessError) => {
          hilog.error(DOMAIN_NUMBER, TAG, `Failed to start ability. Code is ${err.code}, message is ${err.message}`);
        });
      })
  }
}

写在最后

  • 如果你觉得这篇内容对你还蛮有帮助,我想邀请你帮我三个小忙:
  • 点赞,转发,有你们的 『点赞和评论』,才是我创造的动力。
  • 关注小编,同时可以期待后续文章ing🚀,不定期分享原创知识。
  • 想要获取更多完整鸿蒙最新学习知识点,请移步前往小编:https://gitee.com/MNxiaona/733GH/blob/master/jianshu
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,402评论 6 499
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,377评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,483评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,165评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,176评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,146评论 1 297
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,032评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,896评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,311评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,536评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,696评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,413评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,008评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,659评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,815评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,698评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,592评论 2 353

推荐阅读更多精彩内容