【Android】Wi-Fi 混频检测技术
背景:
目前的扫描策略是,仅判断本次扫描的设备的 Wi-Fi 是否存在混频,然后将 2.4G、5G、2.4G/5G 的结果返回给业务层,业务层拿到结果,将当前获取的 Wi-Fi 和 SDK 返回的对象当中的仅 2.4G 和 2.4G/5G 进行比对,比对成功则允许下一步配网,反之则拦截。
缺点:
由于混频的 Wi-Fi 不一定在本次扫描当中扫描到,则会将混频的 Wi-Fi 进行误判,并且很多家庭下的 Wi-Fi 目前都是混频的,这样可能导致很多家庭在本次扫描的时候,仅扫描到 5G 频段或者没有扫描到的 Wi-Fi 被拦截。并且目前测试结果来看,5G Wi-Fi 对外释放的信号更加容易被扫描到。
如果需要下次扫描到具有 2.4GHz 的 Wi-Fi ,需要重新退出页面重新进来走流程才有机会判断成功。
优化思路:
针对上述的现状和缺点,这次做一个优化点。
SDK 增加二级缓存策略,内存缓存和内部存储缓存,仅存储 2.4GHz 的 Wi-Fi 数据。当扫描到 5G Wifi 的时候,判断当前的内存缓存和内部存储是否有相同 SSID 的 2.4G Wi-Fi ,若有则判定为是混频 Wi-Fi 。
每次扫描的时候,解析完成之后都会进行缓存的更新。
混频算法优化,更改之前的 n2 的时间复杂度为 n*logN
完整的链路
扫描能力
若存在 targetSsid,sdk 会扫描附近的 Wi-Fi 列表
纯2.4G 判断,根据返回的结果列表中 2.4G List 是否存在当前传入的 targetSsid (有可能不准确,因为此时可能没有扫描到当对应 5G 频段的 Wi-Fi ,但是不妨碍配网)
混频判断,根据返回的结果列表中 2.4G List 和 5G List 是否都存在当前传入的 targetSsid (准确)
纯 5G 判断,根据返回对结果列表中,是否 5G List 存在,但是2.4G List 不存 targetSsid(有可能不准确,因为此时可能没有扫描到当对应 2.4G 频段的 Wi-Fi )
分频 5G 判断,根据返回到结果列表中,查看是否 5G List 是否存在当前的 targetSsid,然后查看当前 targetSsid 对应的 bssid 在 2.4G 列表里面是否存在,若存在则代表当前是分频 5G (有可能不准确,有可能不准确,因为此时可能没有扫描到当对应 2.4G 频段的 Wi-Fi )
扫描策略一:
业务层可以选择是否传入当前 app 连接的 targetSsid(可以根据当前 app 是否有连接 Wi-Fi 来决定是否传值)
若存在 targetSsid ,sdk 会尽可能保证 「当前 targetSsid 是仅 2.4G、仅 5G、2.4G/5G」的是正确的。由于我们是希望尽可能判断出来 targetSsid 是2.4G 或者 2.4G/5G 的,因此当第一次扫描的时候,如果 targetSsid 的结论是仅 5G 的时候,会进行扫描重试,不会超过 3 次(目前在公司测试 3 次的扫描的准确性高达 100%)。只要扫描过,sdk 会将扫描的 2.4G 的结果存入到缓存里面,下次若 targetSsid 一致就不会再扫描 3 次了。
若不存在 targetSsid,sdk 只会扫描一次,并将结果返回,扫描的 2.4G 的结果会存入到本地缓存。
扫描策略二:
业务层直接调用扫描方法(可以根据当前 app 是否有连接 Wi-Fi 来决定是否传值),sdk 返回当前的扫描结果列表,然后展示给用户,提供给用户一个刷新按钮(参考美的的方式)
综合上述的一个方式,sdk 可以优化扫描策略。由于扫描的策略为2分钟不超过4次,这个限制放在业务层使用并不是非常合理,因此,将这个逻辑下层到 sdk。
sdk 的扫描策略如下:
当有业务层发起 Wi-Fi 扫描的请求的时候,我们一次完整4次的扫描周期为120秒,那么平均分配的扫描时间应该为30秒。即如下所示:
但是这样的扫描效果非常的差,如果第一次扫描的时候,无法分辨出混频、分频的情况的话,会导致第二次的扫描时长过长。通过测试,我们发现,在公司比较复杂的网络环境下,进行混频/分频的扫描的结果如下所示。可以看到,有大概率的第一次的扫描是会失败的,但是基本上前面2次就可以把混频/分频的结果正确拿到,因此,我们更正一下之前的策略,只要保证2分钟的扫描次数不超过4次即可,另外,其实到了第三次第四次的时候,扫描的结果基本上不会发生太大的变化,因此,我们可以考虑将前面2次甚至前面3次的扫描时间紧凑一点。基本上,每次扫描的时间都差不多再5秒左右。
因此,将扫描的时间片分为以下的方式,假设:
第一次扫描的时间是x秒
第二次扫描的时间为y秒
第三次扫描的时间为z秒
第四次扫描的时间为w秒
其中,x + y + z + w <= 120,这里你们可能有个疑问,为什么能保证4者之和一定小于120秒呢,因为实测的结果4次是远远小于120的。
那么我让第一次和第二次直接无延迟串行扫描,第三次则进行适当低延迟扫描,第四次的话,我们进行高延迟扫描。
x:第一次扫描花费的时间
y:第二次扫描花费的时间
delay1: 第二次扫描之后到第三次扫描间隔的时间
z:第三次扫描花费的时间
delay2:第三次扫描之后到第四次扫描的间隔时间
w:第四次扫描花费的时间。(这里会预留30秒的时间给第四次扫描)
sleep status:休眠状态,不会处罚扫描。
以上是自动扫描的策略,那么当业务层多次频繁触发扫描动作的时候,该怎么处理?
后面有进行触发扫描的动作,
若当前正处于第一次、第二次、第三次、第四次扫描,则直接不做处理。
若当前处于延迟-1的阶段,则立马进行第三次扫描。同理,若当前处于延迟-2的情况,那么立马进行第四次扫描。
若处于休眠态,则不做处理,直接返回之前的扫描结果。
我们正常期望的扫描流程如下:
但是用户可以主动停止,可能会导致重新开始的扫描点次数加上下一个周期的扫描次数超过了2分钟4次
如下:
因此,我们规定如下点,在一个周期,前三次扫描要落在前1分钟,第四次扫描一定要或者后1分钟.
但是,即便如此,还是有可能出现扫描超出限制的,因此当现扫描出现限制的时候,则直接将上一次的结果进行返回。通过测试,系统会将这个扫描结果是否返回还回来,因此直接使用系统的管理方式即可。
API 设计
Code: Select all
// 注册监听
public void addWifiScanListener(
IThingDataCallback<List<WifiScanBean>> resultCallback);
// 注销监听
public boolean removeWifiScanListener(
IThingDataCallback<List<WifiScanBean>> resultCallback);
/**
* 刷新 Wi-Fi 列表
* @param context ctx
* @return 当前执行的结果<br />
* 0 :正常执行扫描,扫描结果会在 listener 里面返回<br />
* 1 : 当前处于延迟执行状态,会提前执行下一次扫描,扫描结果会在listener里面返回<br />
*
* -1 : ctx 为空,或者执行状态异常,不生效,<br />
* -2 : 当前正在执行扫描,扫描结果会在 listener 里面返回<br />
* -3 : 当前正处于休眠状态,会将缓存的数据在 listener 里面返回<br />
* -4 : 当前状态异常,会将缓存的数据在 listener 里面返回,通常不会出现,是为了代码的完整性
*/
public int refreshWifiList(Context context);
/**
* 判断某一个 ssid 的 Wi-Fi 类型,
* @param ssid 待判断的 Wi-Fi ssid
* @param wifiType 匹配的 Wi-Fi 类型
* @return 若匹配上,则返回 true,若匹配失败,返回 false,但是返回false 不代表一定不匹配,有可能是还没有扫描过
*/
public boolean determineWifiTypeByCache(String ssid , int wifiType);
// 停止扫描
public void stopScan();
// 清空 Wi-Fi 的缓存
public void clearWifiCache()
Wi-Fi 类型
Code: Select all
/**
* Wi-Fi 类型
*/
public class WifiFrequencyType {
public static final int ONLY_24G = 1; // 仅 2.4G
public static final int ONLY_5G = 2; // 仅 5G
public static final int MIX_24G_AND_5G = 3; // 2.4G 和 5G 混频
}
调用例子
Code: Select all
// 定义数据监听
IThingDataCallback<List<WifiScanBean>> resultCallback = new IThingDataCallback<List<WifiScanBean>>() {
@Override
public void onSuccess(List<WifiScanBean> result) {
// 在这里处理扫描的数据
// 会多次回调
}
@Override
public void onError(String errorCode, String errorMessage) {
}
};
// 注册监听
SmartWifiScanManager.INSTANCE.addWifiScanListener(resultCallback);
// 判断 Wi-Fi 类型
String ssid = "ssid";
if(SmartWifiScanManager.INSTANCE.determineWifiTypeByCache(ssid,WifiFrequencyType.ONLY_24G)
|| SmartWifiScanManager.INSTANCE.determineWifiTypeByCache(ssid,WifiFrequencyType.MIX_24G_AND_5G)){
// 代表该 ssid 是 2.4Ghz 或者 混频
}else{
// 第二步,扫描结果会在上述的回调返回
SmartWifiScanManager.INSTANCE.startScan(ctx);
}
// 在场景下直接停止扫描
// 停止扫描
SmartWifiScanManager.INSTANCE.stopScan();
// 不使用了,记得销毁,避免内存泄漏
SmartWifiScanManager.INSTANCE.removeWifiScanListener(resultCallback);