5、扫描WiFi热点:startScan()与getScanResults()、扫描结果回调、扫描间隔限制
各位同学,今天我们来聊聊WiFi扫描。说白了,就是让你的手机“竖起耳朵”,听听周围有哪些WiFi在“喊话”。
我个人习惯把WiFi扫描比作雷达。你发出一个请求,系统去收集周围的热点信息,然后告诉你结果。这个过程看似简单,但坑不少。尤其是Android 9之后,Google对扫描行为做了严格限制——嗯,这里要重点讲。
5.1 核心API:startScan() 与 getScanResults()
先看最基础的两个方法。它们都在 WifiManager 里。
- startScan():发起一次主动扫描。调用后系统会去执行扫描动作,但结果不会直接返回。
- getScanResults():获取最近一次扫描的结果列表。返回
List<ScanResult>。
注意:startScan() 只是“触发”,结果要通过回调或轮询来拿。我见过不少新手直接调完 startScan() 马上调 getScanResults(),结果拿到的还是旧数据。为什么会这样?因为扫描是异步的,需要时间。
关键点:先调 startScan(),等扫描完成,再调 getScanResults()。别搞反了。
代码示例:
WifiManager wifiManager = (WifiManager) getApplicationContext().getSystemService(Context.WIFI_SERVICE);
// 发起扫描
boolean success = wifiManager.startScan();
if (success) {
// 扫描请求已提交,等待回调
} else {
// 扫描失败,可能是频率太高被限制了
}
// 获取结果(应在扫描完成后调用)
List<ScanResult> results = wifiManager.getScanResults();
for (ScanResult result : results) {
Log.d("WiFiScan", "SSID: " + result.SSID + ", RSSI: " + result.level);
}
5.2 扫描结果回调:BroadcastReceiver + SCAN_RESULTS_AVAILABLE_ACTION
刚才说了,扫描是异步的。那怎么知道扫描完成了?Android提供了广播机制。
你需要注册一个 BroadcastReceiver,监听 WifiManager.SCAN_RESULTS_AVAILABLE_ACTION。当扫描完成,系统会发这个广播。
我个人习惯在 onResume() 里注册,在 onPause() 里注销,避免内存泄漏。
private BroadcastReceiver scanReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (WifiManager.SCAN_RESULTS_AVAILABLE_ACTION.equals(intent.getAction())) {
// 扫描完成,可以获取结果了
List<ScanResult> results = wifiManager.getScanResults();
// 更新UI或处理数据
}
}
};
@Override
protected void onResume() {
super.onResume();
IntentFilter filter = new IntentFilter(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION);
registerReceiver(scanReceiver, filter);
}
@Override
protected void onPause() {
super.onPause();
unregisterReceiver(scanReceiver);
}
小技巧:广播里不要做耗时操作。如果结果需要处理,扔到子线程或Handler里。我曾经因为直接在广播里解析大量数据,导致界面卡顿,被测试骂了一顿。
5.3 扫描间隔限制:Android 9+ 的前台/后台限制
这是个大坑。Android 9(API 28)开始,Google对WiFi扫描做了严格限制。说白了,就是防止App频繁扫描,把电耗光。
具体规则如下:
| 场景 | 限制 |
|---|---|
| 前台应用(有可见Activity) | 每30秒最多4次扫描 |
| 后台应用(无可见Activity) | 每30分钟最多4次扫描 |
注意:这里的“前台”指的是你的App有一个Activity在前台可见。如果用户按了Home键,你的App就进入后台了。后台扫描频率会被大幅降低。
我曾经在做一个室内定位项目,需要频繁扫描WiFi来定位。结果在Android 9的手机上,后台扫描几乎不工作。定位数据一直不更新。排查了半天才发现是扫描限制的问题。
避坑指南:如果你的App需要在后台持续扫描,可以考虑以下方案:
- 使用前台服务(Foreground Service),并显示一个通知。这样系统会认为你的App处于“前台”状态。
- 或者使用
WifiManager.WIFI_STATE_CHANGED_ACTION等被动监听方式,减少主动扫描。 - 如果只是需要获取周围WiFi列表,可以尝试
WifiNetworkSpecifier或NetworkCallback,但适用场景有限。
另外,Android 10 和 11 进一步收紧了限制。比如,后台应用根本拿不到完整的扫描结果,只能拿到部分信息。嗯,这里要提醒大家:如果你的App目标SDK是29+,记得在Manifest里声明 ACCESS_FINE_LOCATION 和 ACCESS_BACKGROUND_LOCATION 权限,否则扫描结果可能为空。
5.4 知识体系总览
下面这张图,帮你理清整个扫描流程的逻辑关系:
从图中可以看到,整个流程是:App发起扫描 → 系统执行 → 广播通知 → App取结果。而扫描限制就像一道“闸门”,在系统执行阶段进行拦截。如果频率太高,startScan() 会直接返回 false。
5.5 实战建议
- 不要依赖轮询:别用
Handler每隔几秒调一次startScan()。系统会直接拒绝。 - 合理利用缓存:
getScanResults()返回的是最近一次扫描的结果,即使扫描被限制,也能拿到旧数据。如果你的App对实时性要求不高,可以复用上次的结果。 - 区分前台/后台:在
onResume()和onPause()里记录当前状态,根据状态决定是否发起扫描。我在项目里就是这么做的,避免后台频繁扫描被系统“拉黑”。
个人经验:如果你需要做WiFi定位或热点收集,建议配合 WorkManager 来调度扫描任务。这样既能遵守系统限制,又能保证任务在合适的时间执行。我曾经用这种方式,在后台每5分钟扫描一次,稳定运行了几个月没出问题。
好了,关于WiFi扫描的核心内容就这些。记住:发起扫描、等待回调、获取结果,同时时刻留意系统限制。下一节我们会讲如何解析 ScanResult 里的详细信息,包括信号强度、频段、加密方式等。到时候见。