news 2026/7/17 1:19:48

《HarmonyOS技术精讲-NearLink Kit(星闪服务)》第2篇:设备发现——星闪设备发现机制与API详解

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
《HarmonyOS技术精讲-NearLink Kit(星闪服务)》第2篇:设备发现——星闪设备发现机制与API详解

《HarmonyOS技术精讲-NearLink Kit(星闪服务)》第2篇:设备发现——星闪设备发现机制与API详解

问题

HarmonyOS NEXT 开发里,startDiscovery这个 API 经常被误用。很多人第一次接触NearLink Kit的设备发现能力时,会发现官方示例能运行,但放到实际项目里就遇到各种问题:扫描结果重复、回调不触发、页面离开后扫描还在跑。这个功能本身不复杂,但真正麻烦的是生命周期管理和扫描结果的去重处理。

它解决什么问题

NearLink Kit是 HarmonyOS 提供的星闪通信服务框架。设备发现是星闪应用的第一步——扫描周边的星闪设备并获取设备的基础信息,比如设备名称、设备ID、信号强度等。

特性传统蓝牙扫描星闪设备发现
扫描模式被动扫描主动+被动混合
发现速度慢(约3-5秒)快(约1-2秒)
设备信息量有限更丰富
过滤能力有限支持多维度过滤

有两个典型场景适合用这个 API:

  • 智能家居设备配网前扫描周边设备
  • 会议系统发现附近的可投屏设备

不适合的场景:如果你只需要连接已知设备,直接使用连接 API 即可,没必要开启扫描。

环境说明

DevEco Studio 版本:DevEco Studio 6.1.0 及以上 HarmonyOS SDK 版本:HarmonyOS 6.1.0(23) 及以上 目标设备:手机

核心实现

1. 准备工作:权限配置

module.json5中添加权限声明。这一步容易漏掉,很多人直接跑示例发现没反应,原因就是权限没配。

{"module":{"requestPermissions":[{"name":"ohos.permission.NEARLINK_DISCOVERY","reason":"$string:permission_nearlink_discovery","usedScene":{"abilities":["EntryAbility"]}},{"name":"ohos.permission.INTERNET"}]}}

注意:NEARLINK_DISCOVERY权限在 API 23 及以上版本才支持。如果跑在低版本设备上,这个权限不会生效,也不会报错,扫描就直接不回调了。

2. 发起设备发现扫描

这一段代码用于封装星闪发现的核心逻辑。实际项目里建议写成一个单独的服务类,不要直接把扫描逻辑塞进 UI 组件。

// NearLinkDiscoverService.etsimport{nearLink}from'@kit.ConnectivityKit';import{BusinessError}from'@kit.BasicServicesKit';classNearLinkDevice{name:string='';deviceId:string='';rssi:number=-100;manufacturerData:Uint8Array=newUint8Array();}interfaceDiscoverResult{devices:NearLinkDevice[];}exportclassNearLinkDiscoverService{privatediscoveryId:number=-1;privatediscoveredDevices:Map<string,NearLinkDevice>=newMap();privateonDeviceFound:((device:NearLinkDevice)=>void)|null=null;privateonError:((error:string)=>void)|null=null;// 注册发现监听registerListener(onFound:(device:NearLinkDevice)=>void,onError:(error:string)=>void):void{this.onDeviceFound=onFound;this.onError=onError;}// 启动发现startDiscovery():void{// 检查是否已经在扫描中if(this.discoveryId!==-1){console.warn('[NearLink] Discovery already running, skip');return;}// 构建发现回调constcallback:nearLink.IDiscoveryCallback={onError:(errCode:number,errMsg:string):void=>{console.error(`[NearLink] Discovery error:${errCode}-${errMsg}`);this.onError?.(`Discovery error:${errMsg}`);},onFound:(device:nearLink.DiscoveredDevice):void=>{// 防重复逻辑:用 deviceId 去重if(this.discoveredDevices.has(device.deviceId)){// 已存在的设备,更新信号强度constexisting=this.discoveredDevices.get(device.deviceId)!;existing.rssi=device.rssi;return;}// 新设备,加入列表并回调constnewDevice:NearLinkDevice={name:device.deviceName,deviceId:device.deviceId,rssi:device.rssi,manufacturerData:device.manufacturerData};this.discoveredDevices.set(device.deviceId,newDevice);this.onDeviceFound?.(newDevice);}};// 配置发现过滤条件(可选)constfilter:nearLink.DiscoveryFilter={type:nearLink.DiscoveryFilterType.DISCOVERY_FILTER_TYPE_MANUFACTURER,value:newUint8Array([0x01,0x02,0x03])// 示例制造商数据前缀};// 配置发现参数constconfig:nearLink.DiscoveryConfig={discoveryId:0,callback:callback,filter:filter// 不传 filter 则发现所有星闪设备};try{this.discoveryId=nearLink.startDiscovery(config);console.info(`[NearLink] Discovery started, id:${this.discoveryId}`);}catch(err){consterror=errasBusinessError;console.error(`[NearLink] startDiscovery failed:${error.code}-${error.message}`);this.onError?.(`startDiscovery failed:${error.message}`);}}// 停止发现stopDiscovery():void{if(this.discoveryId===-1){console.warn('[NearLink] No active discovery to stop');return;}try{nearLink.stopDiscovery(this.discoveryId);console.info('[NearLink] Discovery stopped');this.discoveryId=-1;// 清空缓存(根据业务需要决定是否清空)// this.discoveredDevices.clear();}catch(err){consterror=errasBusinessError;console.error(`[NearLink] stopDiscovery failed:${error.code}`);}}// 清除设备缓存clearCache():void{this.discoveredDevices.clear();}// 获取已发现的设备列表getDiscoveredDevices():NearLinkDevice[]{returnArray.from(this.discoveredDevices.values());}}

