ServiceExtensionAbility是Service类型的ExtensionAbility组件,可以提供接口给其他应用或系统服务使用,通过rpc进行通信。本文参考官方文档,介绍了如何进行服务端开发以及客户端如何进行连接通信。

一、服务端开发

1.对外暴露接口定义

(1)新建项目,在ets下新建目录IdlServiceExt
(2)在IdlServiceExt下新建IIdlTestService.idl文件,需要向外部提供可调用的接口,定义在该文件中,用IDL工具生成三个ts文件,放入到该目录下。IDL工具使用教程:https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/IDL/idl-guidelines.md

-- 进入IDL可执行工具目录下,打开命令窗口,执行命令:
.\idl -gen-ts -d IIdlTestServiceTs -c IIdlTestServiceTs/IIdlTestService.idl

├── ets
│ ├── IdlServiceExt
│ │   ├── i_idl_test_service.ts      # 生成文件
│ │   ├── idl_test_service_proxy.ts  # 生成文件
│ │   ├── idl_test_service_stub.ts   # 生成文件
│ │   ├── idl_test_service_impl.ts   # 开发者自定义文件,对idl接口的具体实现
│ └
└

2.接口实现

在IdlServiceExt文件夹下创建一个名为idl_test_service_impl.ts的文件,作为idl接口的实现,在对应的方法中可对客户端访问身份信息进行校验,编写业务逻辑

import IdlServiceExtStub from './idl_test_service_stub';
import hilog from '@ohos.hilog';
import type { getDataCallback } from './i_idl_test_service';
import rpc from '@ohos.rpc';
import bundleManager from '@ohos.bundle.bundleManager';
import Base from '@ohos.base';

const ERR_OK = 0;
const TAG: string = "[IdlTestServiceImpl]";
const DOMAIN_NUMBER: number = 0xFF00;

// 开发者需要在这个类型里对接口进行实现
export default class ServiceExtImpl extends IdlServiceExtStub {

  constructor(des) {
    super(des)
  }

  getData(data: number, callback: getDataCallback): void {
    //身份校验
    let callerUid = rpc.IPCSkeleton.getCallingUid();
    bundleManager.getBundleNameByUid(callerUid).then((callerBundleName) => {
      hilog.info(DOMAIN_NUMBER, TAG, 'getBundleNameByUid: ' + callerBundleName);
      // 对客户端包名进行识别
      if (callerBundleName !== 'com.oh.clientest') { // 识别不通过
        hilog.info(DOMAIN_NUMBER, TAG, 'The caller bundle is not in trustlist, reject');
        return;
      }
      // 识别通过,执行正常业务逻辑,开发者自行实现业务逻辑,这里将客户端传过来的数据加1再返回
      hilog.info(DOMAIN_NUMBER, TAG, `processData: ${data}`);
      callback(ERR_OK, data + 1);
    }).catch((err: Base.BusinessError) => {
      hilog.info(DOMAIN_NUMBER, TAG, 'getBundleNameByUid failed: ' + err.message);
    });
  }
}

3.创建ServiceExtensionAbility

在DevEco Studio工程中手动新建一个ServiceExtensionAbility,具体步骤如下:

(1)在工程Module对应的ets目录下,右键选择“New > Directory”,新建一个目录并命名为ServiceExtAbility。

(2)在ServiceExtAbility目录,右键选择“New > ArkTS File”,新建一个文件并命名为ServiceExtAbility.ets。

(3)在ServiceExtAbility.ets文件中,增加导入ServiceExtensionAbility的依赖包,自定义类继承ServiceExtensionAbility并实现生命周期回调,在onConnect生命周期回调里,需要将之前定义的ServiceExtImpl对象返回。


import ServiceExtensionAbility from '@ohos.app.ability.ServiceExtensionAbility';
import Want from '@ohos.application.Want';
import rpc from '@ohos.rpc';
import hilog from '@ohos.hilog';
import ServiceExtImpl from '../IdlServiceExt/idl_test_service_impl';
const TAG: string = '[ServiceExtAbility]';
const DOMAIN_NUMBER: number = 0xFF00;

export default class ServiceExtAbility extends ServiceExtensionAbility {
  serviceExtImpl: ServiceExtImpl = new ServiceExtImpl('ExtImpl');

  onCreate(want: Want): void {
    hilog.info(DOMAIN_NUMBER, TAG, `onCreate, want: ${want.abilityName}`);
  };

  onRequest(want: Want, startId: number): void {
    hilog.info(DOMAIN_NUMBER, TAG, `onRequest, want: ${want.abilityName}`);
  };

