# Service Ability Development ## When to Use A Service ability is used to run tasks in the background, such as playing music or downloading files. It does not provide a UI for user interaction. Service abilities can be started by other applications or abilities and can remain running in the background even after the user switches to another application. ## Available APIs **Table 1** Service ability lifecycle callbacks |API|Description| |:------|:------| |onStart|Called to initialize a Service ability when the Service ability is being created. This callback is invoked only once in the entire lifecycle of a Service ability. The **Want** object passed to this callback must be null.| |onCommand|Called every time a Service ability is created on a client. You can calculate calling statistics and perform initialization operations in this callback.| |onConnect|Called when another ability is connected to the Service ability.| |onDisconnect|Called when another ability is disconnected from the Service ability.| |onStop|Called when the Service ability is being destroyed. You should override this callback for your Service ability to clear its resources, such as threads and registered listeners.| ## How to Develop ### Creating a Service Ability 1. Create a child class of the **Ability** class and override the following Service ability-related lifecycle callbacks to implement your own logic for processing requests to interact with your Service ability: The following code snippet shows how to create a Service ability: ```javascript export default { onStart(want) { console.log('ServiceAbility onStart'); }, onCommand(want, restart, startId) { console.log('ServiceAbility onCommand'); }, onConnect(want) { console.log('ServiceAbility OnConnect'); }, onDisconnect() { console.log('ServiceAbility OnDisConnect'); }, onStop() { console.log('ServiceAbility onStop'); }, } ``` 2. Register a Service ability. You must declare your Service ability in the **config.json** file by setting its **type** attribute to **service**. ```javascript { "module": { "abilities": [ { "name": ".ServiceAbility", "type": "service", "visible": true ... } ] ... } ... } ``` ### Starting a Service ability The **Ability** class provides the **startAbility()** API for you to start another Service ability by passing a **Want** object. To set information about the target Service ability, you can first construct a **Want** object with the **bundleName** and **abilityName** parameters specified. The meanings of the parameters are as follows: - **bundleName** indicates the name of the bundle to which the target ability belongs. - **abilityName** indicates the target ability name. The following code snippet shows how to start a Service ability running on the local device: ```javascript import featureAbility from '@ohos.ability.featureAbility'; let promise = await featureAbility.startAbility( { want: { bundleName: "com.jstest.serviceability", abilityName: "com.jstest.serviceability.MainAbility", }, } ); ``` After the preceding code is executed, the **startAbility()** API is called to start the Service ability. - If the Service ability is not running, the system calls **onStart()** to initialize the Service ability, and then calls **onCommand()** on the Service ability. - If the Service ability is running, the system directly calls **onCommand()** on the Service ability. ### Stopping a Service ability Once created, the Service ability keeps running in the background. The system does not stop or destroy it unless memory resources must be reclaimed. You can call **terminateSelf()** on a Service ability to stop it. ### Connecting to a Local Service Ability If you need to connect a Service ability to a Page ability or to a Service ability in another application, you must first implement the **IAbilityConnection** API for the connection. A Service ability allows other abilities to connect to it through **connectAbility()**. When calling **connectAbility()**, you should pass a **Want** object containing information about the target Service ability and an **IAbilityConnection** object to the API. **IAbilityConnection** provides the following callbacks that you should implement: **onConnect()**, **onDisconnect()**, and **onFailed()**. The **onConnect()** callback is invoked when a Service ability is connected, **onDisconnect()** is invoked when a Service ability is unexpectedly disconnected, and **onFailed()** is invoked when a connection to a Service ability fails. The following code snippet shows how to implement the callbacks: ```javascript let mRemote; function onConnectCallback(element, remote){ console.log('onConnectLocalService onConnectDone element: ' + element); console.log('onConnectLocalService onConnectDone remote: ' + remote); mRemote = remote; if (mRemote == null) { prompt.showToast({ message: "onConnectLocalService not connected yet" }); return; } let option = new rpc.MessageOption(); let data = new rpc.MessageParcel(); let reply = new rpc.MessageParcel(); data.writeInt(1); data.writeInt(99); mRemote.sendRequest(1, data, reply, option).then((result) => { console.log('sendRequest success'); let msg = reply.readInt(); prompt.showToast({ message: "onConnectLocalService connect result: " + msg, duration: 3000 }); }).catch((e) => { console.log('sendRequest error:' + e); }); } function onDisconnectCallback(element){ console.log('ConnectAbility onDisconnect Callback') } function onFailedCallback(code){ console.log('ConnectAbility onFailed Callback') } ``` The following code snippet shows how to connect to a local Service ability: ```javascript import featureAbility from '@ohos.ability.featureAbility'; let connId = featureAbility.connectAbility( { bundleName: "com.jstest.serviceability", abilityName: "com.jstest.serviceability.MainAbility", }, { onConnect: onConnectCallback, onDisconnect: onDisconnectCallback, onFailed: onFailedCallback, }, ); ``` When a Service ability is connected, the **onConnect()** callback is invoked and returns an **IRemoteObject** defining the proxy used for communicating with the Service ability. OpenHarmony provides a default implementation of the **IRemoteObject** interface. You can inherit **rpc.RemoteObject** to implement your own class of **IRemoteObject**. The following code snippet shows how the Service ability instance returns itself to the calling ability: ```javascript import rpc from "@ohos.rpc"; let mMyStub; export default { onStart(want) { class MyStub extends rpc.RemoteObject{ constructor(des) { if (typeof des === 'string') { super(des); } return null; } onRemoteRequest(code, message, reply, option) { console.log("ServiceAbility onRemoteRequest called"); if (code === 1) { let op1 = data.readInt(); let op2 = data.readInt(); console.log("op1 = " + op1 + ", op2 = " + op2); reply.writeInt(op1 + op2); } else { console.log("ServiceAbility unknown request code"); } return true; } } mMyStub = new MyStub("ServiceAbility-test"); }, onCommand(want, restart, startId) { console.log('ServiceAbility onCommand'); }, onConnect(want) { console.log('ServiceAbility OnConnect'); return mMyStub; }, onDisconnect() { console.log('ServiceAbility OnDisConnect'); }, onStop() { console.log('ServiceAbility onStop'); }, } ``` ### Connecting to a Remote Service Ability (Applying only to System Applications) >Note: The **getTrustedDeviceListSync** API of the **DeviceManager** class is open only to system applications. Therefore, remote Service ability startup applies only to system applications. If you need to connect a Service ability to a Page ability on another device or to a Service ability in another application on another device, you must first implement the **IAbilityConnection** interface for the connection. A Service ability allows other abilities on another device to connect to it through **connectAbility()**. When calling **connectAbility()**, you should pass a **Want** object containing information about the target Service ability and an **IAbilityConnection** object to the API. **IAbilityConnection** provides the following callbacks that you should implement: **onConnect()**, **onDisconnect()**, and **onFailed()**. The **onConnect()** callback is invoked when a Service ability is connected, **onDisconnect()** is invoked when a Service ability is unexpectedly disconnected, and **onFailed()** is invoked when a connection to a Service ability fails. The following code snippet shows how to implement the callbacks: ```ts let mRemote; function onConnectCallback(element, remote){ console.log('onConnectLocalService onConnectDone element: ' + element); console.log('onConnectLocalService onConnectDone remote: ' + remote); mRemote = remote; if (mRemote == null) { prompt.showToast({ message: "onConnectLocalService not connected yet" }); return; } let option = new rpc.MessageOption(); let data = new rpc.MessageParcel(); let reply = new rpc.MessageParcel(); data.writeInt(1); data.writeInt(99); mRemote.sendRequest(1, data, reply, option).then((result) => { console.log('sendRequest success'); let msg = reply.readInt(); prompt.showToast({ message: "onConnectLocalService connect result: " + msg, duration: 3000 }); }).catch((e) => { console.log('sendRequest error:' + e); }); } function onDisconnectCallback(element){ console.log('ConnectRemoteAbility onDisconnect Callback') } function onFailedCallback(code){ console.log('ConnectRemoteAbility onFailed Callback') } ``` The **Want** of the target Service ability must contain the remote **deviceId**, which can be obtained from **DeviceManager**. The sample code is as follows: ```ts import deviceManager from '@ohos.distributedHardware.deviceManager'; let dmClass; function getRemoteDeviceId() { if (typeof dmClass === 'object' && dmClass != null) { let list = dmClass.getTrustedDeviceListSync(); if (typeof (list) == 'undefined' || typeof (list.length) == 'undefined') { console.log("MainAbility onButtonClick getRemoteDeviceId err: list is null"); return; } console.log("MainAbility onButtonClick getRemoteDeviceId success:" + list[0].deviceId); return list[0].deviceId; } else { console.log("MainAbility onButtonClick getRemoteDeviceId err: dmClass is null"); } } ``` The following code snippet shows how to connect to a remote Service ability: ```ts import featureAbility from '@ohos.ability.featureAbility'; let connId = featureAbility.connectAbility( { deviceId: getRemoteDeviceId(), bundleName: "ohos.samples.etsDemo", abilityName: "ohos.samples.etsDemo.ServiceAbility", }, { onConnect: onConnectCallback, onDisconnect: onDisconnectCallback, onFailed: onFailedCallback, }, ); ``` In the cross-device scenario, the application must also apply for the data synchronization permission from end users. The sample code is as follows: ```ts import abilityAccessCtrl from "@ohos.abilityAccessCtrl"; import bundle from '@ohos.bundle'; async function RequestPermission() { console.info('RequestPermission begin'); let array: Array = ["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 = []; 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) { } else { requestPermissions.push(array[i]); } } console.info("requestPermissions:" + JSON.stringify(requestPermissions)); if (requestPermissions.length == 0 || requestPermissions == []) { return; } let context = featureAbility.getContext(); context.requestPermissionsFromUser(requestPermissions, 1, (data)=>{ console.info("data:" + JSON.stringify(data)); console.info("data requestCode:" + data.requestCode); console.info("data permissions:" + data.permissions); console.info("data authResults:" + data.authResults); }); console.info('RequestPermission end'); } ``` When a Service ability is connected, the **onConnect()** callback is invoked and returns an **IRemoteObject** defining the proxy used for communicating with the Service ability. OpenHarmony provides a default implementation of the **IRemoteObject** interface. You can inherit **rpc.RemoteObject** to implement your own class of **IRemoteObject**. The following code snippet shows how the Service ability instance returns itself to the calling ability: ```ts import rpc from "@ohos.rpc"; class FirstServiceAbilityStub extends rpc.RemoteObject{ constructor(des) { if (typeof des === 'string') { super(des); } else { return null; } } onRemoteRequest(code, data, reply, option) { console.log("ServiceAbility onRemoteRequest called"); if (code === 1) { let op1 = data.readInt(); let op2 = data.readInt(); console.log("op1 = " + op1 + ", op2 = " + op2); reply.writeInt(op1 + op2); } else { console.log("ServiceAbility unknown request code"); } return true; } } export default { onStart() { console.info('ServiceAbility onStart'); }, onStop() { console.info('ServiceAbility onStop'); }, onConnect(want) { console.log("ServiceAbility onConnect"); try { let value = JSON.stringify(want); console.log("ServiceAbility want:" + value); } catch(error) { console.log("ServiceAbility error:" + error); } return new FirstServiceAbilityStub("first ts service stub"); }, onDisconnect(want) { console.log("ServiceAbility onDisconnect"); let value = JSON.stringify(want); console.log("ServiceAbility want:" + value); }, onCommand(want, startId) { console.info('ServiceAbility onCommand'); let value = JSON.stringify(want); console.log("ServiceAbility want:" + value); console.log("ServiceAbility startId:" + startId); } }; ``` ## Samples The following samples are provided to help you better understand how to develop a Service ability: - [`ServiceAbility`: Service Ability Creation and Use (eTS) (API8)](https://gitee.com/openharmony/app_samples/tree/master/ability/ServiceAbility) - [`DMS`: Distributed Demo (eTS) (API7)](https://gitee.com/openharmony/app_samples/tree/master/ability/DMS)