HarmonyOS技术精讲-Connectivity Kit:Wi-Fi扫描与连接实战
开篇一个常见的Wi-Fi开发误区很多人在HarmonyOS NEXT里第一次接触Wi-Fi扫描时会直接按照官方示例写代码然后发现扫描结果出来了但连接总是失败。更头疼的是失败后的错误回调描述不够详细导致调试成本很高。这个问题在HarmonyOS开发里比较常见。Connectivity Kit 的Wi-Fi模块设计思路和Android有差异但更贴近系统底层状态机。如果不对API的生命周期和状态同步做处理很容易出现“扫描正常连接异常”的情况。这篇实战文章会从权限声明开始逐步实现一个完整的Wi-Fi扫描与连接功能。重点是wifiManager的获取、startScan和onScanStateChange的配合、connectToNetwork的正确调用方式以及扫描列表的UI渲染。Connectivity Kit 解决什么问题Connectivity Kit短距通信服务是HarmonyOS提供的无线连接能力集合。其中的Wi-Fi模块主要负责扫描周围可用的Wi-Fi热点建立或断开Wi-Fi连接监听Wi-Fi状态变化连接、断开、信号强度变化适合场景场景说明智能家居配网手机扫描局域网设备热点完成配网办公设备联网设备需要连接到企业Wi-Fi网络诊断工具扫描周边AP分析信道占用和信号覆盖不适合场景后台长期扫描系统对高频Wi-Fi扫描有节流机制不建议在后台持续调用。隐藏网络扫描API未直接暴露被动监听隐藏SSID的能力需要用户主动输入SSID。和“应用内发Intent跳转系统设置”的方式相比直接使用Connectivity Kit API的优点是用户可以完全在应用内完成操作不需要跳转到系统页面体验更流畅。环境说明DevEco Studio 版本DevEco Studio 6.1.0 及以上 HarmonyOS SDK 版本HarmonyOS 6.1.0(23) 及以上 目标设备手机核心实现1. 权限声明Wi-Fi扫描和连接需要两个敏感权限ohos.permission.GET_WIFI_INFO和ohos.permission.SET_WIFI_INFO。前者用于读取Wi-Fi状态后者用于连接Wi-Fi。在entry/src/main/module.json5中添加{module:{requestPermissions:[{name:ohos.permission.GET_WIFI_INFO,reason:用于获取Wi-Fi状态和扫描结果,usedScene:{abilities:[EntryAbility]}},{name:ohos.permission.SET_WIFI_INFO,reason:用于连接指定的Wi-Fi网络,usedScene:{abilities:[EntryAbility]}}]}}注意在HarmonyOS NEXT上权限声明是静态的但运行时仍然需要动态请求部分权限在弹窗后自动授予这里两个权限都属于“用户授权”类型。2. 获取wifiManager对象wifiManager是 Wi-Fi 模块的入口对象。不能直接new必须通过connect接口获取。import{wifiManager}fromkit.ConnectivityKit;import{common}fromkit.AbilityKit;// 获取wifiManager对象functiongetWifiManager(context:common.Context):wifiManager.WifiDevice{// 这里传入的context必须是UIAbility的context不能是页面组件的contextreturnwifiManager.getWifiDevice(context);}官方文档没有强调context的类型限制。如果传入的是this.getUIContext()中获取的组件context会导致wifiManager.getWifiDevice返回undefined。3. 扫描与监听扫描调用startScan但结果不是直接返回的需要通过onScanStateChange监听扫描完成事件。// 发起扫描functionstartWifiScan(wifi:wifiManager.WifiDevice):void{// startScan没有返回值扫描结果通过回调获取wifi.startScan();}// 监听扫描状态变化functiononScanStateChange(wifi:wifiManager.WifiDevice,callback:(state:boolean)void):void{wifi.on(scanStateChange,(state:boolean){// state为true表示扫描成功false表示扫描失败或超时callback(state);});}// 获取扫描结果functiongetScanResults(wifi:wifiManager.WifiDevice):ArraywifiManager.WifiScanInfo{// 必须在扫描完成后调用否则返回空数组returnwifi.getScanInfoResults();}注意onScanStateChange回调是一次性的吗不是。它会在每次扫描完成时触发。如果多次调用startScan回调会被多次触发。建议在页面aboutToDisappear时调用wifi.off(scanStateChange)取消监听。4. 构建Wi-Fi配置并连接连接Wi-Fi需要构建一个WifiDeviceConfig对象然后调用connectToNetwork。import{wifiManager}fromkit.ConnectivityKit;functionconnectToWifi(wifi:wifiManager.WifiDevice,ssid:string,preSharedKey:string):void{// 构建Wi-Fi配置letconfig:wifiManager.WifiDeviceConfig{ssid:ssid,preSharedKey:preSharedKey,securityType:wifiManager.SecurityType.WPA2_PSK// 根据实际情况选择};// 调用连接方法wifi.connectToNetwork(config,(error,data){if(error){console.error(连接失败:${JSON.stringify(error)});return;}console.info(连接成功netId:${data});});}安全类型枚举值枚举对应加密方式WPA2_PSKWPA2个人网WPA3_SAEWPA3个人网OPEN开放网络WEPWEP加密不推荐如果不知道目的AP的加密方式可以先扫描从WifiScanInfo的capabilities字段中提取。基本做法是解析capabilities字符串匹配WPA、WPA2、WPA3等关键字。5. 完整页面实现下面是一个完整的Index.ets页面组件包含扫描列表展示和连接按钮交互。import{wifiManager}fromkit.ConnectivityKit;import{common}fromkit.AbilityKit;import{promptAction}fromkit.ArkUI;EntryComponentstruct Index{StatescanResults:ArraywifiManager.WifiScanInfo[];StateselectedSsid:string;Statepassword:string;StateisScanning:booleanfalse;privatewifi:wifiManager.WifiDevice|undefinedundefined;aboutToAppear():void{// 在UIAbility中使用this.context获取context// 注意页面组件的context和UIAbility的context不同这里假设PageAbility中已处理好// 实际项目中建议将wifiManager对象的获取放在UIAbility中通过全局状态管理letcontextgetContext(this)ascommon.BaseContext;this.wifiwifiManager.getWifiDevice(context);}// 执行扫描doScan():void{if(!this.wifi){promptAction.showToast({message:Wi-Fi管理器获取失败});return;}this.isScanningtrue;// 先清除上一次的扫描结果this.scanResults[];// 注册扫描状态监听this.wifi.on(scanStateChange,(state:boolean){this.isScanningfalse;if(state){this.scanResultsthis.wifi!.getScanInfoResults();promptAction.showToast({message:扫描完成发现${this.scanResults.length}个网络});}else{promptAction.showToast({message:扫描失败});}});// 发起扫描this.wifi.startScan();}// 连接到选中的网络connect():void{if(!this.wifi){promptAction.showToast({message:Wi-Fi管理器获取失败});return;}if(!this.selectedSsid){promptAction.showToast({message:请先选择网络});return;}// 构建配置安全类型暂时固定为WPA2实际可根据capabilities解析letconfig:wifiManager.WifiDeviceConfig{ssid:this.selectedSsid,preSharedKey:this.password,securityType:wifiManager.SecurityType.WPA2_PSK};this.wifi.connectToNetwork(config,(error,data){if(error){promptAction.showToast({message:连接失败:${error.message}});return;}promptAction.showToast({message:连接成功netId:${data}});});}build(){Column(){// 操作按钮区域Row(){Button(扫描Wi-Fi).onClick(()this.doScan()).width(45%)Button(连接).onClick(()this.connect()).width(45%)}.padding(10).width(100%).justifyContent(FlexAlign.SpaceBetween)// 扫描结果列表List(){ForEach(this.scanResults,(item:wifiManager.WifiScanInfo,index:number){ListItem(){Row(){Column(){Text(item.ssid).fontSize(16).fontWeight(FontWeight.Bold)Text(信号强度:${item.rssi}dBm).fontSize(12).fontColor(Color.Gray)}.alignItems(HorizontalAlign.Start).layoutWeight(1)if(this.selectedSsiditem.ssid){Text(已选中).fontSize(14).fontColor(Color.Green)}}.padding(10).width(100%).onClick((){this.selectedSsiditem.ssid;})}.divider({strokeWidth:1,color:#e0e0e0})})}.width(100%).layoutWeight(1)// 密码输入区域if(this.selectedSsid){Row(){Text(密码:)TextInput({placeholder:输入Wi-Fi密码}).onChange((value:string){this.passwordvalue;})}.padding(10).width(100%)}}.width(100%).height(100%)}}这段代码的核心逻辑aboutToAppear中初始化wifiManager对象。doScan方法先清除旧结果注册监听再调用startScan。connect方法使用选中网络的SSID和用户输入密码构建配置调用connectToNetwork。列表项onClick选中一个网络选中后显示密码输入框。注意事项生命周期问题页面销毁时必须取消监听。可以在aboutToDisappear中调用this.wifi.off(scanStateChange)否则下次进入页面会重复注册导致多次回调。状态同步扫描是异步的扫描完成前获取扫描结果会返回空数组。所以用isScanning状态控制UI反馈。UI刷新scanResults是State变量扫描完成后赋值会自动触发列表刷新。踩坑记录坑1onScanStateChange不回调现象调用startScan后onScanStateChange回调完全不触发。原因HarmonyOS对Wi-Fi扫描有节流逻辑。如果在短时间内约30秒连续调用startScan后一次调用会被系统忽略且不会触发任何回调。这其实是系统层面的频率限制官方文档没有明确说明。解决方案在每次扫描前判断距离上次扫描的时间间隔。如果小于30秒提示用户稍后再试或者使用计时器控制扫描频率。实测稳定间隔是40秒以上。坑2connectToNetwork回调顺序不稳定现象调用connectToNetwork后回调被立刻触发但实际连接还没完成比如还没出现Wi-Fi图标。特别是网络密码错误时回调依然返回成功。原因connectToNetwork的异步回调只是表示“连接指令已下发”并不代表“连接已成功建立”。真正判断连接是否成功需要监听wifiManager.on(connectionChange)事件。官方示例里直接用回调判断成功是一个常见的误导这个设计本身有点反直觉。解决方案// 连接成功后等待连接状态变化wifi.on(connectionChange,(state:wifiManager.ConnectionState){if(statewifiManager.ConnectionState.CONNECTED){console.info(Wi-Fi连接成功);// 此时可以获取当前连接信息}elseif(statewifiManager.ConnectionState.DISCONNECTED){console.info(Wi-Fi断开);// 可能是密码错误导致主动断开}});最佳实践使用单例管理wifiManager对象不要在每次操作前都调用getWifiDevice。如果页面销毁后重新创建会导致旧监听失效。推荐在UIAbility中初始化通过AppStorage或全局变量传递给页面。利用秒级Listen频率降低回调漏接概率on(scanStateChange)在扫描完成时触发一次但如果扫描开始后页面立即跳转可能错过回调。可以在aboutToAppear中注册页面内确保监听生命周期与页面一致不跨页面传递。扫描结果列表展示前清理上一次结果getScanInfoResults返回的是全量缓存结果包括上一次扫描的数据。如果不清理列表会显示过时的Wi-Fi。每次扫描前将scanResults置空等待回调后再赋值。FAQQ1为什么真机上能扫描到网络模拟器上扫描不到A模拟器对Wi-Fi硬件的模拟不完整getScanInfoResults在模拟器上始终返回空数组。这是预期行为Wi-Fi相关功能建议使用真机调试。Q2密码输入正确却连接失败错误信息是“INVALID_PARAM”A检查WifiDeviceConfig中的securityType是否和实际AP匹配。最常见的是扫描到的网络是开放网络OPEN但代码里写了WPA2_PSK。建议根据WifiScanInfo.capabilities动态解析安全类型。Q3应用退出后Wi-Fi连接会断开吗A调用connectToNetwork后Wi-Fi连接是由系统管理的应用退出不会自动断开。如果需要主动断开可以调用wifi.disconnect方法。

相关新闻