  onConnect(want: Want): rpc.RemoteObject {
    hilog.info(DOMAIN_NUMBER, TAG, `onConnect, want: ${want.abilityName}`);
    // 返回ServiceExtImpl对象,客户端获取后便可以与ServiceExtensionAbility进行通信
    return this.serviceExtImpl as rpc.RemoteObject;
  };

  onDisconnect(want: Want): void {
    hilog.info(DOMAIN_NUMBER, TAG, `onDisconnect, want: ${want.abilityName}`);
  };

  onDestroy(): void {
    hilog.info(DOMAIN_NUMBER, TAG, 'onDestroy');
  };
};

(4)在工程Module对应的module.json5配置文件中注册ServiceExtensionAbility,type标签需要设置为“service”,srcEntry标签表示当前ExtensionAbility组件所对应的代码路径。

{
  "module": {
    ...
    "extensionAbilities": [
      {
        "name": "ServiceExtAbility",
        "icon": "$media:icon",
        "description": "service",
        "type": "service",
        "exported": true,
        "srcEntry": "./ets/ServiceExtAbility/ServiceExtAbility.ets"
      }
    ]
  }
}

4.配置应用特权(allowAppUsePrivilegeExtension)

(1)获取签名指纹
应用签名和证书指纹获取参考文档:https://laval.csdn.net/6544b3a934bf9e25c799c326.html
(2)获取设备的特权管控白名单文件install_list_capability.json

-- 连接设备
hdc shell

-- 执行如下命令查看设备的特权管控白名单文件install_list_capability.json的位置
find /system -name install_list_capability.json

-- 执行如下命令拉取install_list_capability.json,其中install_list_capability.json文件位置需根据实际情况进行调整
hdc shell mount -o rw,remount /
hdc file recv /system/etc/app/install_list_capability.json D:\install_list_capability.json

-- 修改install_list_capability.json文件,配置签名指纹app_signature(步骤一中的签名指纹)和应用包名
{
    "bundleName": "com.oh.myapplication",
    "app_signature" : ["CA4B96B85448F3C4D204DD7EC8B00FB565C2514E8CE13E339FC7524AF5158F48"],
    "allowAppDesktopIconHide": false,
    "keepAlive": true,
    "allowAppUsePrivilegeExtension": true
}

-- 将修改后的install_list_capability.json文件重新推到设备上,并重启设备
hdc shell mount -o rw,remount / 
hdc file send D:\install_list_capability.json /system/etc/app/install_list_capability.json
hdc shell chmod 777 /system/etc/app/install_list_capability.json 
hdc shell reboot

二、客户端开发

1.导入服务端暴露的文件

新建项目,创建文件夹IdlServiceExt,复制服务端对外暴露的文件到文件夹中

├── ets
│ ├── IdlServiceExt
│ │   ├── i_idl_test_service.ts      # 工具生成的文件
│ │   ├── idl_test_service_proxy.ts  # 工具生成的文件
│ └
└

2.配置权限
在模块下的module.json5配置文件中添加权限,并将应用签名为系统应用

"requestPermissions": [
      {"name": "ohos.permission.DISTRIBUTED_DATASYNC"},
      {"name": "ohos.permission.START_INVISIBLE_ABILITY"},
    ]

3.业务开发
启动或连接服务器,获取到remote: rpc.IRemoteObject对象,创建IdlServiceExtProxy对象,与服务端通信,调用服务端暴露的接口,获取到的IdlServiceExtProxy对象可作为全局变量,在其他的地方使用。

import promptAction from '@ohos.promptAction';
import Want from '@ohos.application.Want';
import hilog from '@ohos.hilog';
import Base from '@ohos.base';
import common from '@ohos.app.ability.common';
import rpc from '@ohos.rpc';
// 客户端需要将服务端对外提供的idl_service_ext_proxy.ts导入到本地工程中
import IdlServiceExtProxy from '../IdlServiceExt/idl_test_service_proxy';

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

let connectionId: number;
//后台服务应用信息
let want: Want = {
  deviceId: '',
  bundleName: 'com.oh.myapplication',
  abilityName: 'ServiceExtAbility'
};

