fa-serviceability.md 12.8 KB
Newer Older
1
# ServiceAbility开发指导
Z
zhaoyuan 已提交
2

ahjxliubao2's avatar
ahjxliubao2 已提交
3
## 场景介绍
4
基于Service模板的Ability(以下简称“Service”)主要用于后台运行任务(如执行音乐播放、文件下载等),但不提供用户交互界面。Service可由其他应用或Ability启动。即使用户切换到其他应用,Service仍将在后台继续运行。
ahjxliubao2's avatar
ahjxliubao2 已提交
5

H
HuangXW 已提交
6
## 生命周期
Z
zhaoyuan 已提交
7

ahjxliubao2's avatar
ahjxliubao2 已提交
8
**表1** Service中相关生命周期API功能介绍
ahjxliubao2's avatar
ahjxliubao2 已提交
9 10
|接口名|描述|
|:------|:------|
H
HuangXW 已提交
11
|onStart?(): void|该方法在创建Service的时候调用,用于Service的初始化,在Service的整个生命周期只会调用一次。|
ahjxliubao2's avatar
ahjxliubao2 已提交
12 13 14
|onCommand?(want: Want, startId: number): void|在Service创建完成之后调用,该方法在客户端每次启动该Service时都会调用,开发者可以在该方法中做一些调用统计、初始化类的操作。|
|onConnect?(want: Want): rpc.RemoteObject|在Ability和Service连接时调用。|
|onDisconnect?(want: Want): void|在Ability与绑定的Service断开连接时调用。|
15
|onStop?(): void|在Service销毁时调用。开发者应通过实现此方法来清理资源,如关闭线程、注册的侦听器等。|
Z
zhaoyuan 已提交
16

H
HuangXW 已提交
17 18 19 20
onCommand()与onConnect()的区别在于:
 - onCommand()只能被startAbility或startAbilityForResult触发,客户端每次启动Service均会触发该回调
 - onConnect()只能被connectAbility触发,客户端每次与Servcie建立新的连接时会触发该回调

ahjxliubao2's avatar
ahjxliubao2 已提交
21
## 开发步骤
Z
zhaoyuan 已提交
22

ahjxliubao2's avatar
ahjxliubao2 已提交
23
### 创建注册Service
Z
zhaoyuan 已提交
24

25
1. 重写Service的生命周期方法,来添加其他Ability请求与Service Ability交互时的处理方法。
R
RayShih 已提交
26
   
Z
zhaoyuan 已提交
27
   创建Service的代码示例如下:
R
RayShih 已提交
28
   
H
HuangXW 已提交
29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
   ```ts
    export default {
        onStart() {
            console.log('ServiceAbility onStart');
        },
        onCommand(want, startId) {
            console.log('ServiceAbility onCommand');
        },
        onConnect(want) {
            console.log('ServiceAbility OnConnect');
            // ServiceAbilityStub的实现在下文给出
            return new ServiceAbilityStub('test');
        },
        onDisconnect(want) {
            console.log('ServiceAbility OnDisConnect');
        },
        onStop() {
            console.log('ServiceAbility onStop');
        }
    }
Z
zhaoyuan 已提交
49 50
   ```

R
RayShih 已提交
51
2. 注册Service。
Z
zhaoyuan 已提交
52

53
   Service需要在应用配置文件config.json中进行注册,注册类型type需要设置为service。
R
RayShih 已提交
54
   
H
HuangXW 已提交
55
   ```json
Z
zhaoyuan 已提交
56
    {
H
HuangXW 已提交
57 58 59 60 61 62
      "module": {
        "abilities": [
          {
            "name": ".ServiceAbility",
            "type": "service",
            "visible": true
Z
zhaoyuan 已提交
63
            ...
H
HuangXW 已提交
64 65
          }
        ]
Z
zhaoyuan 已提交
66
        ...
H
HuangXW 已提交
67 68
      }
      ...
Z
zhaoyuan 已提交
69
    }
R
RayShih 已提交
70
   ```
71

Z
zhaoyuan 已提交
72 73


R
RayShih 已提交
74
### 启动Service
Z
zhaoyuan 已提交
75 76 77

Ability为开发者提供了startAbility()方法来启动另外一个Ability。因为Service也是Ability的一种,开发者同样可以通过将Want传递给该方法来启动Service。

78
开发者可以通过构造包含bundleName与abilityName的Want对象来设置目标Service信息。参数的含义如下:
Z
zhaoyuan 已提交
79

80
- bundleName:表示对端应用的Bundle名称。
81
- abilityName:表示待启动的Ability名称。
Z
zhaoyuan 已提交
82 83 84

