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
34model.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