let serviceExtProxy: IdlServiceExtProxy
// 通信方式一:通过接口调用的方式进行通信,屏蔽了RPC通信的细节,简洁明了
let options: common.ConnectOptions = {
  onConnect(elementName, remote: rpc.IRemoteObject): void {
    hilog.info(DOMAIN_NUMBER, TAG, 'onConnect callback');
    if (remote === null) {
      hilog.info(DOMAIN_NUMBER, TAG, `onConnect remote is null`);
      return;
    }
    serviceExtProxy = new IdlServiceExtProxy(remote);
    //远程调用
    serviceExtProxy.getData(3, (errorCode: number, retVal: number) => {
      //retVal - 远程调用的返回值
      console.log(`return data: ${retVal}`);
      hilog.info(DOMAIN_NUMBER, TAG, `processData, errorCode: ${errorCode}, retVal: ${retVal}`);
    });
  },
  onDisconnect(elementName): void {
    hilog.info(DOMAIN_NUMBER, TAG, 'onDisconnect callback');
  },
  onFailed(code: number): void {
    hilog.info(DOMAIN_NUMBER, TAG, 'onFailed callback', JSON.stringify(code));
  }
};
const REQUEST_CODE = 1;

@Entry
@Component
struct Index {
  @State message: string = 'Hello World';
  @State returnData:number = 0;
  private context: common.UIAbilityContext = getContext(this) as common.UIAbilityContext;
  build() {
    Column() {
      //...
      List({ initialIndex: 0,space:20}) {
        //启动一个后台服务(仅对系统应用开放)
        ListItem() {
          Row() {
            Button("启动后台服务")
          }
          .onClick(() => {
            this.context.startServiceExtensionAbility(want).then(() => {
              hilog.info(DOMAIN_NUMBER, TAG, 'Succeeded in starting ServiceExtensionAbility.');
              // 成功启动后台服务
              promptAction.showToast({
                message: $r('app.string.SuccessfullyStartBackendService')
              });
            }).catch((err:Base.BusinessError) => {
              hilog.error(DOMAIN_NUMBER, TAG, `Failed to start ServiceExtensionAbility. code is ${err.code}, message is ${err.message}`);
            });
          })
        }
        //停止一个已启动的ServiceExtensionAbility
        ListItem() {
          Row() {
            Button("停止后台服务")
          }
          .onClick(() => {
            this.context.stopServiceExtensionAbility(want).then(() => {
              hilog.info(DOMAIN_NUMBER, TAG, 'Succeeded in stopping ServiceExtensionAbility.');
              promptAction.showToast({
                message: $r('app.string.SuccessfullyStoppedAStartedBackendService')
              });
            }).catch((err: Base.BusinessError) => {
              hilog.error(DOMAIN_NUMBER, TAG, `Failed to stop ServiceExtensionAbility. Code is ${err.code}, message is ${err.message}`);
            });
          })
        }

        ListItem() {
          Row() {
            Button("连接后台服务")
          }
          .onClick(() => {
            // 建立连接后返回的Id需要保存下来,在解绑服务时需要作为参数传入
            connectionId = this.context.connectServiceExtensionAbility(want, options);
            // 成功连接后台服务
            promptAction.showToast({
              message: $r('app.string.SuccessfullyConnectBackendService')
            });
            // connectionId = context.connectAbility(want, options);
            hilog.info(DOMAIN_NUMBER, TAG, `connectionId is : ${connectionId}`);
          })
        }

        ListItem() {
          Row() {
            Button("连接成功点击调用函数")
            if (this.returnData!==0) {
              Text(`函数返回值:${this.returnData}`)
            }
          }
          .onClick(() => {
            serviceExtProxy.getData(5, (errorCode: number, retVal: number) => {
              //retVal - 远程调用的返回值
              console.log(`return data: ${retVal}`);
              this.returnData=retVal
              hilog.info(DOMAIN_NUMBER, TAG, `processData, errorCode: ${errorCode}, retVal: ${retVal}`);
            });
          })
        }

        ListItem() {
          Row() {
            Button("断连后台服务")
          }
          .onClick(() => {
            // connectionId为调用connectServiceExtensionAbility接口时的返回值,需开发者自行维护
            this.context.disconnectServiceExtensionAbility(connectionId).then(() => {
              hilog.info(DOMAIN_NUMBER, TAG, 'disconnectServiceExtensionAbility success');
              // 成功断连后台服务
              promptAction.showToast({
                message: $r('app.string.SuccessfullyDisconnectBackendService')
              });
            }).catch((error: Base.BusinessError) => {
              hilog.error(DOMAIN_NUMBER, TAG, 'disconnectServiceExtensionAbility failed');
            });
          })
        }
      }
    }.width('100%')
  }
}

注意点:

1.本文示例所用sdk版本为11,各版本sdk中略有差异,请以官方文档为准
2.sdk需要替换为full sdk,签名为系统应用
3.权限配置完整
​4.确保签名指纹正确
5.本文示例代码详见附件

相关文件下载
service端.zip
656.91 KB
下载
client端.zip
742.39 KB
下载
Logo

社区规范:仅讨论OpenHarmony相关问题。

更多推荐