OpenHarmony应用拉起技术详解:实现应用间无缝协作
1. 概述
在OpenHarmony系统中,应用拉起是指一个应用通过特定接口启动另一个应用或其特定功能组件的过程。这种机制使得应用间能够相互协作,为用户提供无缝的体验。OpenHarmony提供了多种应用拉起方式,包括基于startAbilityByType的垂类应用拉起、基于mailto协议的邮件应用拉起、基于startAbility的组件拉起以及页面拉起等。
应用拉起的技术架构基于Want机制,Want作为应用间信息传递的载体,包含了启动目标应用所需的所有信息,如bundleName、abilityName、parameters等。系统通过解析Want中的信息,定位并启动相应的应用组件。
2. 基于startAbilityByType的垂类应用拉起
2.1 导航类应用拉起
导航类应用拉起通过调用UIAbilityContext.startAbilityByType或UIExtensionContentSession.startAbilityByType接口实现,type字段设置为"navigation"。
参数说明
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| sceneType | number | 否 | 意图场景,表明本次请求对应的操作意图。默认为1,路线规划场景填1,导航场景填2,位置搜索场景填3 |
| endLocation | string | 否 | 终点地址,路线规划和导航场景必填 |
| startLocation | string | 否 | 起点地址,路线规划场景选填,导航场景不填 |
| keyword | string | 否 | 搜索关键词,位置搜索场景必填 |
拉起方实现方法
import { common } from '@kit.AbilityKit';
// 在页面组件中
let context = this.getUIContext().getHostContext() as common.UIAbilityContext;
let wantParam: Record<string, Object> = {
'sceneType': 1,
'endLocation': '北京市海淀区'
};
let abilityStartCallback: common.AbilityStartCallback = {
onError: (code: number, name: string, message: string) => {
console.log(`onError code ${code} name: ${name} message: ${message}`);
},
onResult: (result) => {
console.log(`onResult result: ${JSON.stringify(result)}`);
}
}
context.startAbilityByType("navigation", wantParam, abilityStartCallback, (err) => {
if (err) {
console.error(`startAbilityByType fail, err: ${JSON.stringify(err)}`);
} else {
console.log(`success`);
}
});
目标方配置与实现
在module.json5中配置uris:
{
"abilities": [
{
"skills": [
{
"uris": [
{
"scheme": "navigation",
"host": "routePlan",
"path": "",
"linkFeature": "RoutePlan"
},
{
"scheme": "navigation",
"host": "navigation",
"path": "",
"linkFeature": "Navigation"
},
{
"scheme": "navigation",
"host": "locationSearch",
"path": "",
"linkFeature": "LocationSearch"
}
]
}
]
}
]
}
在UIAbility中解析参数:
private parseWant(want: Want): void {
this.uri = want.uri as string | undefined;
this.endLocation = want.parameters?.endLocation as string | undefined;
this.startLocation = want.parameters?.startLocation as string | undefined;
this.keyword = want.parameters?.keyword as string | undefined;
}
2.2 邮件类应用拉起
邮件类应用拉起通过调用startAbilityByType接口实现,type字段设置为"email"。
参数说明
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| sceneType | number | 否 | 意图场景,表明本次请求对应的操作意图。默认为1,撰写邮件场景填1或不填 |
| string | 是 | 收件人邮箱地址,多个邮箱用英文逗号分隔 | |
| cc | string | 否 | 抄送人邮箱地址,多个邮箱用英文逗号分隔 |
| bcc | string | 否 | 密送人邮箱地址,多个邮箱用英文逗号分隔 |
| subject | string | 否 | 邮件主题 |
| body | string | 否 | 邮件正文 |
拉起方实现方法
import { common } from '@kit.AbilityKit';
// 在页面组件中
let context = this.getUIContext().getHostContext() as common.UIAbilityContext;
let wantParam: Record<string, Object> = {
'sceneType': 1,
'email': 'example@example.com',
'subject': '邮件主题',
'body': '邮件正文内容'
};
let abilityStartCallback: common.AbilityStartCallback = {
onError: (code: number, name: string, message: string) => {
console.log(`onError code ${code} name: ${name} message: ${message}`);
},
onResult: (result) => {
console.log(`onResult result: ${JSON.stringify(result)}`);
}
}
context.startAbilityByType("email", wantParam, abilityStartCallback, (err) => {
if (err) {
console.error(`startAbilityByType fail, err: ${JSON.stringify(err)}`);
} else {
console.log(`success`);
}
});
目标方配置与实现
在module.json5中配置uris:
{
"abilities": [
{
"skills": [
{
"uris": [
{
"scheme": "email",
"host": "composeMail",
"path": "",
"linkFeature": "ComposeMail"
}
]
}
]
}
]
}
在UIAbility中解析参数:
private parseWant(want: Want): void {
this.uri = want.uri as string | undefined;
this.email = want.parameters?.email as string | undefined;
this.cc = want.parameters?.cc as string | undefined;
this.bcc = want.parameters?.bcc as string | undefined;
this.subject = want.parameters?.subject as string | undefined;
this.body = want.parameters?.body as string | undefined;
}
2.3 金融类应用拉起
金融类应用拉起通过调用startAbilityByType接口实现,type字段设置为"finance"。
参数说明
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| sceneType | number | 否 | 意图场景,表明本次请求对应的操作意图。默认为1,转账汇款场景填1或不填,信用卡还款场景填2 |
| bankCardNo | string | 是 | 银行卡号 |
拉起方实现方法
import { common } from '@kit.AbilityKit';
// 在页面组件中
let context = this.getUIContext().getHostContext() as common.UIAbilityContext;
let wantParam: Record<string, Object> = {
'sceneType': 1,
'bankCardNo': '6222021234567890123'
};
let abilityStartCallback: common.AbilityStartCallback = {
onError: (code: number, name: string, message: string) => {
console.log(`onError code ${code} name: ${name} message: ${message}`);
},
onResult: (result) => {
console.log(`onResult result: ${JSON.stringify(result)}`);
}
}
context.startAbilityByType("finance", wantParam, abilityStartCallback, (err) => {
if (err) {
console.error(`startAbilityByType fail, err: ${JSON.stringify(err)}`);
} else {
console.log(`success`);
}
});
目标方配置与实现
在module.json5中配置uris:
{
"abilities": [
{
"skills": [
{
"uris": [
{
"scheme": "finance",
"host": "transfer",
"path": "",
"linkFeature": "Transfer"
},
{
"scheme": "finance",
"host": "creditCardRepayment",
"path": "",
"linkFeature": "CreditCardRepayment"
}
]
}
]
}
]
}
在UIAbility中解析参数:
private parseWant(want: Want): void {
this.uri = want.uri as string | undefined;
this.bankCardNo = want.parameters?.bankCardNo as string | undefined;
}
2.4 航班类应用拉起
航班类应用拉起通过调用startAbilityByType接口实现,type字段设置为"flight"。
参数说明
按航班号查询场景:
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| sceneType | number | 否 | 意图场景,表明本次请求对应的操作意图。默认为1,按航班号查询场景填1或不填 |
| flightNo | string | 是 | 航班号,航司二位代码+数字 |
| departureDate | string | 否 | 航班出发时间:YYYY-MM-DD |
按起降地查询场景:
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| sceneType | number | 是 | 意图场景,表明本次请求对应的操作意图。按起降地查询场景填2 |
| originLocation | string | 是 | 出发地 |
| destinationLocation | string | 是 | 目的地 |
| departureDate | string | 否 | 航班出发时间:YYYY-MM-DD |
拉起方实现方法
import { common } from '@kit.AbilityKit';
// 在页面组件中
let context = this.getUIContext().getHostContext() as common.UIAbilityContext;
let wantParam: Record<string, Object> = {
'sceneType': 1,
'flightNo': 'ZH1509',
'departureDate': '2024-10-01'
};
let abilityStartCallback: common.AbilityStartCallback = {
onError: (code: number, name: string, message: string) => {
console.log(`onError code ${code} name: ${name} message: ${message}`);
},
onResult: (result) => {
console.log(`onResult result: ${JSON.stringify(result)}`);
}
}
context.startAbilityByType("flight", wantParam, abilityStartCallback, (err) => {
if (err) {
console.error(`startAbilityByType fail, err: ${JSON.stringify(err)}`);
} else {
console.log(`success`);
}
});
目标方配置与实现
在module.json5中配置uris:
{
"abilities": [
{
"skills": [
{
"uris": [
{
"scheme": "flight",
"host": "queryByFlightNo",
"path": "",
"linkFeature": "QueryByFlightNo"
},
{
"scheme": "flight",
"host": "queryByLocation",
"path": "",
"linkFeature": "QueryByLocation"
}
]
}
]
}
]
}
在UIAbility中解析参数:
private parseWant(want: Want): void {
this.uri = want.uri as string | undefined;
this.flightNo = want.parameters?.flightNo as string | undefined;
this.departureDate = want.parameters?.departureDate as string | undefined;
this.originLocation = want.parameters?.originLocation as string | undefined;
this.destinationLocation = want.parameters?.destinationLocation as string | undefined;
}
2.5 快递类应用拉起
快递类应用拉起通过调用startAbilityByType接口实现,type字段设置为"express"。
参数说明
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| sceneType | number | 否 | 意图场景,表明本次请求对应的操作意图。默认为1,查询快递填场景填1或不填 |
| expressNo | string | 是 | 快递单号 |
拉起方实现方法
import { common } from '@kit.AbilityKit';
// 在页面组件中
let context = this.getUIContext().getHostContext() as common.UIAbilityContext;
let wantParam: Record<string, Object> = {
'sceneType': 1,
'expressNo': 'SF123456'
};
let abilityStartCallback: common.AbilityStartCallback = {
onError: (code: number, name: string, message: string) => {
console.log(`onError code ${code} name: ${name} message: ${message}`);
},
onResult: (result) => {
console.log(`onResult result: ${JSON.stringify(result)}`);
}
}
context.startAbilityByType("express", wantParam, abilityStartCallback, (err) => {
if (err) {
console.error(`startAbilityByType fail, err: ${JSON.stringify(err)}`);
} else {
console.log(`success`);
}
});
目标方配置与实现
在module.json5中配置uris:
{
"abilities": [
{
"skills": [
{
"uris": [
{
"scheme": "express",
"host": "queryExpress",
"path": "",
"linkFeature": "QueryExpress"
}
]
}
]
}
]
}
在UIAbility中解析参数:
private parseWant(want: Want): void {
this.uri = want.uri as string | undefined;
this.expressNo = want.parameters?.expressNo as string | undefined;
}
3. 基于mailto协议的邮件应用拉起
mailto协议格式与参数说明
mailto协议标准格式如下:
mailto:someone@example.com?key1=value1&key2=value2
mailto::mailto scheme,必填。someone@example.com:收件人地址,选填。如果存在多个地址,用英文逗号分隔。?:邮件头声明开始符号。如果带邮件头参数,则必填。key-value:邮件头参数,详细参数见下表。
| 邮件头 | 含义 | 数据类型 | 是否必填 |
|---|---|---|---|
| subject | 邮件主题 | string | 否 |
| body | 邮件正文 | string | 否 |
| cc | 抄送人,多个用逗号分隔 | string | 否 |
| bcc | 密送人,多个用逗号分隔 | string | 否 |
特殊字符处理方法
如果邮件头参数值中存在特殊字符,如@、?、=、&等符号,可能导致配置不生效。建议将特殊字符替换为ASCII码,并在ASCII码前加百分号%。
常用符号替换为ASCII码的对照表如下:
| 特殊符号 | : | @ | ? | = | & | # | $ |
|---|---|---|---|---|---|---|---|
| 替换编码 | %3A | %40 | %3F | %3D | %26 | %23 | %24 |
从网页拉起邮件应用
网页中的超链接需要满足mailto协议。示例如下:
<a href="mailto:support@example.com?subject=Product Inquiry&body=I am interested in...">联系我们</a>
从应用拉起邮件应用
保证mailto字符串传入uri参数即可,在应用中page页面可通过 getHostContext() 获取context,在ability中可通过this.context获取context。
import { common } from '@kit.AbilityKit';
@Entry
@Component
struct Index {
build() {
Column() {
Button('反馈')
.onClick(() => {
let ctx = this.getUIContext().getHostContext() as common.UIAbilityContext;
ctx.startAbility({
action: 'ohos.want.action.sendToData',
uri: 'mailto:feedback@example.com?subject=App Feedback&body=Please describe your feedback here...'
})
})
}
}
}
目标方配置与解析方法
为了能够支持被其他应用通过mailto协议拉起,目标应用需要在module.json5配置文件中声明mailto。
{
"module": {
// ...
"abilities": [
{
// ...
"skills": [
{
"actions": [
'ohos.want.action.sendToData'
],
"uris": [
{
"scheme": "mailto",
// linkFeature 用于适配垂类面板拉起
"linkFeature": 'ComposeMail'
}
]
}
]
}
]
}
}
目标应用在代码中取出uri参数进行解析:
export default class EntryAbility extends UIAbility {
onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
// 应用冷启动生命周期回调,其他业务处理...
parseMailto(want);
}
onNewWant(want: Want, launchParam: AbilityConstant.LaunchParam): void {
// 应用热启动生命周期回调,其他业务处理...
parseMailto(want);
}
public parseMailto(want: Want) {
const uri = want?.uri;
if (!uri || uri.length <= 0) {
return;
}
// 开始解析 mailto...
}
}
4. 基于startAbility的组件拉起
4.1 PageAbility启动
本地PageAbility启动
PageAbility相关的能力通过featureAbility提供,启动本地Ability通过featureAbility中的startAbility接口实现。
import featureAbility from '@ohos.ability.featureAbility';
import Want from '@ohos.app.ability.Want';
import hilog from '@ohos.hilog';
const TAG: string = 'PagePageAbilityFirst';
const domain: number = 0xFF00;
(async (): Promise<void> => {
try {
hilog.info(domain, TAG, 'Begin to start ability');
let want: Want = {
bundleName: 'com.samples.famodelabilitydevelop',
moduleName: 'entry',
abilityName: 'com.samples.famodelabilitydevelop.PageAbilitySingleton'
};
await featureAbility.startAbility({ want: want });
hilog.info(domain, TAG, `Start ability succeed`);
}
catch (error) {
hilog.error(domain, TAG, 'Start ability failed with ' + error);
}
})()
远程PageAbility启动(仅对系统应用开放)
启动远程PageAbility同样通过featureAbility中的startAbility接口实现。
除引入’@ohos.ability.featureAbility’外,还需引入’@ohos.distributedHardware.deviceManager’,通过DeviceManager的getTrustedDeviceListSync接口获取远端的deviceId,写入want中,用于启动远程PageAbility。
由于当前DeviceManager的getTrustedDeviceListSync接口仅对系统应用开放,故现阶段非系统应用无法获取其他设备信息,无远程启动设备选择入口,远程启动Ability开发。
停止PageAbility
停止PageAbility通过featureAbility中的terminateSelf接口实现。
import featureAbility from '@ohos.ability.featureAbility';
import hilog from '@ohos.hilog';
const TAG: string = 'PagePageAbilityFirst';
const domain: number = 0xFF00;
(async (): Promise<void> => {
try {
hilog.info(domain, TAG, 'Begin to terminateSelf');
await featureAbility.terminateSelf();
hilog.info(domain, TAG, 'terminateSelf succeed');
} catch (error) {
hilog.error(domain, TAG, 'terminateSelf failed with ' + error);
}
})()
4.2 UIAbility启动
从FA模型启动UIAbility
FA模型三种组件启动Stage模型UIAbility的方法:PageAbility通过startAbility/startAbilityForResult接口启动,ServiceAbility/DataAbility通过particleAbility.startAbility接口启动。
// PageAbility启动UIAbility
import featureAbility from '@ohos.ability.featureAbility';
let want = {
bundleName: 'com.example.myapplication',
abilityName: 'com.example.myapplication.MainAbility'
};
featureAbility.startAbility({ want }).then((data) => {
console.log('startAbility succeed');
}).catch((error) => {
console.error('startAbility failed: ' + error);
});
从Stage模型启动PageAbility
Stage模型中UIAbility启动FA模型PageAbility的startAbility接口使用示例,需指定bundleName和abilityName。
import { common } from '@kit.AbilityKit';
// 在UIAbility中
let context = this.context as common.UIAbilityContext;
let want = {
bundleName: 'com.example.faapplication',
abilityName: 'com.example.faapplication.PageAbility'
};
context.startAbility(want).then(() => {
console.log('startAbility succeed');
}).catch((error) => {
console.error('startAbility failed: ' + error);
});
4.3 ServiceAbility启动
在PageAbility中使用featureAbility.startAbility接口启动ServiceAbility。
import featureAbility from '@ohos.ability.featureAbility';
let want = {
bundleName: 'com.example.application',
abilityName: 'com.example.application.ServiceAbility'
};
featureAbility.startAbility({ want }).then((data) => {
console.log('startAbility succeed');
}).catch((error) => {
console.error('startAbility failed: ' + error);
});
ServiceAbility未运行时,会先调用onStart再调用onCommand;ServiceAbility已运行时,直接调用onCommand。
4.4 DataAbility启动
启动DataAbility会获取DataAbilityHelper工具接口类对象,使用featureAbility.acquireDataAbilityHelper(uri)方法获取该对象,其中uri格式为"dataability:///包名.DataAbility"。
import featureAbility from '@ohos.ability.featureAbility';
let uri = "dataability:///com.example.application.DataAbility";
let dataAbilityHelper = featureAbility.acquireDataAbilityHelper(uri);
5. 页面拉起技术
5.1 单例模式下的页面拉起
通过startAbility和onNewWant回调传递parameters参数(如{page: ‘pages/second’})拉起指定页面。
调用方构造want:
let want = {
bundleName: 'com.example.application',
abilityName: 'com.example.application.MainAbility',
parameters: {
page: 'pages/second'
}
};
context.startAbility(want);
目标端在onNewWant中通过GlobalContext存储want:
onNewWant(want: Want, launchParam: AbilityConstant.LaunchParam): void {
// 存储want到全局上下文
GlobalContext.getContext().setObject('want', want);
}
页面组件在onPageShow中解析参数并路由:
onPageShow(): void {
let want = GlobalContext.getContext().getObject('want') as Want;
if (want?.parameters?.page) {
router.pushUrl({ url: want.parameters.page as string });
}
}
5.2 多实例/首次启动单例模式下的页面拉起
在onCreate中获取want并调用router.pushUrl实现指定页面拉起。
onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
if (want?.parameters?.page) {
// 存储页面路径
GlobalContext.getContext().setObject('pagePath', want.parameters.page);
}
}
onWindowStageCreate(windowStage: window.WindowStage): void {
let pagePath = GlobalContext.getContext().getObject('pagePath') as string;
if (pagePath) {
windowStage.loadContent(pagePath);
} else {
// 默认加载首页
windowStage.loadContent('pages/Index');
}
}
6. 跨模型应用拉起
FA模型与Stage模型互启动
FA模型启动Stage模型UIAbility:
// FA模型中
import featureAbility from '@ohos.ability.featureAbility';
let want = {
bundleName: 'com.example.stageapp',
abilityName: 'com.example.stageapp.MainAbility'
};
featureAbility.startAbility({ want });
Stage模型启动FA模型PageAbility:
// Stage模型中
import { common } from '@kit.AbilityKit';
let context = this.context as common.UIAbilityContext;
let want = {
bundleName: 'com.example.faapp',
abilityName: 'com.example.faapp.PageAbility'
};
context.startAbility(want);
不同模型间的参数传递
不同模型间参数传递主要通过Want对象的parameters字段实现,确保参数类型和格式一致。
7. 配置与权限管理
module.json5配置要点
在module.json5中配置skills和uris是应用拉起的关键,正确配置这些信息能够确保应用能够被正确识别和启动。
linkFeature配置说明
linkFeature属性用于声明应用支持的特性功能,系统可以根据这个属性从设备已安装应用中找到支持该特性的应用。
权限申请与处理
某些应用拉起场景可能需要特殊权限,如远程启动需要ohos.permission.DISTRIBUTED_DATASYNC权限。
import abilityAccessCtrl from "@ohos.abilityAccessCtrl";
import featureAbility from '@ohos.ability.featureAbility';
import bundle from '@ohos.bundle.bundleManager';
let array: Array<string> = ['ohos.permission.DISTRIBUTED_DATASYNC'];
let bundleFlag = 0;
let tokenID: number | undefined = undefined;
let userID = 100;
let appInfo = await bundle.getApplicationInfo('com.example.application', bundleFlag, userID);
tokenID = appInfo.accessTokenId;
let atManager = abilityAccessCtrl.createAtManager();
let requestPermissions: Array<string> = [];
for (let i = 0; i < array.length; i++) {
let result = await atManager.verifyAccessToken(tokenID, array[i]);
if (result != abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED) {
requestPermissions.push(array[i]);
}
}
if (requestPermissions.length > 0) {
let context = featureAbility.getContext();
context.requestPermissionsFromUser(requestPermissions, 1, (error, data) => {
console.log('error:' + error.message + ',data:' + JSON.stringify(data));
});
}
8. 开发实践与注意事项
常见问题与解决方案
-
应用拉起失败
- 检查bundleName和abilityName是否正确
- 确认目标应用已安装
- 验证module.json5配置是否正确
-
参数传递失败
- 检查parameters字段中的参数名是否正确
- 确认参数类型是否匹配
- 验证目标端是否正确解析参数
-
权限不足
- 检查是否已申请所需权限
- 确认用户是否已授权
- 验证权限等级是否满足要求
错误处理方法
在应用拉起过程中,应该对可能出现的错误进行处理,提供友好的用户体验:
context.startAbility(want).then(() => {
console.log('startAbility succeed');
}).catch((error) => {
console.error('startAbility failed: ' + error);
// 提供友好的错误提示
promptAction.showToast({
message: '启动应用失败,请稍后重试'
});
});
更多推荐


所有评论(0)