• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# UIAbility与UIAbility连接开发指南
2<!--Kit: Distributed Service Kit-->
3<!--Subsystem: DistributedSched-->
4<!--Owner: @hobbycao-->
5<!--Designer: @gsxiaowen-->
6<!--Tester: @hanjiawei-->
7<!--Adviser: @w_Machine_cc-->
8
9
10## 简介
11
12应用跨设备连接管理可以通过分布式操作系统,将用户拥有的多个设备整合为一个整体,实现设备与设备之间的能力互助,为用户提供比单设备更加高效的沉浸式体验。<!--Del-->例如通过手表相机应用拉起手机的相机功能并实现实时画面预览和遥控拍照。<!--DelEnd-->
13
14
15### 能力范围
16
17- 跨设备拉起应用:可以通过本设备应用,拉起其他组网设备的同应用,并进行协同作业。
18- 数据交互:实现跨设备数据传输<!--Del-->,包括文本信息、字节流、图片、传输流(三方应用仅支持文本信息交互能力)<!--DelEnd-->。
19
20
21### 亮点特征
22
23通过多样化的跨设备传输流特性,实现本设备相机拉起对端设备相机功能。为用户提供<!--Del-->实时预览对端设备摄像头的画面、<!--DelEnd-->文本信息交互<!--Del-->、接受回传照片、遥控拍照等<!--DelEnd-->能力。
24
25
26### 基本概念
27
28在进行应用跨设备连接管理开发前,开发者应了解以下基本概念:
29
30- **DMS**
31
32  DMS(Distributedsched Management Service)是分布式组件管理框架,提供分布式组件的管理能力。
33
34- **UIAbility**
35
36  描述应用程序的界面交互能力,负责管理应用界面的生命周期、用户交互以及界面渲染等任务。
37
38- **Extension**
39
40  用于扩展应用的功能或实现跨设备协同。它允许应用在后台运行某些任务,或者将部分功能迁移到其他设备上执行,从而实现分布式能力。
41
42<!--Del-->
43- **字节流**
44
45  字节流是数据类型为[ArrayBuffer](../arkts-utils/arraybuffer-object.md)类型的数据。可以被用于存储二进制数据,例如图像或音频数据。
46
47- **传输流**
48
49  可进行图片、视频流传输的媒体流。
50<!--DelEnd-->
51### 实现原理
52
53应用跨设备连接管理生长在分布式组件管理框架之上,在分布式组件管理框架上进行了JS对象型的封装,能通过分布式组件管理框架服务建立协同关系并进行应用间的连接,数据的交互能力由系统支持。
54
55**图1** 应用跨设备连接运行机制
56
57![how-abilityconnectmanager-works](figures/how-abilityconnectmanager-works.png)
58
59
60### 约束与限制
61
62- 设备间需要登录相同的华为账号。
63
64- 不同设备间只有相同bundleName的UIAbility应用才能进行协同。
65<!--Del-->
66- 字节流、图片以及传输流的能力仅支持系统应用。
67<!--DelEnd-->
68- 业务协同完毕后需及时结束协同状态。未申请长时任务的应用,在锁屏或退至后台5秒以上,会被结束掉协同生命周期。
69
70- 分布式组件管理框架在协同过程中不会对传输内容进行审查。涉及隐私敏感数据时,建议业务通过数据加密、弹框提醒等方式加强信息安全。
71
72
73## 环境准备
74
75### 环境要求
76
77可登录华为账号的设备A和设备B,设备间需要组网成功(双端登录同一个华为账号,并使用蓝牙连接)。
78
79
80### 搭建环境
81
821. 在PC上安装[DevEco Studio](https://developer.huawei.com/consumer/cn/download/deveco-studio),要求版本在4.1及以上。
832. 将public-SDK更新到API 18或以上<!--Del-->,更新SDK的具体操作可参见[更新指南]( ../tools/openharmony_sdk_upgrade_assistant.md)<!--DelEnd-->。
843. 用USB线缆将两台调测设备(设备A和设备B)连接到PC。
854. 打开设备A和设备B的蓝牙,互相识别,实现组网。
86
87
88### 检验环境是否搭建成功
89
90PC上执行shell命令:
91
92```shell
93hdc shell
94hidumper -s 4700 -a "buscenter -l remote_device_info"
95```
96
97组网成功时可显示组网设备数量的信息,如“remote device num = 1”。
98
99
100## 开发指导
101
102应用跨设备连接管理可以通过分布式操作系统,将用户拥有的多个设备作为一个整体,设备与设备之间取长补短、相互帮助,为用户提供比单设备更加高效、沉浸的体验。
103
104
105### 接口说明
106
107应用跨设备连接管理接口如下表所示。具体API说明详见API参考:[abilityConnectionManager](../reference/apis-distributedservice-kit/js-apis-distributed-abilityConnectionManager.md)。
108
109**表1** abilityConnectionManager接口功能介绍
110
111| 接口名 | 描述 |
112| -------- | -------- |
113| createAbilityConnectionSession(serviceName:&nbsp;string,&nbsp;context:&nbsp;Context,&nbsp;peerInfo:&nbsp;PeerInfo,&nbsp;connectOptions:&nbsp;ConnectOptions):&nbsp;number; | 创建应用间的会话。 |
114| destroyAbilityConnectionSession(sessionId:&nbsp;number):&nbsp;void; | 销毁应用间的会话。 |
115| connect(sessionId:&nbsp;number):&nbsp;Promise&lt;ConnectResult&gt;; | source侧进行ability的连接。 |
116| acceptConnect(sessionId:&nbsp;number,&nbsp;token:&nbsp;string):&nbsp;Promise&lt;void&gt;; | sink侧进行ability的连接。 |
117| disconnect(sessionId:&nbsp;number):&nbsp;void; | 断开ability的连接。 |
118| on(type:&nbsp;'connect'&nbsp;\| &nbsp;'disconnect'&nbsp;\| &nbsp;'receiveMessage'&nbsp;\| &nbsp;'receiveData',&nbsp;sessionId:&nbsp;number,&nbsp;callback:&nbsp;Callback&lt;EventCallbackInfo&gt;):&nbsp;void | 监听<!--Del-->connect/disconnect/receiveMessage/receiveData<!--DelEnd-->事件。 |
119| off(type:&nbsp;'connect'&nbsp;\| &nbsp;'disconnect'&nbsp;\| &nbsp;'receiveMessage'&nbsp;\| &nbsp;'receiveData',&nbsp;sessionId:&nbsp;number,&nbsp;callback?:&nbsp;Callback&lt;EventCallbackInfo&gt;):&nbsp;void | 取消<!--Del-->connect/disconnect/receiveMessage/receiveData<!--DelEnd-->事件的监听。 |
120| sendMessage(sessionId:&nbsp;number,&nbsp;msg:&nbsp;string):&nbsp;Promise&lt;void&gt;; | 发送文本信息。 |
121|<!--DelRow--> sendData(sessionId:&nbsp;number,&nbsp;data:&nbsp;ArrayBuffer):&nbsp;Promise&lt;void&gt;; | 发送字节流(仅支持系统应用调用)。 |
122|<!--DelRow--> sendImage(sessionId:&nbsp;number,&nbsp;image:&nbsp;image.PixelMap):&nbsp;Promise&lt;void&gt;; | 发送图片(仅支持系统应用调用)。 |
123|<!--DelRow--> createStream(sessionId:&nbsp;number,&nbsp;param:&nbsp;StreamParam):&nbsp;Promise&lt;number&gt;; | 创建传输流(仅支持系统应用调用)。 |
124|<!--DelRow--> destroyStream(sessionId:&nbsp;number):&nbsp;void; | 关闭传输流(仅支持系统应用调用)。 |
125
126
127### 开发步骤
128
129通过应用跨设备管理模块,设备A拉起并连接设备B上的应用。连接成功后,设备A和设备B通过on接口注册相应事件的回调监听。设备A或设备B通过sendMessage<!--Del-->、sendData、sendImage、createStream等<!--DelEnd-->接口发送消息<!--Del-->、字节流、传输流<!--DelEnd-->。对端通过监听到的回调信息进行后续协同业务。
130
131**导入AbilityConnectionManager模块文件**
132
133   ```ts
134   import { abilityConnectionManager } from '@kit.DistributedServiceKit';
135   ```
136
137
138**发现设备**
139
140设备A上的应用,需要发现并选择设备B的netWorkId来作为协同接口的入参。可调用分布式设备管理模块接口,进行对端设备的发现和选择,详情可参考[分布式设备管理模块](devicemanager-guidelines.md)进行开发。
141
142
143**应用间创建会话并进行连接**
144
145设备A和设备B在创建会话和连接时要执行的操作不同,接下来的开发步骤中,以设备A作为连接发起方,设备B作为连接接收端。
146
147**1.设备A**
148
149应用主动调用createAbilityConnectionSession()接口创建会话,获得sessionId。之后调用connect()方法启动ability会话连接(此时设备B上应用会被拉起)。
150
151  ```ts
152  import { abilityConnectionManager, distributedDeviceManager } from '@kit.DistributedServiceKit';
153  import { common } from '@kit.AbilityKit';
154  import { hilog } from '@kit.PerformanceAnalysisKit';
155
156  let dmClass: distributedDeviceManager.DeviceManager;
157
158  function initDmClass(): void {
159    try {
160      dmClass = distributedDeviceManager.createDeviceManager('com.example.remotephotodemo');
161    } catch (err) {
162      hilog.error(0x0000, 'testTag', 'createDeviceManager err: ' + JSON.stringify(err));
163    }
164  }
165  // 获取设备B的设备ID
166  function getRemoteDeviceId(): string | undefined {
167    initDmClass();
168    if (typeof dmClass === 'object' && dmClass !== null) {
169      hilog.info(0x0000, 'testTag', 'getRemoteDeviceId begin');
170      let list = dmClass.getAvailableDeviceListSync();
171      if (typeof (list) === 'undefined' || typeof (list.length) === 'undefined') {
172        hilog.info(0x0000, 'testTag', 'getRemoteDeviceId err: list is null');
173        return;
174      }
175      if (list.length === 0) {
176        hilog.info(0x0000, 'testTag', 'getRemoteDeviceId err: list is empty');
177        return;
178      }
179      return list[0].networkId;
180    } else {
181      hilog.info(0x0000, 'testTag', 'getRemoteDeviceId err: dmClass is null');
182      return;
183    }
184  }
185  // 定义设备B的协同信息
186  const peerInfo: abilityConnectionManager.PeerInfo = {
187    deviceId: getRemoteDeviceId(),
188    bundleName: 'com.example.remotephotodemo',
189    moduleName: 'entry',
190    abilityName: 'EntryAbility',
191    serviceName: 'collabTest'
192  };
193  const myRecord: Record<string, string> = {
194    "newKey1": "value1",
195  };
196
197  const options: Record<string, string> = {
198    'ohos.collabrate.key.start.option': 'ohos.collabrate.value.foreground',
199  };
200  // 定义连接选项
201  const connectOptions: abilityConnectionManager.ConnectOptions = {
202    needSendData: true,
203    startOptions: abilityConnectionManager.StartOptionParams.START_IN_FOREGROUND,
204    parameters: myRecord
205  };
206  let context = this.getUIContext().getHostContext();
207  try {
208    this.sessionId = abilityConnectionManager.createAbilityConnectionSession("collabTest", context, peerInfo, connectOptions);
209    hilog.info(0x0000, 'testTag', 'createSession sessionId is', this.sessionId);
210
211    abilityConnectionManager.connect(this.sessionId).then((ConnectResult) => {
212      if (!ConnectResult.isConnected) {
213        hilog.info(0x0000, 'testTag', 'connect failed');
214        return;
215      }
216    }).catch(() => {
217      hilog.error(0x0000, 'testTag', "connect failed");
218    })
219
220  } catch (error) {
221    hilog.error(0x0000, 'testTag', error);
222  }
223  ```
224
225**2.设备B**
226
227设备A的应用调用connect()后,设备B的应用会通过协同的方式被拉起,拉起时会触发协同生命周期函数onCollaborate(),可在该接口中配置createAbilityConnectionSession()接口以及acceptConnect()接口的调用。
228
229  ```ts
230  import { AbilityConstant, UIAbility, Want } from '@kit.AbilityKit';
231  import { abilityConnectionManager } from '@kit.DistributedServiceKit';
232  import { hilog } from '@kit.PerformanceAnalysisKit';
233
234  export default class EntryAbility extends UIAbility {
235    onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
236      hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onCreate');
237    }
238
239    onCollaborate(wantParam: Record<string, Object>): AbilityConstant.CollaborateResult {
240      hilog.info(0x0000, 'testTag', '%{public}s', 'on collaborate');
241      let param = wantParam["ohos.extra.param.key.supportCollaborateIndex"] as Record<string, Object>
242      this.onCollab(param);
243      return 0;
244    }
245
246    onCollab(collabParam: Record<string, Object>) {
247      const sessionId = this.createSessionFromWant(collabParam);
248      if (sessionId == -1) {
249        hilog.info(0x0000, 'testTag', 'Invalid session ID.');
250        return;
251      }
252      const collabToken = collabParam["ohos.dms.collabToken"] as string;
253      abilityConnectionManager.acceptConnect(sessionId, collabToken).then(() => {
254        hilog.info(0x0000, 'testTag', 'acceptConnect success');
255      }).catch(() => {
256        hilog.error("failed");
257      })
258    }
259
260    createSessionFromWant(collabParam: Record<string, Object>): number {
261      let sessionId = -1;
262      const peerInfo = collabParam["PeerInfo"] as abilityConnectionManager.PeerInfo;
263      if (peerInfo == undefined) {
264        return sessionId;
265      }
266
267      const options = collabParam["ConnectOptions"] as abilityConnectionManager.ConnectOptions;
268      options.needSendBigData = true;
269      options.needSendStream = true;
270      options.needReceiveStream = false;
271      try {
272        sessionId = abilityConnectionManager.createAbilityConnectionSession("collabTest", this.context, peerInfo, options);
273        AppStorage.setOrCreate('sessionId', sessionId);
274        hilog.info(0x0000, 'testTag', 'createSession sessionId is' + sessionId);
275      } catch (error) {
276        hilog.error(0x0000, 'testTag', error);
277      }
278      return sessionId;
279    }
280  }
281  ```
282
283**注册事件监听**
284
285在应用创建会话成功并获得sessionId后,开发者可调用on()方法进行对应事件的监听,通过触发回调函数的方式通知监听者,以便执行对应业务。
286<!--RP1-->
287  ```ts
288  import { abilityConnectionManager } from '@kit.DistributedServiceKit';
289  import { hilog } from '@kit.PerformanceAnalysisKit';
290
291  abilityConnectionManager.on("connect", this.sessionId,(callbackInfo) => {
292    hilog.info(0x0000, 'testTag', 'session connect, sessionId is', callbackInfo.sessionId);
293  });
294  abilityConnectionManager.on("disconnect", this.sessionId,(callbackInfo) => {
295    hilog.info(0x0000, 'testTag', 'session disconnect, sessionId is', callbackInfo.sessionId);
296  });
297  abilityConnectionManager.on("receiveMessage", this.sessionId,(callbackInfo) => {
298    hilog.info(0x0000, 'testTag', 'session receiveMessage, sessionId is', callbackInfo.sessionId);
299  });
300  abilityConnectionManager.on("receiveData", this.sessionId,(callbackInfo) => {
301    hilog.info(0x0000, 'testTag', 'session receiveData, sessionId is', callbackInfo.sessionId);
302  });
303  abilityConnectionManager.on("receiveImage", this.sessionId,(callbackInfo) => {
304    hilog.info(0x0000, 'testTag', 'session receiveImage, sessionId is', callbackInfo.sessionId);
305  });
306```
307<!--RP1End-->
308<!--Del-->
309**发送数据**
310<!--DelEnd-->
311**<!--Del-->1.<!--DelEnd-->发送消息**
312
313应用连接成功后,开发者可在设备A或者设备B上调用sendMessage()方法给对端应用发送文本信息。
314
315  ```ts
316  import { abilityConnectionManager } from '@kit.DistributedServiceKit';
317  import { hilog } from '@kit.PerformanceAnalysisKit';
318
319  abilityConnectionManager.sendMessage(this.sessionId, "message send success").then(() => {
320    hilog.info(0x0000, 'testTag', "sendMessage success");
321  }).catch(() => {
322    hilog.error(0x0000, 'testTag', "connect failed");
323  })
324  ```
325<!--Del-->
326**2.发送字节流数据**
327
328应用连接成功后,开发者可在设备A或者设备B上调用sendData()方法给对端应用发送字节数据(仅支持系统应用调用)。
329
330  ```ts
331  import { abilityConnectionManager } from '@kit.DistributedServiceKit';
332  import { hilog } from '@kit.PerformanceAnalysisKit';
333
334  let textEncoder = util.TextEncoder.create("utf-8");
335  const arrayBuffer  = textEncoder.encodeInto("data send success");
336
337  abilityConnectionManager.sendData(this.sessionId, arrayBuffer.buffer).then(() => {
338    hilog.info(0x0000, 'testTag', "sendMessage success");
339  }).catch(() => {
340    hilog.info(0x0000, 'testTag', "sendMessage failed");
341  })
342  ```
343
344**3.发送图片**
345
346应用连接成功后,开发者可在设备A或者设备B上调用sendImage()方法给对端应用发送图片(仅支持系统应用调用)。
347
348  ```ts
349  import { abilityConnectionManager } from '@kit.DistributedServiceKit';
350  import { hilog } from '@kit.PerformanceAnalysisKit';
351  import CameraService from '../model/CameraService';
352  import { photoAccessHelper } from '@kit.MediaLibraryKit';
353  import { image } from '@kit.ImageKit';
354  import { fileIo as fs } from '@kit.CoreFileKit';
355
356  try {
357    let photoSelectOptions = new photoAccessHelper.PhotoSelectOptions();
358    photoSelectOptions.MIMEType = photoAccessHelper.PhotoViewMIMETypes.IMAGE_TYPE;
359    photoSelectOptions.maxSelectNumber = 5;
360    let photoPicker = new photoAccessHelper.PhotoViewPicker();
361    photoPicker.select(photoSelectOptions).then((photoSelectResult) => {
362      if (!photoSelectResult) {
363        hilog.error(0x0000, 'testTag', 'photoSelectResult = null');
364      return;
365      }
366
367      let file = fs.openSync(photoSelectResult.photoUris[0], fs.OpenMode.READ_ONLY);
368      hilog.info(0x0000, 'testTag', 'file.fd:' + file.fd);
369
370      let imageSourceApi: image.ImageSource = image.createImageSource(file.fd);
371      if (imageSourceApi) {
372        imageSourceApi.createPixelMap().then((pixelMap) => {
373          abilityConnectionManager.sendImage(this.sessionId, pixelMap)
374        });
375      } else {
376        hilog.info(0x0000, 'testTag', 'imageSourceApi is undefined');
377      }
378    })
379  } catch (error) {
380    hilog.error(0x0000, 'testTag', 'photoPicker failed with error: ' + JSON.stringify(error));
381  }
382  ```
383
384**4.发送传输流**
385
386应用连接成功后,开发者可在设备A或者设备B上调用createStream()方法创建传输流(仅支持系统应用调用),之后调用startStream()方法传输流给对端设备。
387
388  ```ts
389  import { abilityConnectionManager } from '@kit.DistributedServiceKit';
390  import { hilog } from '@kit.PerformanceAnalysisKit';
391
392  hilog.info(0x0000, 'testTag', 'startStream');
393  abilityConnectionManager.createStream(sessionId ,{name: 'receive', role: 0}).then(async (streamId) => {
394    let surfaceParam: abilityConnectionManager.SurfaceParam = {
395      width: 640,
396      height: 480,
397      format: 1
398    }
399    let surfaceId = abilityConnectionManager.getSurfaceId(streamId, surfaceParam);
400    hilog.info(0x0000, 'testTag', 'surfaceId is'+surfaceId);
401    AppStorage.setOrCreate<string>('surfaceId', surfaceId);
402    await CameraService.initCamera(surfaceId, 0);
403    abilityConnectionManager.startStream(streamId);
404  })
405  ```
406<!--DelEnd-->
407**结束协同**
408
409业务协同完毕后需及时结束协同状态。若是后续短期内还有协同需要,可调用disconnect()方法断开应用间的连接,保留sessionId,以便下次继续使用该sessionId进行连接。若是短期无需使用协同业务,可直接调用destroyAbilityConnectionSession()接口销毁会话,此时会自动断开连接。
410
411  ```ts
412  import { abilityConnectionManager } from '@kit.DistributedServiceKit';
413  import { hilog } from '@kit.PerformanceAnalysisKit';
414
415  hilog.info(0x0000, 'testTag', 'disconnectRemoteAbility begin');
416  if (this.sessionId == -1) {
417    hilog.info(0x0000, 'testTag', 'Invalid session ID.');
418  return;
419  }
420  abilityConnectionManager.disconnect(this.sessionId);
421
422  hilog.info(0x0000, 'testTag', 'destroyAbilityConnectionSession called');
423  abilityConnectionManager.destroyAbilityConnectionSession(this.sessionId);
424  ```
425
426
427### 调测验证
428
429应用侧开发完成后,可在设备A和设备B上安装应用,测试步骤如下:
430
4311. 点击设备A应用的“连接”按钮,此时设备B上的应用被拉起。
4322. 点击设备A应用的“sendMessage”按钮,此时设备B上的应用会触发on()方法的回调,接受该字符串。
433<!--Del-->
4343. 点击设备A应用的“sendData”按钮,此时设备B上的应用会触发on()方法的回调,接受该字节流。
4354. 点击设备A应用的“sendImage”按钮,此时设备B上的应用会触发on()方法的回调,接受该图片。
4365. 点击设备A应用的“启动传输流”按钮,此时设备B上的应用会触发on()方法的回调,接受传输流内容。
437<!--DelEnd-->
4386. 点击设备A或设备B应用的“disconnect”按钮,此时双端会断开连接,触发connect()接口的回调,将断连信息上报给双端应用。
439
440## 常见问题
441
442### 设备A应用无法拉起设备B应用
443
444**可能原因**
445
446- 【原因1】:设备间没有相互组网,导致设备A发起连接时,createAbilityConnectionSession()接口中的peerInfo.deviceId属性未设置正确。
447
448- 【原因2】:有多台设备相互组网,设备A发起连接时,createAbilityConnectionSession()接口中的peerInfo.deviceId属性设置为其他设备的deviceId,未正确指定到B设备上。
449
450**解决措施**
451
452- 针对原因1,设备A和设备B开启USB调试功能,用USB线连接设备和PC。执行shell命令:
453
454  ```shell
455  hdc shell
456  hidumper -s 4700 -a "buscenter -l remote_device_info"
457  ```
458  回显信息为“remote device num = 0”即为组网失败,请确保登录同一华为账号并使用蓝牙连接。组网成功时可显示组网设备数量的信息,如“remote device num = 1”。
459
460- 针对原因2,查询并选择指定设备时,添加设备选择列表,确保指定到期望的设备。
461
462### 应用锁屏或者退后台一段时间后,正在执行的协同业务被断开
463
464**可能原因**
465
466应用在协同过程中,DMS会对应用的生命周期进行监听。发生锁屏、退后台操作持续五秒后,未申请长时任务的应用会被结束协同状态。
467
468**解决措施**
469
470应用[申请长时任务](../task-management/continuous-task.md),消除此限制。