1# 如何实现WLAN网络扫描、连接、断开 2 3## 场景说明 4 5对可用的WLAN列表进行扫描,连接、断开WLAN网络是设备常见的功能,本例将为大家介绍如何实现上述功能。 6 7## 效果呈现 8 9本例效果如下: 10 11| 扫描WLAN | 连接WLAN | 断开WLAN | 12| :----------------------------------------------------------: | :----------------------------------------------------------: | :----------------------------------------------------------: | 13| <img src="figures/wlanscan.png" alt="contactlist" style="zoom: 50%;" /> | <img src="figures/wlanconnect.png" alt="contactlist" style="zoom: 50%;" /> | <img src="figures/wlandisconnect.png" alt="contactlist" style="zoom: 50%;" /> | 14 15## 运行环境 16 17本例基于以下环境开发,开发者也可以基于其他适配的版本进行开发。 18 19- IDE:DevEco Studio 4.0.0.201 Beta1 20- SDK:Ohos_sdk_public 4.0.7.5 (API Version 10 Beta1) 21 22## 实现思路 23 24本例主要通过@ohos.wifiManager 相关API实现WLAN激活和关闭、扫描和连接WLAN等功能。 25 26- WLAN功能的激活和关闭:点击首页的切换按钮,如果是打开,使用wifi.enableWifi()开启WLAN功能;如果是关闭,则使用wifi.disconnect()断开WLAN功能, 然后使用wifi.disableWifi()关闭WLAN。 27- WLAN的连接:点击WLAN列表中加密的WLAN,并在弹窗中输入密码后,会在AvailableWifi.ets中通过WifiModule.connectNetwork()调用wifi.connectToDevice()连接WLAN。 28- WLAN的扫描:进入Index.ets 后就会间歇性的调用wifi.scan()开启扫描,然后通过WifiModel模块中的getScanInfos()调用wifi.getScanResults()来获取扫描的结果. 29 30## 开发步骤 31 321. 申请所需权限 33 34 在model.json5中添加以下配置: 35 36 ```json 37 "requestPermissions": [ 38 { 39 "name": "ohos.permission.GET_WIFI_INFO"//允许应用获取WLAN信息 40 }, 41 { 42 "name": "ohos.permission.GET_WIFI_INFO_INTERNAL"//允许应用获取WLAN信息 43 }, 44 { 45 "name": "ohos.permission.SET_WIFI_INFO"//允许应用配置WLAN设备 46 }, 47 { 48 "name": "ohos.permission.GET_WIFI_PEERS_MAC"//允许应用获取对端WLAN或者蓝牙设备的MAC地址 49 }, 50 { 51 "name": "ohos.permission.GET_WIFI_LOCAL_MAC"//允许应用获取本机WLAN或者蓝牙设备的MAC地址 52 }, 53 { 54 "name": "ohos.permission.GET_WIFI_CONFIG"//允许应用获取WLAN配置信息 55 }, 56 { 57 "name": "ohos.permission.SET_WIFI_CONFIG"//允许应用配置WLAN信息 58 }, 59 { 60 "name": "ohos.permission.MANAGE_WIFI_CONNECTION"//允许应用管理WLAN连接 61 }, 62 { 63 "name": "ohos.permission.MANAGE_WIFI_HOTSPOT"//允许应用开启或者关闭WLAN热点 64 }, 65 { 66 "name": "ohos.permission.LOCATION"//允许应用获取设备位置信息 67 }, 68 { 69 "name": "ohos.permission.APPROXIMATELY_LOCATION"//允许应用获取设备模糊位置信息 70 } 71 ] 72 ``` 73 742. 准备工作 75 76 定义boolean变量isSwitchOn作为WLAN开关是否打开的标志。 77 78 ```typescript 79 import wifi from '@ohos.wifiManager' 80 aboutToAppear() { 81 // 如果WLAN是开的,就记录下状态,然后扫描WLAN,并获取连接信息 82 if (wifi.isWifiActive()) {//查询WLAN是否已使能 83 Logger.log(TAG, 'wifi is active') 84 this.isSwitchOn = true//已使能说明WLAN开关已被打开,那么让isSwitchOn为true 85 wifi.scan()//@ohos.wifiManager的接口,对WLAN进行扫描 86 this.scan()//自定义的scan函数,确保扫描的持续性 87 this.getLinkedInfo() 88 } 89 // 启动监听 90 this.addListener()//监听连接状态的变化 91 } 92 ``` 93 943. 持续地扫描可用WLAN 95 96 scan方法不断地自我调用以实现对WLAN的不停扫描。 97 98 ```typescript 99 async getLinkedInfo() { 100 try { 101 //调用getLinkedInfo接口得到是否已连接WLAN的信息 102 let wifiLinkedInfo = await wifi.getLinkedInfo() 103 if (wifiLinkedInfo === null || wifiLinkedInfo.bssid === '') { 104 //如果结果为空,则说明未连接 105 this.isLinked = false 106 this.linkedInfo = null 107 return 108 } 109 this.isLinked = true 110 this.linkedInfo = wifiLinkedInfo 111 } catch (err) { 112 Logger.info(`getLinkedInfo failed err is ${JSON.stringify(err)}`) 113 } 114 } 115 async scan() { 116 // 获取有关WLAN连接的信息,存入linkedInfo 117 await this.getLinkedInfo() 118 // 不停地扫描WLAN 119 let result: Array<WifiType> = await this.wifiModel.getScanInfos()//调用model/WifiModel.ets中的getScanInfos得到扫描结果 120 if (this.isSwitchOn) { 121 //如果WLAN功能已打开,则要进行不停地扫描 122 AppStorage.SetOrCreate('wifiList', result)//将扫描结果存至全局 123 //每3000毫秒调用一次scan(),实现不停扫描可用WLAN 124 setTimeout(async () => { 125 await this.scan() 126 }, 3000) 127 } 128 } 129 addListener() { 130 // WLAN连接状态改变事件发生时,修改连接信息 131 wifi.on('wifiConnectionChange', async state => { 132 Logger.log(TAG, `wifiConnectionChange: ${state}`) 133 await this.getLinkedInfo()//WLAN连接状态改变,重新获取WLAN连接信息 134 }) 135 // WLAN状态改变时,先清空WLAN列表,然后判断是否是开启状态,如果是就扫描 136 wifi.on('wifiStateChange', state => { 137 Logger.log(TAG, `wifiStateLisener state: ${state}`) 138 AppStorage.SetOrCreate('wifiList', []) 139 if (state === 1) { // 1: wifi is enable, 0:wifi is disable 140 this.scan() 141 } 142 }) 143 } 144 ... 145 //model/WifiModel.ets 146 async getScanInfos(): Promise<Array<WifiType>> { 147 Logger.log(TAG, 'scanWifi begin') 148 let wifiList: Array<WifiType> = [] 149 let result: Array<wifi.WifiScanInfo> = [] 150 try { 151 result = await wifi.getScanResults()//因为在abouttoappear()中执行了wifi.scan(),所以这里直接调用getScanResults接口得到扫描结果 152 } catch (err) { 153 Logger.log(TAG, `scan info err: ${JSON.stringify(err)}`) 154 return wifiList 155 } 156 Logger.log(TAG, `scan info call back: ${result.length}`) 157 for (var i = 0; i < result.length; ++i) { 158 wifiList.push({ 159 ssid: result[i].ssid, 160 bssid: result[i].bssid, 161 securityType: result[i].securityType, 162 rssi: result[i].rssi, 163 band: result[i].band, 164 frequency: result[i].frequency, 165 timestamp: result[i].timestamp 166 }) 167 } 168 return wifiList 169 } 170 ``` 171 1724. 构建UI框架、通过关闭按钮实现断开WLAN连接。 173 174 TitleBar组件呈现了标题框。WLAN开关按钮由一个Toggle组件呈现,其绑定的onChange事件在按钮被打开时开始扫描可用WLAN,在被关闭时断开当前连接WLAN并且关闭WLAN功能。当前已连接的WLAN由两个Text组件分别显示其ssid与连接状态。 175 176 ```typescript 177 build() { 178 Column() { 179 TitleBar()//引入自component/TitleBar.ets,负责标题框的具体呈现 180 Row() { 181 Text($r('app.string.wlan')) 182 .fontSize(22) 183 .fontWeight(FontWeight.Bold) 184 .height(40) 185 Column() { 186 Toggle({ type: ToggleType.Switch, isOn: this.isSwitchOn })//切换按钮 187 .id('switch') 188 .onChange((isOn: boolean) => { 189 Logger.log(`LSQ: wifi swtich is: ${isOn}`) 190 AppStorage.SetOrCreate('wifiList', []) 191 try { 192 // 如果按钮被打开了,记录状态,打开网络,开始扫描可用WLAN 193 if (isOn) { 194 this.isSwitchOn = true 195 wifi.enableWifi()//使WLAN功能可用 196 return 197 } else { 198 // 如果按钮被关闭了记录状态,断开网络禁用网络 199 this.isSwitchOn = false 200 this.isLinked = false 201 wifi.disconnect()//断开当前WLAN连接 202 wifi.disableWifi()//使WLAN功能不可用 203 } 204 } catch (error) { 205 Logger.error(TAG, `failed,code:${JSON.stringify(error.code)},message:${JSON.stringify(error.message)}`) 206 } 207 }) 208 } 209 } 210 .width('100%') 211 .padding({ left: 16, right: 16 }) 212 if (this.isLinked && this.isSwitchOn) { 213 //如果当前按钮处于打开状态,且WLAN是已连接状态 214 Column() { 215 Text($r('app.string.connected')) 216 .fontSize(22) 217 .width('100%') 218 Row() { 219 Text(this.linkedInfo.ssid)//展示当前已连接的WLAN 220 .fontSize(20) 221 .fontColor(Color.Black) 222 .layoutWeight(1) 223 Text($r('app.string.connected')) 224 .fontSize(18) 225 .fontColor(Color.Black) 226 } 227 .width('100%') 228 .padding(10) 229 .margin({ left: 16, right: 16 }) 230 .border({ radius: 15, color: Color.Gray, width: 1 }) 231 .backgroundColor(Color.White) 232 } 233 .width('100%') 234 .padding({ left: 16, right: 16 }) 235 } 236 if (this.isSwitchOn) { 237 //如果按钮处于打开状态,展示所有可用WLAN 238 AvailableWifi({ linkedInfo: this.linkedInfo })//AvailableWifi引入自component/AvailableWifi.ets 239 } 240 } 241 .width('100%') 242 .height('100%') 243 .backgroundColor($r('app.color.index_bg')) 244 } 245 ``` 246 2475. 展示可用WLAN列表及WLAN的连接。 248 249 使用List组件呈现可用WLAN列表,ListItem由一个呈现WLANssid的Text组件,一个表示其是否被加密的Text组件和一个展示其加密与否图像的Image组件组成。 250 251 ```typescript 252 //component/AvailableWifi.ets 253 build() { 254 List() { 255 ListItem() { 256 Row() { 257 Text($r('app.string.available_wlan')) 258 .fontSize(22) 259 .layoutWeight(1) 260 } 261 .id('validWlan') 262 .width('100%') 263 } 264 //通过数据懒加载的方式从数据源中每次迭代一个可用WLAN进行展示,可用列表被放置在滚动容器中,被划出可视区域外的资源会被回收 265 LazyForEach(this.wifiDataResource, (item, index) => { 266 ListItem() { 267 WifiView({ wifi: item })//WifiView引入自Component/WifiView.ets,负责可用WLAN信息展示的具体实现 268 } 269 .id(`Wifi${index}`) 270 //对可用WLAN点击时触发事件 271 .onClick(() => { 272 Logger.info(TAG, 'wifi click') 273 this.scanInfo = item 274 if (this.linkedInfo !== null && item.ssid === this.linkedInfo.ssid) { 275 prompt.showToast({ message: 'this wifi is connected' })//如果当前已连接WLAN并且点击的就是这个WLAN,创建并显示文本提示框 276 return 277 } 278 if (item.securityType === 0 || item.securityType === 1) { 279 //如果点击的这个WLAN的加密类型是无效加密类型或者开放加密类型,调用model/Wifimodel.ets中的connectNetwork方法连接此WLAN 280 this.wifiModel.connectNetwork(item, '') 281 return 282 } 283 //如果点击的这个WLAN的加密类型是其他加密类型,则需要输入密码,调用component/PswDialog.ets中的方法进行弹窗的生成 284 this.pswDialogController.open() 285 }) 286 }, item => JSON.stringify(item)) 287 } 288 .width('100%') 289 .height('100%') 290 .padding({ left: 16, right: 16 }) 291 .layoutWeight(1) 292 .divider({ strokeWidth: 1, color: Color.Gray, startMargin: 10, endMargin: 10 }) 293 .margin({ top: 10 }) 294 } 295 //pswDialogController回调的action函数,将传回的WLAN信息与密码传入model/Wifimodel.ets中的connectNetwork方法中 296 onAccept(scanInfo, psw) { 297 Logger.info(TAG, 'connect wifi') 298 self.wifiModel.connectNetwork(scanInfo, psw) 299 } 300 ... 301 //Component/WifiView.ets 302 aboutToAppear() { 303 Logger.debug(TAG, `aboutToAppear ${JSON.stringify(this.wifi)}`) 304 if (this.wifi) { 305 if (this.wifi.securityType) { 306 if (this.wifi.securityType === 0 || this.wifi.securityType === 1) { 307 //WLAN的加密类型是无效加密类型或者开放加密类型 308 this.securityString = $r('app.string.open') 309 this.isLock = false 310 } 311 } 312 } 313 } 314 build() { 315 Row() { 316 Column() { 317 if (this.wifi) { 318 if (this.wifi.ssid) { 319 Text(this.wifi.ssid) 320 .fontSize(20) 321 .width('100%') 322 } 323 } 324 //如果WLAN的加密类型是无效加密类型或者开放加密类型,显示“开放”,反之显示“加密” 325 Text(this.securityString) 326 .fontSize(18) 327 .fontColor(Color.Gray) 328 .width('100%') 329 } 330 .layoutWeight(1) 331 332 Stack({ alignContent: Alignment.BottomEnd }) { 333 //如果WLAN的加密类型是无效加密类型或者开放加密类型,显示不带锁的图标,反之显示带锁的图标 334 Image($r('app.media.wifi')) 335 .height(30) 336 .width(30) 337 .objectFit(ImageFit.Contain) 338 if (this.isLock) { 339 Image($r('app.media.lock')) 340 .objectFit(ImageFit.Contain) 341 .width(15) 342 .height(15) 343 } 344 } 345 .width(40) 346 .height(40) 347 .margin({ right: 10 }) 348 } 349 .backgroundColor(Color.White) 350 .width('100%') 351 .padding(10) 352 } 353 } 354 ... 355 //model/Wifimodel.ets 356 connectNetwork(scanInfo: wifi.WifiScanInfo, psw) { 357 prompt.showToast({ message: 'connecting', duration: 5000 }) 358 Logger.debug(TAG, `connectNetwork bssid=${scanInfo.bssid}`) 359 // 这里因为api问题,需要声明为any,已提单 360 let deviceConfig: any = { 361 ssid: scanInfo.ssid, 362 bssid: scanInfo.bssid, 363 preSharedKey: psw, 364 isHiddenSsid: false, 365 securityType: scanInfo.securityType 366 } 367 try { 368 wifi.connectToDevice(deviceConfig)//连接到指定网络 369 Logger.debug(TAG, `connectToDevice success`) 370 } catch (err) { 371 Logger.debug(TAG, `connectToDevice fail err is ${JSON.stringify(err)}`) 372 } 373 try { 374 wifi.addDeviceConfig(deviceConfig)//添加网络配置 375 } catch (err) { 376 Logger.debug(TAG, `addDeviceConfig fail err is ${JSON.stringify(err)}`) 377 } 378 } 379 ... 380 //component/PswDialog.ets 381 build() { 382 Column() { 383 Text(this.scanInfo.ssid) 384 .fontSize(20) 385 .width('95%') 386 387 TextInput({ placeholder: 'input password' })//密码的输入框 388 .id('input') 389 .type(InputType.Password) 390 .placeholderColor(Color.Gray) 391 .fontSize(19) 392 .margin({ top: 15 }) 393 .width('95%') 394 .height(36) 395 //每当输入框中的内容变化,它就赋值给psw 396 .onChange((value: string) => { 397 this.psw = value 398 }) 399 400 Row() { 401 //确认按钮 402 Button() { 403 Text($r('app.string.sure')) 404 .fontColor(Color.Blue) 405 .fontSize(17) 406 } 407 .id('sure') 408 .layoutWeight(7) 409 .backgroundColor(Color.White) 410 .margin(5) 411 .onClick(() => { 412 this.controller.close() 413 this.action(this.scanInfo, this.psw) 414 }) 415 416 Text() 417 .width(1) 418 .height(35) 419 .backgroundColor($r('app.color.text_color')) 420 //取消按钮 421 Button() { 422 Text($r('app.string.cancel')) 423 .fontColor(Color.Red) 424 .fontSize(17) 425 } 426 .layoutWeight(7) 427 .backgroundColor(Color.White) 428 .margin(5) 429 .onClick(() => { 430 this.controller.close() 431 }) 432 } 433 .width('100%') 434 .margin({ top: '3%' }) 435 } 436 .padding(15) 437 } 438 ``` 439 440## 全部代码 441 442本例完整代码sample示例链接:[WLAN](https://gitee.com/openharmony/applications_app_samples/tree/master/code/SystemFeature/Connectivity/Wlan) 443 444## 参考 445 446- [权限列表](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/security/AccessToken/permissions-for-all.md#ohospermissiondistributed_datasync) 447- [@ohos.wifiManager](../application-dev/reference/apis-connectivity-kit/js-apis-wifiManager.md) 448 449