文档管理中心

碰一碰实现设备间交互

概述

从API版本26.0.0开始,新增碰一碰能力,提供顶端触碰方式,实现设备间的快速连接并进行数据交互。

在日常使用中,用户经常需要在两台设备之间快速传输数据或协同操作。例如,在聚会时想要交换名片、在游戏中想要邀请好友加入、在购物时想要分享商品信息等。传统的方式通常需要通过蓝牙配对、扫码添加好友等繁琐步骤,操作复杂且耗时。

碰一碰提供了一种简单直观的解决方案:用户只需将两台设备轻轻一碰,即可快速建立连接并进行数据传输。这种方式无需复杂的配对过程,操作简单自然,大大提升了用户体验。

实现原理

碰一碰基于设备的近场通信能力,通过顶端碰的方式触发连接建立。当两台设备相碰时会自动识别并建立连接,然后通过回调机制通知应用。应用可以通过Connection对象发送消息,并通过回调接收消息。

约束与限制

设备限制

设备需要打开WLAN和蓝牙开关。

权限限制

需要申请权限ohos.permission.KNOCK_COLLABORATION,该能力受限开放。权限申请方式请参见申请使用受限权限。

接口说明

碰一碰关键接口如下表所示。具体API说明详见接口文档。

展开
接口名 描述
onServiceInteraction(config: CollaborationConfig, callbacks: ServiceInteractionCallback): void 注册碰一碰回调方法。
offServiceInteraction(config: CollaborationConfig, callbacks?: ServiceInteractionCallback): void 取消注册碰一碰回调方法。
Connection.sendMessage(data: ArrayBuffer): Promise<void> 向连接中的对端设备传递信息。使用Promise异步回调。
Connection.disconnect(): Promise<void> 断开与对端设备的连接。使用Promise异步回调。

