概要
HarmonyOSでは、異なるデバイス間でアプリケーション連携を実現する様々な手法が提供されています。本稿では、UIAbilityの起動・ServiceExtensionAbilityへの接続・Callインターフェースを活用した3つの主要な連携方式について詳しく解説します。
1. デバイス間でのUIAbility起動とデータ返却
あるデバイスから別のデバイスのUIAbilityを起動し、処理結果を返す実装方法について説明します。
起動側の実装
import { BusinessError } from '@ohos.base';
import common from '@ohos.app.ability.common';
import Want from '@ohos.app.ability.Want';
@Entry
@Component
struct PageName {
private context = getContext(this) as common.UIAbilityContext;
build() {
Button('startAbilityForResult')
.onClick(() => {
let want: Want = {
deviceId: this.getRemoteDeviceId(),
bundleName: 'com.example.myapplication',
abilityName: 'FuncAbility',
moduleName: 'module1'
};
this.context.startAbilityForResult(want).then((data) => {
if (data?.resultCode === 1001) {
let info = data.want?.parameters?.info;
}
}).catch((error: BusinessError) => {
console.info("startAbilityForResult err: " + JSON.stringify(error));
});
});
Button('terminateSelfWithResult')
.onClick(() => {
const RESULT_CODE: number = 1001;
this.context.terminateSelfWithResult(
{
resultCode: RESULT_CODE,
want: {
bundleName: 'com.example.myapplication',
abilityName: 'FuncAbility',
moduleName: 'module1'
}
},
(err: BusinessError) => {
console.info("terminateSelfWithResult err: " + JSON.stringify(err));
}
);
});
}
private getRemoteDeviceId(): string {
// 実際の実装では、デバイスDiscovery APIを使用
return '';
}
}
対象端での実装
import { BusinessError } from '@ohos.base';
import common from '@ohos.app.ability.common';
@Entry
@Component
struct TargetPage {
private context = getContext(this) as common.UIAbilityContext;
build() {
Button('terminateSelfWithResult')
.onClick(() => {
const RESULT_CODE: number = 1001;
this.context.terminateSelfWithResult(
{
resultCode: RESULT_CODE,
want: {
bundleName: 'com.example.myapplication',
abilityName: 'FuncAbility',
moduleName: 'module1'
}
},
(err: BusinessError) => {
console.info("terminateSelfWithResult err: " + JSON.stringify(err));
}
);
});
}
}
2. ServiceExtensionAbilityへの接続による連携
システムアプリケーションでは、connectServiceExtensionAbility()を使用してリモートデバイスのサービスに接続できます。例えば、スマートTVをディスプレイとして使い、タブレットをリモコンとして利用する分散型ゲームシナリオ 등에 활용됩니다。
API解説
| API名 | 説明 |
|---|---|
| connectServiceExtensionAbility(want: Want, options: ConnectOptions): number | ServiceExtensionAbilityへの接続を確立 |
| disconnectServiceExtensionAbility(connection: number, callback: AsyncCallback<void>): void | 接続解除(コールバック形式) |
| disconnectServiceExtensionAbility(connection: number): Promise<void> | 接続解除(Promise形式) |
実装手順
- ohos.permission.DISTRIBUTED_DATASYNC権限を申請
- 初回起動時にユーザーへの許可を求め
- バックグラウンドサービスを実装
- サービスへの接続を実装
接続の実装
import rpc from '@ohos.rpc';
import Want from '@ohos.app.ability.Want';
import common from '@ohos.app.ability.common';
import { BusinessError } from '@ohos.base';
@Entry
@Component
struct PageName {
private context = getContext(this) as common.UIAbilityContext;
private connectionId: number = -1;
build() {
Button('connectServiceExtensionAbility')
.onClick(() => {
const REQUEST_CODE = 99;
let want: Want = {
deviceId: this.getRemoteDeviceId(),
bundleName: 'com.example.myapplication',
abilityName: 'ServiceExtAbility'
};
this.connectionId = this.context.connectServiceExtensionAbility(want, {
onConnect: (elementName, remote) => {
console.info('onConnect callback');
if (remote === null) {
console.info('onConnect remote is null');
return;
}
let option = new rpc.MessageOption();
let requestData = new rpc.MessageSequence();
let responseData = new rpc.MessageSequence();
requestData.writeInt(1);
requestData.writeInt(99);
remote.sendMessageRequest(REQUEST_CODE, requestData, responseData, option)
.then((ret: rpc.RequestResult) => {
let msg = responseData.readInt();
console.info(`sendRequest ret:${ret} msg:${msg}`);
})
.catch((error: BusinessError) => {
console.info('sendRequest failed');
});
},
onDisconnect: (elementName) => {
console.info('onDisconnect callback');
},
onFailed: (code) => {
console.info('onFailed callback');
}
});
});
Button('disconnectServiceExtensionAbility')
.onClick(() => {
if (this.connectionId !== -1) {
this.context.disconnectServiceExtensionAbility(this.connectionId)
.then(() => {
console.info('disconnectServiceExtensionAbility success');
this.connectionId = -1;
})
.catch((error: BusinessError) => {
console.error('disconnectServiceExtensionAbility failed');
});
}
});
}
private getRemoteDeviceId(): string {
return '';
}
}
3. デバイス間Call呼び出しによる連携
Callインターフェースを使用した連携方式について説明します。デバイス内のCall呼び出しと同様の原理で動作します。
API解説
| API名 | 説明 |
|---|---|
| startAbilityByCall(want: Want): Promise<Caller> | UIAbilityを起動し、Caller通信インターフェースを取得 |
| on(method: string, callback: CalleeCallBack): void | Calleeにメソッドコールバックを登録 |
| off(method: string): void | Calleeのメソッドコールバックを解除 |
| call(method: string, data: rpc.Parcelable): Promise<void> | Calleeにデータを送信 |
| callWithResult(method: string, data: rpc.Parcelable): Promise<rpc.MessageSequence> | Calleeにデータを送信し、結果を受け取る |
| release(): void | Callerインターフェースを解放 |
実装手順
- 権限申請(ohos.permission.DISTRIBUTED_DATASYNC)
- ユーザーへの許可要求
- 被呼び出し側UIAbilityの実装
- UIAbility起動モードの設定(singleton)
被呼び出し側の実装
import rpc from '@ohos.rpc';
import Want from '@ohos.app.ability.Want';
import UIAbility from '@ohos.app.ability.UIAbility';
import AbilityConstant from '@ohos.app.ability.AbilityConstant';
const TAG: string = '[CalleeAbility]';
const MSG_SEND_METHOD: string = 'CallSendMsg';
class MyParcelable {
num: number = 0;
str: string = "";
constructor(num: number, str: string) {
this.num = num;
this.str = str;
}
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): MyParcelable {
console.info('CalleeSortFunc called');
let receivedData: MyParcelable = new MyParcelable(0, '');
data.readParcelable(receivedData);
console.info(`receiveData[${receivedData.num}, ${receivedData.str}]`);
return new MyParcelable(Number(receivedData.num) + 1, `send ${receivedData.str} succeed`);
}
export default class CalleeAbility extends UIAbility {
onCreate(want: Want, launchParam: AbilityConstant.LaunchParam) {
try {
this.callee.on(MSG_SEND_METHOD, sendMsgCallback);
} catch (error) {
console.info(`${MSG_SEND_METHOD} register failed with error ${JSON.stringify(error)}`);
}
}
onDestroy() {
try {
this.callee.off(MSG_SEND_METHOD);
} catch (error) {
console.error(TAG, `${MSG_SEND_METHOD} unregister failed with error ${JSON.stringify(error)}`);
}
}
}
呼び出し側の実装
import UIAbility, { Caller } from '@ohos.app.ability.UIAbility';
import { BusinessError } from '@ohos.base';
const MSG_SEND_METHOD: string = 'CallSendMsg';
export default class EntryAbility extends UIAbility {
private remoteCaller: Caller | undefined;
async onButtonGetRemoteCaller() {
let context = this.context;
context.startAbilityByCall({
deviceId: this.getRemoteDeviceId(),
bundleName: 'com.samples.CallApplication',
abilityName: 'CalleeAbility'
}).then((data) => {
if (data != null) {
this.remoteCaller = data;
console.info('get remote caller success');
this.remoteCaller.onRelease((msg) => {
console.info(`remote caller onRelease is called ${msg}`);
});
try {
this.remoteCaller.onRemoteStateChange((str) => {
console.info('Remote state changed ' + str);
});
} catch (error) {
console.info(`Caller.onRemoteStateChange catch error`);
}
}
}).catch((error: BusinessError) => {
console.error(`get remote caller failed with ${error}`);
});
}
async onButtonCall() {
try {
let msg: MyParcelable = new MyParcelable(1, 'origin_Msg');
if (this.remoteCaller) {
await this.remoteCaller.call(MSG_SEND_METHOD, msg);
}
} catch (error) {
console.info(`caller call failed with ${error}`);
}
}
async onButtonCallWithResult(originMsg: string) {
try {
let msg: MyParcelable = new MyParcelable(1, originMsg);
if (this.remoteCaller) {
const data = await this.remoteCaller.callWithResult(MSG_SEND_METHOD, msg);
console.info('caller callWithResult succeed');
let result: MyParcelable = new MyParcelable(0, '');
data.readParcelable(result);
console.info(`caller result is [${result.num}, ${result.str}]`);
}
} catch (error) {
console.info(`caller callWithResult failed with ${error}`);
}
}
releaseCall() {
try {
if (this.remoteCaller) {
this.remoteCaller.release();
this.remoteCaller = undefined;
}
console.info('caller release succeed');
} catch (error) {
console.info(`caller release failed with ${error}`);
}
}
private getRemoteDeviceId(): string {
return '';
}
}
モジュール設定
// module.json5
{
"abilities": [{
"name": ".CalleeAbility",
"srcEntry": "./ets/CalleeAbility/CalleeAbility.ts",
"launchType": "singleton",
"description": "$string:CalleeAbility_desc",
"icon": "$media:icon",
"label": "$string:CalleeAbility_label",
"exported": true
}]
}
まとめ
HarmonyOSでは、UIAbilityの起動・ServiceExtensionAbilityへの接続・Call呼び出しという3つの主要な方式により、異なるデバイス間でのシームレスな連携を実現できます。アプリケーションの要件に応じて、適切な方式を選択してください。