hop-multi-device-collaboration.md 23.5 KB
Newer Older
G
Gloria 已提交
1
# Multi-device Collaboration (for System Applications Only)
G
Gloria 已提交
2 3 4 5


## When to Use

6
Multi-device coordination involves the following scenarios:
G
Gloria 已提交
7

8
- [Starting UIAbility or ServiceExtensionAbility Across Devices (No Data Returned)](#starting-uiability-or-serviceextensionability-across-devices-no-data-returned)
G
Gloria 已提交
9 10 11 12 13

- [Starting UIAbility Across Devices (Data Returned)](#starting-uiability-across-devices-data-returned)

- [Connecting to ServiceExtensionAbility Across Devices](#connecting-to-serviceextensionability-across-devices)

14
- [Using Cross-Device Call](#using-cross-device-call)
G
Gloria 已提交
15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33


## Multi-Device Collaboration Process

The figure below shows the multi-device collaboration process.

**Figure 1** Multi-device collaboration process 
![hop-multi-device-collaboration](figures/hop-multi-device-collaboration.png)


## Constraints

- Since multi-device collaboration task management is not available, you can obtain the device list by developing system applications. Access to third-party applications is not supported.

- Multi-device collaboration must comply with [Inter-Device Component Startup Rules](component-startup-rules.md#inter-device-component-startup-rules).

- For better user experience, you are advised to use the **want** parameter to transmit data smaller than 100 KB.


34
## Starting UIAbility or ServiceExtensionAbility Across Devices (No Data Returned)
G
Gloria 已提交
35

36
On device A, touch the **Start** button provided by the initiator application to start a specified UIAbility or ServiceExtensionAbility on device B.
G
Gloria 已提交
37 38 39 40 41 42 43 44


### Available APIs

**Table 1** Cross-device startup APIs

| **API**| **Description**|
| -------- | -------- |
45 46 47
| startAbility(want: Want, callback: AsyncCallback<void>): void; | Starts a UIAbility or ServiceExtensionAbility. This API uses an asynchronous callback to return the result.|
| stopServiceExtensionAbility(want: Want, callback: AsyncCallback<void>): void; | Stops a ServiceExtensionAbility. This API uses an asynchronous callback to return the result.|
| stopServiceExtensionAbility(want: Want): Promise<void>; | Stops a ServiceExtensionAbility. This API uses a promise to return the result.|
G
Gloria 已提交
48 49 50 51


### How to Develop

52
1. Request the **ohos.permission.DISTRIBUTED_DATASYNC** permission. For details, see [Declaring Permissions in the Configuration File](../security/accesstoken-guidelines.md#declaring-permissions-in-the-configuration-file).
G
Gloria 已提交
53

54
2. Display a dialog box to ask authorization from the user when the application is started for the first time. For details, see [Requesting User Authorization](../security/accesstoken-guidelines.md#requesting-user-authorization).
G
Gloria 已提交
55 56

3. Obtain the device ID of the target device.
57

G
Gloria 已提交
58 59 60 61 62 63 64 65
   ```ts
   import deviceManager from '@ohos.distributedHardware.deviceManager';
   
   let dmClass;
   function initDmClass() {
       // createDeviceManager is a system API.
       deviceManager.createDeviceManager('ohos.samples.demo', (err, dm) => {
           if (err) {
G
Gloria 已提交
66
               ...
G
Gloria 已提交
67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85
               return
           }
           dmClass = dm
       })
   }
   function getRemoteDeviceId() {
       if (typeof dmClass === 'object' && dmClass !== null) {
           let list = dmClass.getTrustedDeviceListSync()
           if (typeof (list) === 'undefined' || typeof (list.length) === 'undefined') {
               console.info('EntryAbility onButtonClick getRemoteDeviceId err: list is null')
               return;
           }
           return list[0].deviceId
       } else {
           console.info('EntryAbility onButtonClick getRemoteDeviceId err: dmClass is null')
       }
   }
   ```

86
4. Set the target component parameters, and call [startAbility()](../reference/apis/js-apis-inner-application-uiAbilityContext.md#uiabilitycontextstartability) to start a UIAbility or ServiceExtensionAbility.
87

G
Gloria 已提交
88 89 90 91 92 93 94
   ```ts
   let want = {
       deviceId: getRemoteDeviceId(), 
       bundleName: 'com.example.myapplication',
       abilityName: 'FuncAbility',
       moduleName: 'module1', // moduleName is optional.
   }
95
   // context is the AbilityContext of the initiator UIAbility.
G
Gloria 已提交
96
   this.context.startAbility(want).then(() => {
G
Gloria 已提交
97
       ...
G
Gloria 已提交
98
   }).catch((err) => {
G
Gloria 已提交
99
       ...
G
Gloria 已提交
100 101 102
   })
   ```

G
Gloria 已提交
103
5. Call [stopServiceExtensionAbility](../reference/apis/js-apis-inner-application-uiAbilityContext.md#uiabilitycontextstopserviceextensionability) to stop the ServiceExtensionAbility when it is no longer required on device B. (This API cannot be used to stop a UIAbility. Users must manually stop a UIAbility through task management.)
104 105 106 107 108 109 110 111 112 113 114 115 116 117 118

   ```ts
   let want = {
       deviceId: getRemoteDeviceId(),
       bundleName: 'com.example.myapplication',
       abilityName: 'FuncAbility',
       moduleName: 'module1', // moduleName is optional.
   }
   // Stop the ServiceExtensionAbility started by calling startAbility().
   this.context.stopServiceExtensionAbility(want).then(() => {
       console.info("stop service extension ability success")
   }).catch((err) => {
       console.info("stop service extension ability err is " + JSON.stringify(err))
   })
   ```
G
Gloria 已提交
119 120 121 122 123 124 125 126

## Starting UIAbility Across Devices (Data Returned)

On device A, touch the **Start** button provided by the initiator application to start a specified UIAbility on device B. When the UIAbility on device B exits, a value is sent back to the initiator application.


### Available APIs

127
**Table 2** APIs for starting a UIAbility across devices and returning the result data
G
Gloria 已提交
128 129 130 131

| API| Description|
| -------- | -------- |
| startAbilityForResult(want: Want, callback: AsyncCallback<AbilityResult>): void; | Starts a UIAbility. This API uses an asynchronous callback to return the result when the UIAbility is terminated.|
132 133
| terminateSelfWithResult(parameter: AbilityResult, callback: AsyncCallback<void>): void;| Terminates this UIAbility. This API uses an asynchronous callback to return the result information. It is used together with **startAbilityForResult**.|
| terminateSelfWithResult(parameter: AbilityResult): Promise<void>; | Terminates this UIAbility. This API uses a promise to return the result information. It is used together with **startAbilityForResult**.|
G
Gloria 已提交
134 135 136 137


### How to Develop

138
1. Request the **ohos.permission.DISTRIBUTED_DATASYNC** permission. For details, see [Declaring Permissions in the Configuration File](../security/accesstoken-guidelines.md#declaring-permissions-in-the-configuration-file).
G
Gloria 已提交
139

140
2. Display a dialog box to ask authorization from the user when the application is started for the first time. For details, see [Requesting User Authorization](../security/accesstoken-guidelines.md#requesting-user-authorization).
G
Gloria 已提交
141

142
3. Set the target component parameters on the initiator, and call **startAbilityForResult()** to start the target UIAbility. **data** in the asynchronous callback is used to receive the information returned by the target UIAbility to the initiator UIAbility after the target UIAbility terminates itself. For details about how to implement **getRemoteDeviceId()**, see [Starting UIAbility or ServiceExtensionAbility Across Devices (No Data Returned)](#starting-uiability-or-serviceextensionability-across-devices-no-data-returned).
143

G
Gloria 已提交
144 145 146 147 148 149 150
   ```ts
   let want = {
       deviceId: getRemoteDeviceId(), 
       bundleName: 'com.example.myapplication',
       abilityName: 'FuncAbility',
       moduleName: 'module1', // moduleName is optional.
   }
151
   // context is the AbilityContext of the initiator UIAbility.
G
Gloria 已提交
152
   this.context.startAbilityForResult(want).then((data) => {
G
Gloria 已提交
153
       ...
G
Gloria 已提交
154
   }).catch((err) => {
G
Gloria 已提交
155
       ...
G
Gloria 已提交
156 157 158 159
   })
   ```

4. After the UIAbility task at the target device is complete, call **terminateSelfWithResult()** to return the data to the initiator UIAbility.
160

G
Gloria 已提交
161 162 163 164 165 166 167 168 169 170
   ```ts
   const RESULT_CODE: number = 1001;
   let abilityResult = {
       resultCode: RESULT_CODE,
       want: {
           bundleName: 'com.example.myapplication',
           abilityName: 'FuncAbility',
           moduleName: 'module1',
       },
   }
171
   // context is the AbilityContext of the target UIAbility.
G
Gloria 已提交
172
   this.context.terminateSelfWithResult(abilityResult, (err) => {
G
Gloria 已提交
173
       ...
G
Gloria 已提交
174 175 176 177
   });
   ```

5. The initiator UIAbility receives the information returned by the target UIAbility and processes the information.
178

G
Gloria 已提交
179 180 181
   ```ts
   const RESULT_CODE: number = 1001;
   
G
Gloria 已提交
182
   ...
G
Gloria 已提交
183
   
184
   // context is the UIAbilityContext of the initiator UIAbility.
G
Gloria 已提交
185 186 187 188
   this.context.startAbilityForResult(want).then((data) => {
       if (data?.resultCode === RESULT_CODE) {
           // Parse the information returned by the target UIAbility.
           let info = data.want?.parameters?.info
G
Gloria 已提交
189
           ...
G
Gloria 已提交
190 191
       }
   }).catch((err) => {
G
Gloria 已提交
192
       ...
G
Gloria 已提交
193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214
   })
   ```


## Connecting to ServiceExtensionAbility Across Devices

A system application can connect to a service on another device by calling [connectServiceExtensionAbility()](../reference/apis/js-apis-inner-application-uiAbilityContext.md#abilitycontextconnectserviceextensionability). For example, in the distributed game scenario, a tablet is used as the remote control and a smart TV is used as the display.


### Available APIs

**Table 3** APIs for cross-device connection

| API| Description|
| -------- | -------- |
| connectServiceExtensionAbility(want: Want, options: ConnectOptions): number; | Connects to a ServiceExtensionAbility.|
| disconnectServiceExtensionAbility(connection: number, callback: AsyncCallback<void>): void; | Disconnects a connection. This API uses an asynchronous callback to return the result.|
| disconnectServiceExtensionAbility(connection: number): Promise<void>; | Disconnects a connection. This API uses a promise to return the result.|


### How to Develop

215 216 217
1. Request the **ohos.permission.DISTRIBUTED_DATASYNC** permission. For details, see [Declaring Permissions in the Configuration File](../security/accesstoken-guidelines.md#declaring-permissions-in-the-configuration-file).

2. Display a dialog box to ask authorization from the user when the application is started for the first time. For details, see [Requesting User Authorization](../security/accesstoken-guidelines.md#requesting-user-authorization).
G
Gloria 已提交
218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273

3. (Optional) [Implement a background service](serviceextensionability.md#implementing-a-background-service). Perform this operation only if no background service is available.

4. Connect to the background service.
   - Implement the **IAbilityConnection** class. **IAbilityConnection** provides the following callbacks that you should implement: **onConnect()**, **onDisconnect()**, and **onFailed()**. The **onConnect()** callback is invoked when a service is connected, **onDisconnect()** is invoked when a service is unexpectedly disconnected, and **onFailed()** is invoked when the connection to a service fails.
   - Set the target component parameters, including the target device ID, bundle name, and ability name.
   - Call **connectServiceExtensionAbility** to initiate a connection.
   - Receive the service handle returned by the target device when the connection is successful.
   - Perform cross-device invoking and obtain the result returned by the target service.
     
      ```ts
      import rpc from '@ohos.rpc';
      
      const REQUEST_CODE = 99;
      let want = {
          "deviceId": getRemoteDeviceId(), 
          "bundleName": "com.example.myapplication",
          "abilityName": "ServiceExtAbility"
      };
      let options = {
          onConnect(elementName, remote) {
              console.info('onConnect callback');
              if (remote === null) {
                  console.info(`onConnect remote is null`);
                  return;
              }
              let option = new rpc.MessageOption();
              let data = new rpc.MessageParcel();
              let reply = new rpc.MessageParcel();
              data.writeInt(1);
              data.writeInt(99); // You can send data to the target application for corresponding operations.
      
              // @param code Indicates the service request code sent by the client.
              // @param data Indicates the {@link MessageParcel} object sent by the client.
              // @param reply Indicates the response message object sent by the remote service.
              // @param options Specifies whether the operation is synchronous or asynchronous.
              // 
              // @return Returns {@code true} if the operation is successful; returns {@code false} otherwise.
              remote.sendRequest(REQUEST_CODE, data, reply, option).then((ret) => {
                  let msg = reply.readInt();   // Receive the information (100) returned by the target device if the connection is successful.
                  console.info(`sendRequest ret:${ret} msg:${msg}`);
              }).catch((error) => {
                  console.info('sendRequest failed');
              });
          },
          onDisconnect(elementName) {
              console.info('onDisconnect callback');
          },
          onFailed(code) {
              console.info('onFailed callback');
          }
      }
      // The ID returned after the connection is set up must be saved. The ID will be passed for service disconnection.
      let connectionId = this.context.connectServiceExtensionAbility(want, options);
      ```

274
      For details about how to implement **getRemoteDeviceId()**, see [Starting UIAbility or ServiceExtensionAbility Across Devices (No Data Returned)](#starting-uiability-or-serviceextensionability-across-devices-no-data-returned).
G
Gloria 已提交
275 276

5. Disconnect the connection. Use **disconnectServiceExtensionAbility()** to disconnect from the background service.
277

G
Gloria 已提交
278 279 280 281 282 283 284 285 286 287
   ```ts
   let connectionId = 1 // ID returned when the service is connected through connectServiceExtensionAbility.
   this.context.disconnectServiceExtensionAbility(connectionId).then((data) => {
       console.info('disconnectServiceExtensionAbility success');
   }).catch((error) => {
       console.error('disconnectServiceExtensionAbility failed');
   })
   ```


288
## Using Cross-Device Call
G
Gloria 已提交
289

290
The basic principle of cross-device call is the same as that of intra-device call. For details, see [Using Call to Implement UIAbility Interaction (for System Applications Only)](uiability-intra-device-interaction.md#using-call-to-implement-uiability-interaction-for-system-applications-only).
G
Gloria 已提交
291

292
The following describes how to implement multi-device collaboration through cross-device call.
G
Gloria 已提交
293 294 295 296


### Available APIs

297
**Table 4** Call APIs
G
Gloria 已提交
298 299 300 301

| API| Description|
| -------- | -------- |
| startAbilityByCall(want: Want): Promise<Caller>; | Starts a UIAbility in the foreground or background and obtains the caller object for communicating with the UIAbility.|
302 303 304 305
| on(method: string, callback: CalleeCallBack): void | Callback invoked when the CalleeAbility registers a method.|
| off(method: string): void | Callback invoked when the CalleeAbility deregisters a method.|
| call(method: string, data: rpc.Parcelable): Promise<void> | Sends agreed parcelable data to the CalleeAbility.|
| callWithResult(method: string, data: rpc.Parcelable): Promise<rpc.MessageSequence>| Sends agreed parcelable data to the CalleeAbility and obtains the agreed parcelable data returned by the CalleeAbility.|
G
Gloria 已提交
306
| release(): void | Releases the caller object.|
G
Gloria 已提交
307
| on(type: "release", callback: OnReleaseCallback): void | Callback invoked when the caller object is released.|
G
Gloria 已提交
308 309 310 311


### How to Develop

312
1. Request the **ohos.permission.DISTRIBUTED_DATASYNC** permission. For details, see [Declaring Permissions in the Configuration File](../security/accesstoken-guidelines.md#declaring-permissions-in-the-configuration-file).
G
Gloria 已提交
313

314 315 316
2. Display a dialog box to ask authorization from the user when the application is started for the first time. For details, see [Requesting User Authorization](../security/accesstoken-guidelines.md#requesting-user-authorization).

3. Create the CalleeAbility.
G
Gloria 已提交
317
   
318
     For the CalleeAbility, implement the callback to receive data and the methods to marshal and unmarshal data. When data needs to be received, use **on()** to register a listener. When data does not need to be received, use **off()** to deregister the listener.
G
Gloria 已提交
319

320
     1. Configure the launch type of the UIAbility.
G
Gloria 已提交
321

322
         Set **launchType** of the CalleeAbility to **singleton** in the **module.json5** file.
G
Gloria 已提交
323

324 325 326
         | JSON Field| Description|
         | -------- | -------- |
         | "launchType"| UIAbility launch type. Set this parameter to **singleton**.|
G
Gloria 已提交
327

328
         An example of the UIAbility configuration is as follows:
G
Gloria 已提交
329

G
Gloria 已提交
330
         
331 332 333
         ```json
         "abilities":[{
             "name": ".CalleeAbility",
334
             "srcEntry": "./ets/CalleeAbility/CalleeAbility.ts",
335 336 337 338
             "launchType": "singleton",
             "description": "$string:CalleeAbility_desc",
             "icon": "$media:icon",
             "label": "$string:CalleeAbility_label",
339
             "exported": true
340 341 342 343 344 345 346 347 348 349
         }]
         ```
     2. Import the **UIAbility** module.
        
         ```ts
         import Ability from '@ohos.app.ability.UIAbility';
         ```
     3. Define the agreed parcelable data.

         The data formats sent and received by the CallerAbility and CalleeAbility must be consistent. In the following example, the data formats are number and string.
G
Gloria 已提交
350

351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415
         
         ```ts
         export default class MyParcelable {
             num: number = 0;
             str: string = "";
         
             constructor(num, string) {
                 this.num = num;
                 this.str = string;
             }
         
             marshalling(messageSequence) {
                 messageSequence.writeInt(this.num);
                 messageSequence.writeString(this.str);
                 return true;
             }
         
             unmarshalling(messageSequence) {
                 this.num = messageSequence.readInt();
                 this.str = messageSequence.readString();
                 return true;
             }
         }
         ```
     4. Implement **Callee.on** and **Callee.off**.

           In the following example, the **MSG_SEND_METHOD** listener is registered in **onCreate()** of the UIAbility and deregistered in **onDestroy()**. After receiving parcelable data, the application processes the data and returns the data result. You need to implement processing based on service requirements.
           
         ```ts
         const TAG: string = '[CalleeAbility]';
         const MSG_SEND_METHOD: string = 'CallSendMsg';
         
         function sendMsgCallback(data) {
             console.info('CalleeSortFunc called');
         
             // Obtain the parcelable data sent by the CallerAbility.
             let receivedData = new MyParcelable(0, '');
             data.readParcelable(receivedData);
             console.info(`receiveData[${receivedData.num}, ${receivedData.str}]`);
         
             // Process the data.
             // Return the parcelable data result to the CallerAbility.
             return new MyParcelable(receivedData.num + 1, `send ${receivedData.str} succeed`);
         }
         
         export default class CalleeAbility extends Ability {
             onCreate(want, 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)}`);
                 }
             }
         }
         ```

4. Obtain the caller object and access the CalleeAbility.
G
Gloria 已提交
416 417 418 419 420 421
   1. Import the **UIAbility** module.
      
       ```ts
       import Ability from '@ohos.app.ability.UIAbility';
       ```
   2. Obtain the caller object.
G
Gloria 已提交
422

423
       The **context** attribute of the UIAbility implements **startAbilityByCall** to obtain the caller object for communication. The following example uses **this.context** to obtain the **context** attribute of the UIAbility, uses **startAbilityByCall** to start the CalleeAbility, obtain the caller object, and register the **onRelease** and **onRemoteStateChange** listeners of the CallerAbility. You need to implement processing based on service requirements.
G
Gloria 已提交
424

G
Gloria 已提交
425 426 427
       
       ```ts
       async onButtonGetRemoteCaller() {
G
Gloria 已提交
428 429
           var caller = undefined;
           var context = this.context;
G
Gloria 已提交
430 431 432 433 434 435 436
       
           context.startAbilityByCall({
               deviceId: getRemoteDeviceId(),
               bundleName: 'com.samples.CallApplication',
               abilityName: 'CalleeAbility'
           }).then((data) => {
               if (data != null) {
G
Gloria 已提交
437 438
                   caller = data;
                   console.info('get remote caller success');
439
                   // Register the onRelease listener of the CallerAbility.
G
Gloria 已提交
440
                   caller.onRelease((msg) => {
G
Gloria 已提交
441
                       console.info(`remote caller onRelease is called ${msg}`);
G
Gloria 已提交
442
                   })
G
Gloria 已提交
443
                   console.info('remote caller register OnRelease succeed');
444 445 446
                   // Register the onRemoteStateChange listener of the CallerAbility.
                   try {
                        caller.onRemoteStateChange((str) => {
G
Gloria 已提交
447
                            console.info('Remote state changed ' + str);
448 449
                        });
                    } catch (error) {
G
Gloria 已提交
450
                        console.info('Caller.onRemoteStateChange catch error, error.code: ${JSON.stringify(error.code)}, error.message: ${JSON.stringify(error.message)}');
451
                    }
G
Gloria 已提交
452 453
               }
           }).catch((error) => {
G
Gloria 已提交
454
               console.error(`get remote caller failed with ${error}`);
G
Gloria 已提交
455 456
           })
       }
G
Gloria 已提交
457 458
       ```

459
       For details about how to implement **getRemoteDeviceId()**, see [Starting UIAbility or ServiceExtensionAbility Across Devices (No Data Returned)](#starting-uiability-or-serviceextensionability-across-devices-no-data-returned).
G
Gloria 已提交
460

461 462
5. Sends agreed parcelable data to the CalleeAbility.
   1. The parcelable data can be sent to the CalleeAbility with or without a return value. The method and parcelable data must be consistent with those of the CalleeAbility. The following example describes how to send data to the CalleeAbility.
G
Gloria 已提交
463 464 465 466 467
      
       ```ts
       const MSG_SEND_METHOD: string = 'CallSendMsg';
       async onButtonCall() {
           try {
G
Gloria 已提交
468
               let msg = new MyParcelable(1, 'origin_Msg');
G
Gloria 已提交
469 470 471 472 473 474
               await this.caller.call(MSG_SEND_METHOD, msg);
           } catch (error) {
               console.info(`caller call failed with ${error}`);
           }
       }
       ```
475
   2. In the following, **CallWithResult** is used to send data **originMsg** to the CalleeAbility and assign the data processed by the **CallSendMsg** method to **backMsg**.
G
Gloria 已提交
476 477 478 479 480 481 482
      
       ```ts
       const MSG_SEND_METHOD: string = 'CallSendMsg';
       originMsg: string = '';
       backMsg: string = '';
       async onButtonCallWithResult(originMsg, backMsg) {
           try {
G
Gloria 已提交
483
               let msg = new MyParcelable(1, originMsg);
G
Gloria 已提交
484 485 486
               const data = await this.caller.callWithResult(MSG_SEND_METHOD, msg);
               console.info('caller callWithResult succeed');
       
G
Gloria 已提交
487 488
               let result = new MyParcelable(0, '');
               data.readParcelable(result);
G
Gloria 已提交
489 490 491 492 493 494 495 496 497 498
               backMsg(result.str);
               console.info(`caller result is [${result.num}, ${result.str}]`);
           } catch (error) {
               console.info(`caller callWithResult failed with ${error}`);
           }
       }
       ```

6. Release the caller object.

499 500
   When the caller object is no longer required, use **release()** to release it.

G
Gloria 已提交
501 502 503 504 505 506 507 508 509 510 511
   ```ts
   releaseCall() {
       try {
           this.caller.release();
           this.caller = undefined
           console.info('caller release succeed');
       } catch (error) {
           console.info(`caller release failed with ${error}`);
       }
   }
   ```