开发步骤

  1. 导入serviceInteraction模块以及其他必要的工具模块。

    收起
    自动换行
    深色代码主题
    复制
    1. import { serviceInteraction } from '@kit.ServiceCollaborationKit';
    2. import { hilog } from '@kit.PerformanceAnalysisKit';
    3. import { util } from '@kit.ArkTS';
  2. 创建CollaborationConfig对象,配置触发类型、窗口ID等参数。

    收起
    自动换行
    深色代码主题
    复制
    1. const config: serviceInteraction.CollaborationConfig = {
    2. triggerType: serviceInteraction.TriggerType.KNOCK,
    3. windowId: 1, // 此处示例传入1,具体开发场景可通过this.getUIContext().getWindowId(); 来获取windowId。
    4. appLinking: 'https://example.com/app',
    5. timeOut: 10 // 可选,默认值为20秒
    6. };
  3. 通过onServiceInteraction接口注册回调方法,处理设备连接、断连、消息接收等事件。

    收起
    自动换行
    深色代码主题
    复制
    1. const callback: serviceInteraction.ServiceInteractionCallback = {
    2. onDeviceConnect: (connection: serviceInteraction.Connection) => {
    3. // 设备连接成功
    4. hilog.info(0, 'MEMOMOCK', '设备连接成功,connectId: ' + connection.connectId);
    5. },
    6. onDeviceDisconnect: (connection: serviceInteraction.Connection, retCode: number) => {
    7. // 设备断开连接
    8. hilog.info(0, 'MEMOMOCK', '设备断开连接,connectId: ' + connection.connectId + ',retCode: ' + retCode);
    9. },
    10. onMessageReceive: (connection: serviceInteraction.Connection, data: ArrayBuffer) => {
    11. // 收到对端消息
    12. hilog.info(0, 'MEMOMOCK', '收到消息,connectId: ' + connection.connectId);
    13. },
    14. onConnectFail: (retCode: number) => {
    15. // 连接失败
    16. hilog.error(0, 'MEMOMOCK', '连接失败,retCode: ' + retCode);
    17. }
    18. };
    19. try {
    20. serviceInteraction.onServiceInteraction(config, callback);
    21. hilog.info(0, 'MEMOMOCK', '注册成功');
    22. } catch (error) {
    23. hilog.error(0, 'MEMOMOCK', '注册失败,message:' + error.message);
    24. }
  4. 通过Connection对象的sendMessage方法向对端设备发送消息。

    收起
    自动换行
    深色代码主题
    复制
    1. // 发送消息
    2. const message: string = 'Hello from device A';
    3. const textEncoder: util.TextEncoder = new util.TextEncoder();
    4. const uint8Array = new Uint8Array(textEncoder.encodeInto(message).length);
    5. textEncoder.encodeIntoUint8Array(message, uint8Array);
    6. try {
    7. connection.sendMessage(uint8Array.buffer).then(() => {
    8. hilog.info(0, 'MEMOMOCK', '消息发送成功');
    9. }).catch((error: Error) => {
    10. hilog.error(0, 'MEMOMOCK', '消息发送失败: ' + error.message);
    11. });
    12. } catch (error) {
    13. hilog.error(0, 'MEMOMOCK', '发送失败,message:' + error.message);
    14. }
  5. 通过Connection对象的disconnect方法断开与对端设备的连接。

    收起
    自动换行
    深色代码主题
    复制
    1. try {
    2. connection.disconnect().then(() => {
    3. hilog.info(0, 'MEMOMOCK', '断开连接成功');
    4. }).catch((error: Error) => {
    5. hilog.error(0, 'MEMOMOCK', '断开连接失败: ' + error.message);
    6. });
    7. } catch (error) {
    8. hilog.error(0, 'MEMOMOCK', '断开失败,message:' + error.message);
    9. }
  6. 通过offServiceInteraction接口取消注册回调方法,停止接收回调事件。

    收起
    自动换行
    深色代码主题
    复制
    1. // 取消注册回调
    2. try {
    3. serviceInteraction.offServiceInteraction(config);
    4. hilog.info(0, 'MEMOMOCK', '取消注册成功');
    5. } catch (error) {
    6. hilog.error(0, 'MEMOMOCK', '取消注册失败,message:' + error.message);
    7. }

调测验证

开发完成后,可以通过以下步骤验证功能:

  1. 准备两台设备,并确保WLAN和蓝牙开关已打开。
  2. 在两台设备上分别运行应用。
  3. 将两台设备顶端轻轻一碰,观察是否触发连接回调。
  4. 检查连接状态是否正确显示。
  5. 在一台设备上发送消息,观察另一台设备是否收到消息。
  6. 断开连接,观察断开回调是否正确触发。
  7. 检查hilog日志,确认所有操作是否正常执行。

完整示例

以下示例展示了如何使用碰一碰功能实现双端设备交互的场景,包括消息发送和接收功能。

