此笔记已同步推送到微信公众号:灰灰的Rom笔记


这两天在看WIFI扫描连接的问题,发现了几个坑:
1、getScanResult 没有结果
2、getConnectionInfo 结果不正确
3、getScanResult 结果有重复项

1、getScanResult 没有结果

这个问题是因为权限不够,要解决这个问题,不仅要WIFI相关的权限,还需要添加:

android.Manifest.permission.ACCESS_COARSE_LOCATION或android.Manifest.permission.ACCESS_FINE_LOCATION

针对这个问题之前在公众号推过一篇笔记,有兴趣可以看一下,传送门:[App] Wifi 扫描问题分析

2、getConnectionInfo 结果不正确

getConnectionInfo 可以获取当前连接到的WIFI的信息,但是得到的结果却很奇怪:

getSSID = getBSSID =  "02:00:00:00:00:00"

这个问题同上一点,也是权限问题(同样需要第一点中的权限),这个方法的注释已经说明了:

/**  * Return dynamic information about the current Wi-Fi connection, if any is active.  * 

* In the connected state, access to the SSID and BSSID requires * the same permissions as {@link #getScanResults}. If such access is not allowed, * {@link WifiInfo#getSSID} will return {@code ""} and * {@link WifiInfo#getBSSID} will return {@code "02:00:00:00:00:00"}. * * @return the Wi-Fi information, contained in {@link WifiInfo}. */

3、getScanResult 结果有重复项

当出现这个现象时,可能第一反应是list添加数据之前,没有先清空,导致数据重复,可检查代码发现却并无异常。是不是有点懵?

实际上这个现象并不是bug,而是很正常的现象,反而我们平时在手机设置中看到的没有重复项的列表是不正常的。

原因分析

在解释原因之前,先要提两个概念,也是 WIFI 开发中常见的:SSIDBSSID
SSID

Short for Service Set Identifier, a 32-character unique identifier attached to the header of packets sent over a WLAN that acts as a password when a mobile device tries to connect to the BSS. The SSID differentiates one WLAN from another, so all access points and all devices attempting to connect to a specific WLAN must use the same SSID. A device will not be permitted to join the BSS unless it can provide the unique SSID. Because an SSID can be sniffed in plain text from a packet it does not supply any security to the network.

其实说白了就一句话:SSID 就是路由器(下面用 AP 代替)发射的 WIFI 名称

BSSID

The BSSID is a 48bit identity used to identify a particular BSS (Basic Service Set) within an area. In Infrastructure BSS networks, the BSSID is the MAC (Medium Access Control) address of the AP (Access Point) and in Independent BSS or ad hoc networks, the BSSID is generated randomly.

可以理解为就是 AP 的 MAC地址

PS:还有一个 ESSID,不常见,一般情况下也可认为就是 SSID。

总结一下:SSID 是 WIFI 名称,BSSID 是 AP 的 MAC 地址

在有了这两个概念之后,我们再看下获取到的 WIFI 列表:

SSID: EM-guest, BSSID: 38:91:d5:56:35:82, capabilities: [ESS], level: -38, frequency: 2437, timestamp: 8613443970, distance: ?(cm), distanceSd: ?(cm), passpoint: no, ChannelBandwidth: 0, centerFreq0: 0, centerFreq1: 0, 80211mcResponder: is not supported, Carrier AP: no, Carrier AP EAP Type: -1, Carrier name: null, SSID: EM-guest, BSSID: 38:91:d5:56:35:b2, capabilities: [ESS], level: -39, frequency: 2462, timestamp: 8613444138, distance: ?(cm), distanceSd: ?(cm), passpoint: no, ChannelBandwidth: 0, centerFreq0: 0, centerFreq1: 0, 80211mcResponder: is not supported, Carrier AP: no, Carrier AP EAP Type: -1, Carrier name: null, SSID: EM-guest, BSSID: 38:91:d5:56:2f:52, capabilities: [ESS], level: -47, frequency: 2462, timestamp: 8613444167, distance: ?(cm), distanceSd: ?(cm), passpoint: no, ChannelBandwidth: 0, centerFreq0: 0, centerFreq1: 0, 80211mcResponder: is not supported, Carrier AP: no, Carrier AP EAP Type: -1, Carrier name: null, SSID: EM-guest, BSSID: 38:91:d5:56:3d:f2, capabilities: [ESS], level: -49, frequency: 2412, timestamp: 8613443943, distance: ?(cm), distanceSd: ?(cm), passpoint: no, ChannelBandwidth: 0, centerFreq0: 0, centerFreq1: 0, 80211mcResponder: is not supported, Carrier AP: no, Carrier AP EAP Type: -1, Carrier name: null, 

不难发现,这4个 WIFI,虽然 SSID 是一样的,但 BSSID 却不一样,所以扫描时设备将结果全部列出来了。

4 个相同的 SSID,BSSID 却不同,而 BSSID 就是 MAC 地址,难道一个 AP 可以有多个 MAC 地址???

想多了!一般情况下,一个AP往往只有一个MAC地址,上面这4个 WIFI 其实是对应 4 个AP,只不过它们发射的 WIFI 的名字是一样的(都叫“EM-guest”),并且这4个 WIFI 密码往往也会设置成一样的。