关键细节:

  • 防重复逻辑onFound回调会反复上报同一个设备,信号强度变化时也会再次回调。使用deviceId作为 Map 的 key 去重,只更新已存在设备的 RSSI,不重复添加。
  • discoveryId 管理startDiscovery返回一个 id,停止时要传入这个 id。如果页面销毁时没有保存这个 id,后续无法正确停止扫描。
  • filter 配置:不传 filter 会扫描周边所有星闪设备。如果只关心某个制造商的产品,用DISCOVERY_FILTER_TYPE_MANUFACTURER过滤效率更高。

3. 在页面中使用

这段代码展示如何在 UI 组件中调用发现服务,并处理生命周期。

// IndexPage.etsimport{NearLinkDiscoverService,NearLinkDevice}from'./NearLinkDiscoverService';@Entry@Componentstruct IndexPage{@Statedevices:NearLinkDevice[]=[];@StateisScanning:boolean=false;privatediscoverService:NearLinkDiscoverService=newNearLinkDiscoverService();aboutToAppear():void{// 注册监听this.discoverService.registerListener((device:NearLinkDevice)=>{// 回调中更新 UI(@State 自动触发刷新)this.devices=this.discoverService.getDiscoveredDevices();},(error:string)=>{console.error(`[Page]${error}`);});}aboutToDisappear():void{// 页面离开时必须停止扫描,否则后台持续耗电this.stopScan();}startScan():void{this.isScanning=true;this.devices=[];this.discoverService.clearCache();this.discoverService.startDiscovery();}stopScan():void{this.isScanning=false;this.discoverService.stopDiscovery();}build(){Column(){Button(this.isScanning?'停止扫描':'开始扫描').onClick(()=>{this.isScanning?this.stopScan():this.startScan();}).margin(16)List(){ForEach(this.devices,(device:NearLinkDevice)=>{ListItem(){Row(){Text(device.name||'未知设备').fontSize(16)Text(`RSSI:${device.rssi}dBm`).fontSize(12).fontColor('#666')}.padding(12).width('100%')}})}.layoutWeight(1)}.width('100%').height('100%')}}

注意aboutToDisappear中的stopScan()调用。实际项目里很多人漏掉这一步,导致页面返回后扫描还在持续,不仅耗电,还可能造成回调在已销毁页面中执行,引发异常。

4. 过滤条件完整示例

如果不希望扫描所有设备,可以用制造商数据过滤。这段代码展示如何只发现特定厂商的设备。

// 只发现制造商ID为 0x0001 的设备(示例)constfilter:nearLink.DiscoveryFilter={type:nearLink.DiscoveryFilterType.DISCOVERY_FILTER_TYPE_MANUFACTURER,value:newUint8Array([0x00,0x01])};// 或者使用设备服务类型过滤constfilterByService:nearLink.DiscoveryFilter={type:nearLink.DiscoveryFilterType.DISCOVERY_FILTER_TYPE_SERVICE,value:newUint8Array([0x10,0x20])// 示例服务 UUID 前缀};

常见问题

问题 1:页面返回后扫描未停止

现象:从扫描页面返回,过一会儿发现手机还在发热,日志显示扫描回调仍在触发。

原因aboutToDisappear中没有调用stopDiscovery。HarmonyOS 不会在页面销毁时自动停止已启动的扫描,需要开发者手动处理。

