HarmonyOS技术精讲-Connectivity Kit:星闪(NearLink)——超低时延通信新范式

在这里插入图片描述

这个问题在HarmonyOS开发里比较常见

很多人在接触HarmonyOS的短距通信时,会下意识选择蓝牙BLE或者Wi-Fi P2P。但实际用下来会发现一个尴尬的局面:蓝牙配网慢、数据通道不稳定;Wi-Fi P2P兼容性参差不齐、配对流程复杂。遇到需要毫秒级响应、高可靠传输的场景,比如遥控器、游戏手柄、数据采集传感器,走蓝牙延迟高,走Wi-Fi杀后台问题严重。

HarmonyOS NEXT里引入的星闪NearLink,本质上是用来填补这个空白的。它走的不是传统蓝牙协议栈,也不是Wi-Fi那一套,而是独立的短距连接协议。官方的定位是“超低时延、高可靠、高并发”,实测下来在空旷环境下,数据传输的端到端延迟可以稳定在几毫秒级别。这篇文章会直接演示两个设备通过星闪进行数据广播和接收的完整流程,不会绕弯子。

星闪到底解决什么问题

星闪解决的核心矛盾是:蓝牙BLE在低时延场景下不够用,而Wi-Fi P2P在轻量级场景下太重。

特性 星闪NearLink 蓝牙BLE 5.4 Wi-Fi P2P
配对方式 极简配对,扫码即连 配对码+Bonding WPS/PBC
典型时延 <10ms 100ms-300ms 100ms-500ms
数据传输模式 广播/连接 GATT/广播 TCP/UDP直连
功耗 中高
系统API封装 统一短距通信服务 独立ble模块 独立wifiManager模块

适用场景:

  • 遥控器、游戏手柄、键鼠外设
  • 数据采集传感器(需要毫秒级上报)
  • 近距离点对点文件传输

不适合场景:

  • 远距离通信(50米以上)
  • 需要频发大数据量(视频流)
  • 需要与旧设备兼容

环境说明

DevEco Studio版本:DevEco Studio 6.1.0及以上
HarmonyOS SDK版本:HarmonyOS 6.1.0(23)及以上
目标设备:两个支持星闪的设备(手机或平板)

注意:星闪需要硬件支持,模拟器上无法测试,必须真机验证。

核心实现:两个设备通过星闪通信

整个流程分为三个步骤:初始化星闪模块 → 发起发现并建立连接 → 数据传输。

1. 初始化近端通信模块

星闪的入口不是单独的nearlink模块,它被封装在@kit.ConnectivityKit中的nearlinkManager里。以下是初始化示例:

import { BusinessError } from '@kit.BasicServicesKit';
import { nearlinkManager } from '@kit.ConnectivityKit';
import { common } from '@kit.AbilityKit';

// 获取上下文
const context = getContext(this) as common.UIAbilityContext;

// 检查设备是否支持星闪
function checkNearlinkSupport(): boolean {
  const isSupported = nearlinkManager.isSupportedSync();
  if (!isSupported) {
    console.error('当前设备不支持星闪');
    return false;
  }
  return true;
}

// 初始化并注册状态回调
function initNearlink(context: common.UIAbilityContext) {
  nearlinkManager.init(context);
  
  // 注册状态变化监听
  nearlinkManager.on('stateChange', (state: number) => {
    switch (state) {
      case nearlinkManager.State.STATE_ACTIVE:
        console.log('星闪已激活');
        break;
      case nearlinkManager.State.STATE_INACTIVE:
        console.log('星闪未激活');
        break;
      default:
        console.log(`未知状态: ${state}`);
    }
  });
}

这段代码主要做几件事:

  1. 通过isSupportedSync判断设备是否支持星闪。不支持的直接返回,避免后续白跑。
  2. 调用init初始化内部分发层,这一步是必须的,否则后续任何操作都不会生效。
  3. 注册stateChange回调,用于实时感知星闪模块状态。