为什么会设置成一样的名字

用过路由器桥接功能,或者子母路由的应该都注意到一个现象了,那就是这几个路由器的WIFI名称和密码往往是一致的。这样做有一个好处,就是设备只需要输入一次密码,便可在这几个 WIFI 覆盖区域内随意移动。

但是 WIFI 要形成桥接,或者子母路由能协同工作,有个前提条件,那就是这几个路由器一定要在彼此的覆盖范围内,只有这样它们才能正常通信。

举个栗子:
假设有两个路由器,AP1是母路由,AP2是子路由,在无任何信号遮挡的情况下,它们要想正常通信,它们的位置关系就是这样的。

此时,若有手机处于A、C区域,则只能搜到一个 WIFI 信号;若处于B区域,就能收到两个信号了,搜索列表也就出现了重复选项。但通常在这种情况下,只会留下信号最好的那个 WIFI,其它的都过滤掉,因此手机显示的最终列表也就没有了重复项。

如何过滤重复项?

至于如何过滤,那就是写代码的事了。每个人思路不一样,算法也会不一样,算法对应的效率也会有差异。

一开始碰到这个问题时,也是想在网上找个轮子装上好了,省得重新造一个。但网上看了一圈之后,却发现几乎都是基于ArrayList实现的,在效率和代码复杂度上都不太满意,于是最后还是自己重新造了个轮子。轮子如下:

/** * 以 SSID 为关键字,过滤掉信号弱的选项 * @param list * @return */public static List filterScanResult(final List list) {    LinkedHashMap linkedMap = new LinkedHashMap<>(list.size());    for (ScanResult rst : list) {        if (linkedMap.containsKey(rst.SSID)) {            if (rst.level > linkedMap.get(rst.SSID).level) {                linkedMap.put(rst.SSID, rst);            }            continue;        }        linkedMap.put(rst.SSID, rst);    }    list.clear();    list.addAll(linkedMap.values());    return list;}

基于个人理解,简单解释下为什么这么做。

1、使用 LinkedHashMap 作为临时存储

我们知道 HashMap 是基于 Key 的 hash 值进行操作的,一个 Key 只能有一个 Value。因此在过滤重复项时,以 SSID 作为 Key,以 ScanResult 作为 Value 临时存储数据对,借助 HashMap 的特性将能很方便的判断 ScanResult 是否存在,那个的信号更强。
但是 HashMap 存储数据表现出来是无序的,而其子类 LinkedHashMap 则是有序的,因此最终采用 LinkedHashMap 作为临时存储。
而且这里可以确定 LinkedHashMap 的大小不会超过 list.size,因此初始化 LinkedHashMap 时可以指定最大值为 list.size,避免触发扩容操作,进一步提升效率。

2、为何不用 ArrayList 作为临时存储?

ArrayList 是基于数组实现的,数据有序,但同一 Item 可重复出现,并且 List 存储的是ScanResult 对象,这在一定程度就给判断 SSID 是否重复,哪个信号更好带来了不便(可能需要同时遍历两个 List,遍历次数将是两个size的乘积)。
此外,基于数组的机制,使得每次扩容触发后,数组都要经历一次创建、拷贝、销毁的过程,这都是很耗时耗空间的操作。

当然,你可能会说数据量小,影响很小。
没毛病,确实如此,这个看你自己。

3、使用 continue 替代 else 分支

在循环中,适时适当的使用 continue、break、return,可以在不改变代码逻辑的前提下,减少 if-else 分支及层数,使代码更简洁清晰。

一般情况下,当嵌套层数超过3层,就应该考虑重构了。

4、java 引用(地址)传递

java 传递复杂类型时,实际传递的是引用。这里已传入的List 为载体,放入过滤后的列表并返回,有两个好处:首先是避免创建临时的 List,其次是调用时可以不关注返回值。下面两种方法结果一致:

List list = WifiUtils.filterScanResult(wifiManager.getScanResults());show(list)或者List list = wifiManager.getScanResults();WifiUtils.filterScanResult(list);show(list)

此笔记已同步推送到微信公众号:灰灰的Rom笔记

更多相关文章

  1. Android(安卓)Native内存泄漏诊断
  2. 基于eclipse的android项目实战—博学谷(十 八)关于视频播放问题
  3. UDP广播遇到的坑
  4. Android(安卓)开源项目第三篇——优秀项目篇
  5. ArcGIS Android(安卓)10.1.1 API开发资源
  6. android pdf
  7. Android下EditText的hint的一种显示效果------FloatLabelLayout
  8. Android(安卓)map转json格式,附上Jackson包下载地址,导入过程
  9. Android引路蜂地图开发示例:地址查询

随机推荐

  1. 本文是独立应用商店GetJar创始人兼CEO Il
  2. Android(安卓)微信支付
  3. ListView的Adapter使用 之 初学ArrayAdap
  4. [置顶] Android之SharedPreferences两个
  5. 处理JNI ERROR (app bug): accessed stal
  6. 使用zipalign对齐应用程序
  7. Android面试常客之Handler全解
  8. Android(安卓)应用中十大常见 UX 错误
  9. Android欢迎界面,一个Activity搞定
  10. Android(安卓)学习之路 之 第2组UI组件:Te