解决方案:严格在aboutToDisappear中停止扫描。如果服务类被多个页面共享,需要在服务类里判断是否有页面还在使用扫描,不要直接 stop。

问题 2:onFound 回调频繁触发同一设备

现象:一个设备在 3 秒内被回调 10 次,页面列表疯狂刷新。

原因:星闪设备会主动广播,每次收到广播都会触发回调。这不是 bug,而是星闪协议本身的特性。

解决方案:使用deviceId做去重,如上面的代码所示。注意:只更新 RSSI 等动态数据,不重复添加列表项。

最佳实践

  1. 不要把扫描逻辑直接写在 UI 组件里。扫描涉及状态管理、生命周期、回调处理,拆成独立服务类后测试和复用都更方便。

  2. stopDiscovery 必须在页面销毁时调用。这不是可选的。如果忘记停止,扫描会持续消耗电量,而且回调中操作 UI 时可能因为页面已销毁而崩溃。

  3. 回调中更新 UI 时注意线程onFound运行在非 UI 线程,不要直接操作 UI 组件。ArkTS 的@State变量是线程安全的,赋值即可触发刷新。但如果有复杂的 UI 操作,建议使用setTimeoutpostTask切回主线程。

FAQ

Q:为什么真机正常,模拟器不生效?

A:星闪能力依赖硬件支持,模拟器不具备星闪硬件,所以startDiscovery调用后不会触发任何回调。必须在 real device 上测试。

Q:为什么页面返回后状态丢失?

A:检查aboutToDisappear是否正确调用了stopDiscovery。另外,如果你在页面间切换时使用了NavStack,注意onPageShowonPageHide的生命周期,建议在onPageHide中暂停扫描,onPageShow中恢复。

Q:过滤条件设置后,扫描不到设备怎么办?

A:先去掉 filter,测试是否能看到设备。如果能看见,说明 filter 配置错误。确认你的 filter value 和设备广播的制造商数据完全匹配(包括字节序和长度)。

如果你也遇到类似问题,可以重点检查生命周期和状态同步逻辑。不同设备上的星闪广播行为可能存在差异,建议真机测试。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/17 1:19:42

前端Docker实战:从开发到生产环境部署指南

1. 写给前端的Docker实战教程&#xff1a;从零到生产环境部署作为一名长期奋战在前端开发一线的工程师&#xff0c;我深知大多数前端开发者对Docker既好奇又畏惧。这份教程将彻底改变你对容器化技术的认知——它不是运维的专属工具&#xff0c;而是现代前端开发者的必备技能。我…

作者头像 李华
网站建设 2026/7/17 1:19:28

鸿蒙原生开发手记:徒步迹 - MapKit 地图组件集成

鸿蒙原生开发手记&#xff1a;徒步迹 - MapKit 地图组件集成集成鸿蒙地图服务&#xff0c;实现路线展示和位置定位一、前言 地图是徒步 App 的核心功能。鸿蒙提供了 MapKit 地图组件&#xff0c;支持地图展示、标记、路线绘制、定位等功能。二、准备工作 2.1 申请 API Key 在华…

作者头像 李华
网站建设 2026/7/17 1:18:59

Windows10系统U盘制作与安装全指南

1. Windows10系统U盘制作全攻略 上周帮朋友重装系统时&#xff0c;发现很多人还在花钱找人装系统。其实用U盘制作Windows10安装盘特别简单&#xff0c;自己动手10分钟就能搞定。这个技能不仅能省下50-100元的装机费&#xff0c;关键时刻还能救急。下面我就把多年装机总结的最稳…

作者头像 李华
网站建设 2026/7/17 1:18:22

Windows RuntimeBroker.exe进程解析与UWP权限管理

1. RuntimeBroker.exe进程深度解析RuntimeBroker.exe是Windows操作系统中一个关键的系统进程&#xff0c;主要负责管理Universal Windows Platform&#xff08;UWP&#xff09;应用程序的权限和沙盒环境。这个进程在Windows 8时代首次引入&#xff0c;当时被称为Metro应用&…

作者头像 李华
网站建设 2026/7/17 1:18:15

3分钟搞定Windows和Office激活难题:KMS_VL_ALL_AIO智能解决方案

3分钟搞定Windows和Office激活难题&#xff1a;KMS_VL_ALL_AIO智能解决方案 【免费下载链接】KMS_VL_ALL_AIO Smart Activation Script 项目地址: https://gitcode.com/gh_mirrors/km/KMS_VL_ALL_AIO 还在为Windows和Office激活问题烦恼吗&#xff1f;KMS_VL_ALL_AIO智能…

作者头像 李华