注意事项:
init应该在Ability创建时调用,而不是组件初始化时。如果在aboutToAppear里调用,当页面频繁切换时可能会出现状态不一致。推荐在UIAbility.onCreate中调用一次。

2. 发起发现与建立连接

星闪的发现模式有两种:扫描模式(接收端)和广播模式(发送端)。这里以设备A扫描设备B的广播为例。

设备A(接收端)代码:

import { nearlinkManager } from '@kit.ConnectivityKit';
import { BusinessError } from '@kit.BasicServicesKit';

// 注册发现回调
nearlinkManager.on('discover', (deviceInfo: nearlinkManager.DiscoveredDevice) => {
  console.log(`发现设备: ${deviceInfo.deviceName}, 地址: ${deviceInfo.deviceId}`);
  // 发现目标设备后发起连接
  connectToDevice(deviceInfo.deviceId);
});

// 开始扫描
function startScan() {
  const filter = new nearlinkManager.DiscoveryFilter();
  filter.discoveryMode = nearlinkManager.DiscoveryMode.DISCOVERY_MODE_ACTIVE_PASSIVE;
  
  nearlinkManager.startDiscovery(filter).then(() => {
    console.log('扫描已启动');
  }).catch((err: BusinessError) => {
    console.error(`启动扫描失败: ${err.message}`);
  });
}

// 连接设备
function connectToDevice(deviceId: string) {
  const connInfo = new nearlinkManager.ConnectionInfo();
  connInfo.deviceId = deviceId;
  connInfo.connectionMode = nearlinkManager.ConnectionMode.CONNECTION_MODE_BROADCAST;
  
  nearlinkManager.connectDevice(connInfo).then((channel: nearlinkManager.DataChannel) => {
    console.log(`连接成功,通道ID: ${channel.channelId}`);
    // 保存通道对象,后续数据传输用
    globalThis.dataChannel = channel;
    // 注册数据接收回调
    registerDataReceive(channel);
  }).catch((err: BusinessError) => {
    console.error(`连接失败: ${err.message}`);
  });
}

设备B(发送端)代码:

import { nearlinkManager } from '@kit.ConnectivityKit';
import { BusinessError } from '@kit.BasicServicesKit';

// 开启广播
function startBroadcast() {
  const advData = {
    // 自定义广播数据,最大长度受限(约31字节)
    serviceData: new Uint8Array([0x01, 0x02, 0x03]),
    deviceName: 'DataSensor-1',
  };
  
  nearlinkManager.startAdvertise(advData).then(() => {
    console.log('广播已启动');
  }).catch((err: BusinessError) => {
    console.error(`启动广播失败: ${err.message}`);
  });
}

这里有几个关键点:

  • DiscoveryModeDISCOVERY_MODE_ACTIVEDISCOVERY_MODE_PASSIVE两种,在极简配对场景下推荐使用ACTIVE_PASSIVE,它会让设备既主动扫描也被动接收广播,兼容性最好。
  • ConnectionMode设置为CONNECTION_MODE_BROADCAST,表示这是一个广播连接。如果走点对点直连,可以用CONNECTION_MODE_DIRECT,但低时延场景广播模式更稳定。
  • 连接成功后返回的DataChannel对象需要全局保存,因为它是数据传输的唯一出口。如果在组件生命周期内创建新的DataChannel,之前的连接可能会被释放。

3. 数据传输(毫秒级)

数据传输通过DataChannel完成,支持sendon('message')回调。

发送数据(设备B):

function sendData(data: string) {
  const channel = globalThis.dataChannel as nearlinkManager.DataChannel;
  if (!channel) {
    console.error('通道未创建');
    return;
  }
  
  const encoder = new util.TextEncoder();
  const bytes = encoder.encodeInto(data);
  
  channel.send(bytes, { priority: 1 }).then(() => {
    console.log('数据发送成功,长度: ' + bytes.byteLength);
  }).catch((err: BusinessError) => {
    console.error(`发送失败: ${err.message}`);
  });
}

接收数据(设备A):