启动本地设备Service的代码示例如下:

H
HuangXW 已提交
85 86 87 88
```ts
import featureAbility from '@ohos.ability.featureAbility'

featureAbility.startAbility(
Z
zhaoyuan 已提交
89 90 91
    {
        want:
        {
ahjxliubao2's avatar
ahjxliubao2 已提交
92
            bundleName: "com.jstest.service",
H
HuangXW 已提交
93 94
            abilityName: "com.jstest.service.ServiceAbility"
        }
Z
zhaoyuan 已提交
95
    }
H
HuangXW 已提交
96 97 98 99 100
).then((err) => {
    console.log("startService success");
}).catch (err => {
    console.log("startService FAILED");
});
Z
zhaoyuan 已提交
101 102
```

R
RayShih 已提交
103
执行上述代码后,Ability将通过startAbility() 方法来启动Service。
H
HuangXW 已提交
104 105 106 107
- 如果Service尚未运行,则系统会先初始化Service,然后回调onStart()来启动Service,再回调onCommand()方法。
- 如果Service正在运行,则系统会直接回调Service的onCommand()方法。

启动远端设备Service的代码示例如下,详见[连接远程Service](fa-serviceability.md#连接远程service当前仅对系统应用开放)
Z
zhaoyuan 已提交
108

H
HuangXW 已提交
109 110
```ts
import featureAbility from '@ohos.ability.featureAbility'
Q
ql 已提交
111

H
HuangXW 已提交
112
featureAbility.startAbility(
Q
ql 已提交
113 114 115
    {
        want:
        {
H
HuangXW 已提交
116
            deviceId: remoteDeviceId,    // 远端设备Id
Q
ql 已提交
117
            bundleName: "com.jstest.service",
H
HuangXW 已提交
118 119
            abilityName: "com.jstest.service.ServiceAbility"
        }
Q
ql 已提交
120
    }
H
HuangXW 已提交
121 122 123 124 125
).then((err) => {
    console.log("startService success");
}).catch (err => {
    console.log("startService FAILED");
});
Q
ql 已提交
126
```
127 128 129


### 停止Service
Z
zhaoyuan 已提交
130

H
HuangXW 已提交
131 132 133 134
  常规情况下,Service可以将自己停止,或者被系统停止,具体场景如下:
   - Service调用particleAbility.terminateSelf()方法将自己停止。
   - Service所在的应用进程退出,Service将随着进程被回收。
   - 若Service仅仅是通过connectAbility()方法被访问的(从未执行过onCommand()回调),那么当最后一个连接被断开后,系统会将Service停止。
Z
zhaoyuan 已提交
135

R
RayShih 已提交
136
### 连接本地Service
Z
zhaoyuan 已提交
137 138 139 140

如果Service需要与Page Ability或其他应用的Service Ability进行交互,则须创建用于连接的Connection。Service支持其他Ability通过connectAbility()方法与其进行连接。


ahjxliubao2's avatar
ahjxliubao2 已提交
141
开发者可使用如下两种方式实现连接Service。
Z
zhaoyuan 已提交
142

ahjxliubao2's avatar
ahjxliubao2 已提交
143 144 145 146
1. 使用IDL自动生成代码

    使用OpenHarmony IDL(OpenHarmony Interface Definition Language)来自动生成对应客户端服务端及IRemoteObject代码,具体示例代码和说明请参考:

H
HuangXW 已提交
147
   - [`OpenHarmony IDL`:TS开发步骤](../IDL/idl-guidelines.md#ts)
ahjxliubao2's avatar
ahjxliubao2 已提交
148 149 150

2. 在对应文件编写代码

H
HuangXW 已提交
151 152 153 154
    在使用connectAbility()时,需要传入目标Service的Want与ConnectOptions的实例,其中ConnectOptions封装了三个回调,分别对应不同情况,开发者需自行实现:
     - onConnect():用来处理连接Service成功的回调。
     - onDisconnect():用来处理Service断连或异常死亡的回调。
     - onFailed():用来处理连接Service失败的回调。
ahjxliubao2's avatar
ahjxliubao2 已提交
155 156 157

    创建连接本地Service回调实例的代码示例如下:

H
HuangXW 已提交
158
    ```ts
ahjxliubao2's avatar
ahjxliubao2 已提交
159
    import prompt from '@system.prompt'
160
    
黄师伟 已提交
161 162
    var option = {
        onConnect: function onConnectCallback(element, proxy) {
H
HuangXW 已提交
163
            console.log(`onConnectLocalService onConnectDone`);
黄师伟 已提交
164 165 166
            if (proxy === null) {
                prompt.showToast({
                    message: "Connect service failed"
H
HuangXW 已提交
167 168
                });
                return;
黄师伟 已提交
169
            }
H
HuangXW 已提交
170 171 172 173 174 175
            // 得到Service的proxy对象后便可以与其进行通信
            let data = rpc.MessageParcel.create();
            let reply = rpc.MessageParcel.create();
            let option = new rpc.MessageOption();
            data.writeString("InuptString");
            proxy.sendRequest(0, data, reply, option);
ahjxliubao2's avatar
ahjxliubao2 已提交
176
            prompt.showToast({
黄师伟 已提交
177
                message: "Connect service success"
H
HuangXW 已提交
178
            });
黄师伟 已提交
179 180
        },
        onDisconnect: function onDisconnectCallback(element) {
H
HuangXW 已提交
181
            console.log(`onConnectLocalService onDisconnectDone element:${element}`);
黄师伟 已提交
182 183
            prompt.showToast({
                message: "Disconnect service success"
H
HuangXW 已提交
184
            });
黄师伟 已提交
185 186
        },
        onFailed: function onFailedCallback(code) {
H
HuangXW 已提交
187
            console.log(`onConnectLocalService onFailed errCode:${code}`);
黄师伟 已提交
188 189
            prompt.showToast({
                message: "Connect local service onFailed"
H
HuangXW 已提交
190
            });
黄师伟 已提交
191
        }
H
HuangXW 已提交
192
    };
ahjxliubao2's avatar
ahjxliubao2 已提交
193
    ```
Z
zhaoyuan 已提交
194

ahjxliubao2's avatar
ahjxliubao2 已提交
195
    连接本地Service的代码示例如下:
Z
zhaoyuan 已提交
196

H
HuangXW 已提交
197 198
    ```ts
    import featureAbility from '@ohos.ability.featureAbility'
199
    
H
HuangXW 已提交
200 201 202 203 204
    let want = {
        bundleName: "com.jstest.service",
        abilityName: "com.jstest.service.ServiceAbility"
    };
    let connectId = featureAbility.connectAbility(want, option);
ahjxliubao2's avatar
ahjxliubao2 已提交
205
    ```
Z
zhaoyuan 已提交
206

H
HuangXW 已提交
207
    同时,Service侧也需要在onConnect()时返回IRemoteObject,从而定义与Service进行通信的接口。onConnect()需要返回一个IRemoteObject对象。OpenHarmony提供了IRemoteObject的默认实现,开发者可以通过继承rpc.RemoteObject来创建自定义的实现类,从而实现与Service的通信。具体使用方法可参考[ohos.rpc API文档](..\reference\apis\js-apis-rpc.md)。
Z
zhaoyuan 已提交
208

ahjxliubao2's avatar
ahjxliubao2 已提交
209
    Service侧把自身的实例返回给调用侧的代码示例如下:
Z
zhaoyuan 已提交
210

H
HuangXW 已提交
211 212
    ```ts
    import rpc from "@ohos.rpc"
213
    
H
HuangXW 已提交
214 215 216 217 218 219 220 221 222
    class ServiceAbilityStub extends rpc.RemoteObject {
        constructor(des: any) {
            if (typeof des === 'string') {
                super(des);
            } else {
                console.log("Error, the input param is not string");
                return;
            }
        }
223
    
H
HuangXW 已提交
224 225 226 227 228 229 230 231 232 233 234 235 236 237
        onRemoteRequest(code: number, data: any, reply: any, option: any) {
            console.log("onRemoteRequest called");
            // 可根据code执行不同的业务逻辑
            if (code === 1) {
                // 将传入的字符串进行排序
                let string = data.readString();
                console.log(`Input string = ${string}`);
                let result = Array.from(string).sort().join('');
                console.log(`Output result = ${result}`);
                reply.writeString(result);
            } else {
                console.log(`Unknown request code`);
            }
            return true;
黄师伟 已提交
238 239
        }
    }
240
    
H
HuangXW 已提交
241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256
    export default {
        onStart() {
            console.log('ServiceAbility onStart');
        },
        onCommand(want, startId) {
            console.log('ServiceAbility onCommand');
        },
        onConnect(want) {
            console.log('ServiceAbility OnConnect');
            return new ServiceAbilityStub('ServiceAbilityRemoteObject');
        },
        onDisconnect(want) {
            console.log('ServiceAbility OnDisConnect');
        },
        onStop() {
            console.log('ServiceAbility onStop');
黄师伟 已提交
257
        }
ahjxliubao2's avatar
ahjxliubao2 已提交
258 259
    }
    ```
Z
zhaoyuan 已提交
260

261
### 连接远程Service(当前仅对系统应用开放)
262

H
HuangXW 已提交
263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282
连接远程Service,构造ConnectOptions的方法与连接本地Serivce相同,区别在于:
 - 应用需要向用户申请数据同步权限
 - 目标Service的Want需要包含对端设备的deviceId

> 说明:
> (1) 由于DeviceManager的getTrustedDeviceList等接口仅对系统应用开放,当前仅系统应用支持连接远程Service。
> (2) API定义可见:[deviceManager模块](..\reference\apis\js-apis-device-manager.md)
> (3) 参考Demo可见:[分布式Demo](https://gitee.com/openharmony/applications_app_samples/tree/master/ability/DMS)

在跨设备场景下,需要向用户申请数据同步的权限,首先在config.json里配置权限:

```json
{
  ...
  "module": {
    ...
    "reqPermissions": [{
      "name": "ohos.permission.DISTRIBUTED_DATASYNC"
    }]
  }
东方_月初's avatar
东方_月初 已提交
283 284 285
}
```

H
HuangXW 已提交
286
DISTRIBUTED_DATASYNC权限需要用户授予,在应用启动时需要向用户弹框请求授予权限,示例代码如下:
287 288

```ts
H
HuangXW 已提交
289 290
import abilityAccessCtrl from "@ohos.abilityAccessCtrl"
import bundle from '@ohos.bundle'
ahjxliubao2's avatar
ahjxliubao2 已提交
291

H
HuangXW 已提交
292 293 294 295 296 297 298 299 300 301 302 303 304 305 306
async function RequestPermission() {
    console.info('RequestPermission begin');
    let array: Array<string> = ["ohos.permission.DISTRIBUTED_DATASYNC"];
    let bundleFlag = 0;
    let tokenID = undefined;
    let userID = 100;
    let appInfo = await bundle.getApplicationInfo('ohos.samples.etsDemo', 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]);
        console.info("verifyAccessToken result:" + JSON.stringify(result));
        if (result != abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED) {
            requestPermissions.push(array[i]);
307 308
        }
    }
H
HuangXW 已提交
309 310 311
    console.info("requestPermissions:" + JSON.stringify(requestPermissions));
    if (requestPermissions.length == 0 || requestPermissions == []) {
        return;
312
    }
H
HuangXW 已提交
313 314 315 316 317
    let context = featureAbility.getContext();
    context.requestPermissionsFromUser(requestPermissions, 1, (data)=>{
        console.info("data:" + JSON.stringify(data));
    });
    console.info('RequestPermission end');
318 319
}
```
东方_月初's avatar
东方_月初 已提交
320

H
HuangXW 已提交
321 322 323
获取deviceId需要导入`@ohos.distributedHardware.deviceManager`模块,其中提供了getTrustedDeviceList等接口用于获取远端设备的deviceId。
 - 接口使用可参考[deviceManager模块](..\reference\apis\js-apis-device-manager.md)
 - 具体实现可参考[分布式Demo](https://gitee.com/openharmony/applications_app_samples/tree/master/ability/DMS)
东方_月初's avatar
东方_月初 已提交
324

H
HuangXW 已提交
325
连接远程Service,只需要在want内定义deviceId即可,示例代码如下:
东方_月初's avatar
东方_月初 已提交
326

327
```ts
H
HuangXW 已提交
328
import featureAbility from '@ohos.ability.featureAbility'
329

H
HuangXW 已提交
330 331 332 333
let want = {
    deviceId: remoteDeviceId,
    bundleName: "com.jstest.service",
    abilityName: "com.jstest.service.ServiceAbility"
334
};
H
HuangXW 已提交
335
let connectId = featureAbility.connectAbility(want, option);
ahjxliubao2's avatar
ahjxliubao2 已提交
336 337
```

H
HuangXW 已提交
338 339
其余实现均与本地连接Service相同,参考[连接本地Service](fa-serviceability.md#连接本地service)的示例代码即可。

340
## 相关实例
ahjxliubao2's avatar
ahjxliubao2 已提交
341

342
针对ServiceAbility开发,有以下相关实例可供参考:
343 344
- [`ServiceAbility`:ServiceAbility的创建与使用(ArkTS)(API8)](https://gitee.com/openharmony/applications_app_samples/tree/master/ability/ServiceAbility)
- [`DMS`:分布式Demo(ArkTS)(API8)(Full SDK)](https://gitee.com/openharmony/applications_app_samples/tree/master/ability/DMS)