收起
自动换行
深色代码主题
复制
  1. import { serviceInteraction } from '@kit.ServiceCollaborationKit';
  2. import { hilog } from '@kit.PerformanceAnalysisKit';
  3. import { util } from '@kit.ArkTS';
  4. /**
  5. * 连接信息封装
  6. */
  7. interface ConnectionInfo {
  8. connection: serviceInteraction.Connection;
  9. deviceName: string;
  10. }
  11. /**
  12. * 核心流程:
  13. * 1. 注册监听:只注册一次onServiceInteraction
  14. * 2. 处理回调:每次设备相碰会触发回调,返回新的Connection
  15. * 3. 多Connection处理:使用connectionList管理多个连接,每个连接独立设置回调
  16. */
  17. @Entry
  18. @Component
  19. struct LuckyCardExchangePage {
  20. @State connectionStatus: string = '未连接';
  21. @State connectionList: ConnectionInfo[] = [];
  22. @State windowId: number = 0;
  23. private appLinking: string = 'https://example.com/luckycard/exchange';
  24. @State sendMessage: string = '';
  25. @State receivedMessage: string = '';
  26. aboutToAppear() {
  27. this.windowId = this.getUIContext().getWindowId() as number;
  28. this.registerServiceInteraction();
  29. }
  30. aboutToDisappear() {
  31. this.unregisterServiceInteraction();
  32. this.disconnectAll();
  33. }
  34. /**
  35. * 注册碰一碰监听
  36. */
  37. private registerServiceInteraction(): void {
  38. const config: serviceInteraction.CollaborationConfig = {
  39. triggerType: serviceInteraction.TriggerType.KNOCK,
  40. windowId: this.windowId,
  41. appLinking: this.appLinking
  42. };
  43. const callback: serviceInteraction.ServiceInteractionCallback = {
  44. onDeviceConnect: (connection: serviceInteraction.Connection) => {
  45. this.addConnection(connection);
  46. },
  47. onDeviceDisconnect: (connection: serviceInteraction.Connection, retCode: number) => {
  48. this.onDisconnected(connection, retCode);
  49. },
  50. onMessageReceive: (connection: serviceInteraction.Connection, data: ArrayBuffer) => {
  51. this.onMessageReceived(connection, data);
  52. },
  53. onConnectFail: (retCode: number) => {
  54. hilog.info(0, 'MEMOMOCK', '连接失败, 错误码是:' + retCode);
  55. },
  56. };
  57. try {
  58. serviceInteraction.onServiceInteraction(config, callback);
  59. } catch (error) {
  60. hilog.error(0, 'MEMOMOCK', '注册失败,message:' + error.message);
  61. }
  62. hilog.info(0, 'MEMOMOCK', '已注册服务交互监听');
  63. }
  64. /**
  65. * 取消注册监听
  66. */
  67. private unregisterServiceInteraction(): void {
  68. const config: serviceInteraction.CollaborationConfig = {
  69. triggerType: serviceInteraction.TriggerType.KNOCK,
  70. windowId: this.windowId,
  71. appLinking: this.appLinking
  72. };
  73. try {
  74. serviceInteraction.offServiceInteraction(config);
  75. } catch (error) {
  76. hilog.error(0, 'MEMOMOCK', '取消注册失败,message:' + error.message);
  77. }
  78. hilog.info(0, 'MEMOMOCK', '已取消服务交互监听');
  79. }
  80. /**
  81. * 添加新连接
  82. */
  83. private addConnection(connection: serviceInteraction.Connection): void {
  84. // 检查是否已存在相同connectId的连接
  85. const exists = this.connectionList.some(
  86. item => item.connection.connectId === connection.connectId
  87. );
  88. if (exists) {
  89. hilog.info(0, 'MEMOMOCK', '连接已存在,connectId: ' + connection.connectId);
  90. return;
  91. }
  92. // 创建新的连接信息
  93. const connectionInfo: ConnectionInfo = {
  94. connection: connection,
  95. deviceName: '设备' + (this.connectionList.length + 1)
  96. };
  97. // 添加到连接列表
  98. this.connectionList.push(connectionInfo);
  99. this.updateConnectionStatus();
  100. hilog.info(0, 'MEMOMOCK', '新连接成功,connectId: ' + connection.connectId +
  101. ',当前连接数: ' + this.connectionList.length);
  102. }
  103. /**
  104. * 处理断开连接
  105. */
  106. private onDisconnected(connection: serviceInteraction.Connection, retCode: number): void {
  107. const index = this.connectionList.findIndex(
  108. item => item.connection.connectId === connection.connectId
  109. );
  110. if (index > -1) {
  111. this.connectionList.splice(index, 1);
  112. hilog.info(0, 'MEMOMOCK', '连接已断开,connectId: ' + connection.connectId + ',retCode: ' + retCode);
  113. }
  114. this.updateConnectionStatus();
  115. }
  116. /**
  117. * 处理消息接收
  118. */
  119. private onMessageReceived(connection: serviceInteraction.Connection, data: ArrayBuffer): void {
  120. const connectionInfo = this.connectionList.find(
  121. item => item.connection === connection
  122. );
  123. const deviceName = connectionInfo ? connectionInfo.deviceName : '未知设备';
  124. hilog.info(0, 'MEMOMOCK', '收到消息,设备: ' + deviceName + ',connectId: ' + connection.connectId);
  125. // 解码消息内容
  126. const decoder = util.TextDecoder.create('utf-8');
  127. const str = decoder.decodeToString(new Uint8Array(data));
  128. hilog.info(0, 'MEMOMOCK', 'data: ' + data + ',str: ' + str);
  129. // 保存接收到的消息
  130. this.receivedMessage = str;
  131. }
  132. /**
  133. * 发送消息
  134. */
  135. private sendToAllDevices(): void {
  136. if (this.connectionList.length === 0) {
  137. hilog.warn(0, 'MEMOMOCK', '没有已连接的设备');
  138. return;
  139. }
  140. this.connectionList.forEach((connection) => {
  141. let textEncoder: util.TextEncoder = new util.TextEncoder();
  142. let uint8Array = new Uint8Array(textEncoder.encodeInto(this.sendMessage).length);
  143. textEncoder.encodeIntoUint8Array(this.sendMessage, uint8Array);
  144. try {
  145. connection.connection.sendMessage(uint8Array.buffer);
  146. } catch (error) {
  147. hilog.error(0, 'MEMOMOCK', '发送失败,message:' + error.message);
  148. }
  149. });
  150. }
  151. /**
  152. * 断开所有连接
  153. */
  154. private disconnectAll(): void {
  155. this.connectionList.forEach((connection) => {
  156. try {
  157. connection.connection.disconnect();
  158. } catch (error) {
  159. hilog.error(0, 'MEMOMOCK', '断开失败,message:' + error.message);
  160. }
  161. });
  162. this.connectionList = [];
  163. this.updateConnectionStatus();
  164. }
  165. /**
  166. * 更新连接状态
  167. */
  168. private updateConnectionStatus(): void {
  169. this.connectionStatus = this.connectionList.length > 0
  170. ? '已连接 ' + this.connectionList.length + ' 台设备'
  171. : '未连接';
  172. }
  173. build() {
  174. Column() {
  175. Text('碰一碰协同')
  176. .fontSize(24)
  177. .fontWeight(FontWeight.Bold)
  178. .margin({ top: 40, bottom: 20 })
  179. Text('连接状态: ' + this.connectionStatus)
  180. .fontSize(16)
  181. .margin({ bottom: 20 })
  182. if (this.connectionList.length > 0) {
  183. Text('已连接设备:')
  184. .fontSize(14)
  185. .margin({ bottom: 10 })
  186. ForEach(this.connectionList, (item: ConnectionInfo) => {
  187. Text(item.deviceName + ' (ID: ' + item.connection.connectId + ')')
  188. .fontSize(14)
  189. .margin({ bottom: 5 })
  190. })
  191. }
  192. Text('使用说明: 将两台设备相碰即可建立连接')
  193. .fontSize(12)
  194. .fontColor(Color.Gray)
  195. .margin({ top: 20 })
  196. if (this.connectionList.length > 0) {
  197. TextInput()
  198. .margin({ top: 20, bottom: 20 })
  199. .onChange((value) => {
  200. this.sendMessage = value;
  201. })
  202. Button('给已连接设备发送消息')
  203. .onClick(() => {
  204. this.sendToAllDevices();
  205. })
  206. Button('断连')
  207. .margin({ top: 10 })
  208. .onClick(() => {
  209. this.disconnectAll();
  210. })
  211. }
  212. if (this.receivedMessage != '' && this.connectionList.length > 0) {
  213. TextArea({
  214. text: this.receivedMessage
  215. })
  216. .margin({ top: 20, bottom: 20 })
  217. }
  218. }
  219. .width('100%')
  220. .height('100%')
  221. .padding(20)
  222. }
  223. }