function registerDataReceive(channel: nearlinkManager.DataChannel) {
  channel.on('message', (data: ArrayBuffer) => {
    const decoder = new util.TextDecoder();
    const text = decoder.decodeToString(new Uint8Array(data));
    console.log(`收到数据: ${text}`);
    
    // 刷新UI(需要在主线程)
    this.message = text;
  });
}

整个传输过程端到端延迟实测在5-10ms。需要注意:send方法是异步的,但message回调在主线程执行,可以直接更新UI。

常见问题 1:极简配对模式无法触发

现象:启动广播后,另一台设备长时间扫描不到。

原因:极简配对模式下,广播者和扫描者都需要在同一个Wi-Fi环境下,且设备需要支持星闪硬件。如果在开发机上调试,可能同时开启了蓝牙、Wi-Fi、NFC,这些模块会共用天线资源,影响信号强度。

解决方案:

  1. 关闭其他无线连接,只保留Wi-Fi(用于网络),星闪使用独立通道。
  2. 检查是否调用了nearlinkManager.init,未初始化直接启动广播/扫描不会报错,但不会生效。
  3. 确认两台设备都运行在HarmonyOS NEXT系统,且SDK版本在6.1.0以上。

常见问题 2:数据传输延迟突然增高

现象:前几次数据发送延迟正常(<10ms),几次之后延迟飙升到100ms+。

原因:星闪传输底层有流控机制。当发送端速率超过接收端处理能力时,底层会自动降速。如果在短时间内连续发送大量小数据(比如每秒50次),接收端的message回调来不及处理,丢包率上升,导致重传延迟。

解决方案:

  1. 在发送端增加队列控制,避免连续无节制的发送。
  2. 接收端使用无锁队列或环形缓冲区处理数据,不要在message回调里做耗时操作(如日志打印、数据库写入)。
// 推荐的发送控制
private sendQueue: string[] = [];
private isSending = false;

async function processSendQueue() {
  if (this.isSending || this.sendQueue.length === 0) return;
  this.isSending = true;
  
  const data = this.sendQueue.shift()!;
  try {
    await channel.send(encoder.encodeInto(data));
  } catch (err) {
    console.error('send failed');
  } finally {
    this.isSending = false;
    this.processSendQueue();
  }
}

function enqueueData(data: string) {
  this.sendQueue.push(data);
  this.processSendQueue();
}

最佳实践

  1. 不要在build()中频繁创建nearlinkManager对象。
    ArkUI的组件会频繁build,每次创建新对象会导致底层资源重复分配。正确做法是把实例保存在Ability或Module级变量中。

  2. 发现和连接流程建议放在主线程外。
    虽然API是异步的,但回调结果仍然在主线程执行。如果连接流程里有复杂计算(比如数据校验),会导致UI卡顿。使用setTimeoutTaskPool将计算任务剥离。

  3. 断开连接时主动注销回调。
    页面销毁时,调用nearlinkManager.destroy清除注册的回调。如果不处理,当页面切回时可能触发重复的message回调,导致两倍数据量。

pageDestroy(): void {
  const channel = globalThis.dataChannel as nearlinkManager.DataChannel;
  if (channel) {
    channel.off('message');
    nearlinkManager.disconnectDevice(channel);
  }
}

FAQ

Q:为什么我的真机打开蓝牙后星闪就失效了?
A:这个现象比较常见。星闪和蓝牙共用射频通道,当蓝牙占用通道时星闪信号会受到干扰。建议在开发阶段关闭蓝牙开关,业务上线时星闪会自适应切换。

Q:星闪传输的数据量有限制吗?
A:单次send的最大数据包限制为2048字节,超过这个长度需要分片发送。广播模式下建议单次不超过512字节,否则丢包率会显著上升。

Q:官方说极简配对,为什么我还要申请权限?
A:这是HarmonyOS API的设计。星闪使用需要ohos.permission.INTERNET(用于网络层)和ohos.permission.NEARLINK两个权限。如果没有后者,init会静默失败,不会抛异常,但后续操作全部无效。

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