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列表,可以尝试 WifiNetworkSpecifierNetworkCallback,但适用场景有限。

另外,Android 10 和 11 进一步收紧了限制。比如,后台应用根本拿不到完整的扫描结果,只能拿到部分信息。嗯,这里要提醒大家:如果你的App目标SDK是29+,记得在Manifest里声明 ACCESS_FINE_LOCATIONACCESS_BACKGROUND_LOCATION 权限,否则扫描结果可能为空。

5.4 知识体系总览

下面这张图,帮你理清整个扫描流程的逻辑关系:

WiFi扫描流程与限制 App调用startScan() 系统执行扫描 发送SCAN_RESULTS_AVAILABLE App获取getScanResults() 扫描限制(Android 9+) 前台:30秒内最多4次 后台:30分钟内最多4次 超出限制 startScan() 返回false

从图中可以看到,整个流程是:App发起扫描 → 系统执行 → 广播通知 → App取结果。而扫描限制就像一道“闸门”,在系统执行阶段进行拦截。如果频率太高,startScan() 会直接返回 false

5.5 实战建议

  • 不要依赖轮询:别用 Handler 每隔几秒调一次 startScan()。系统会直接拒绝。
  • 合理利用缓存getScanResults() 返回的是最近一次扫描的结果,即使扫描被限制,也能拿到旧数据。如果你的App对实时性要求不高,可以复用上次的结果。
  • 区分前台/后台:在 onResume()onPause() 里记录当前状态,根据状态决定是否发起扫描。我在项目里就是这么做的,避免后台频繁扫描被系统“拉黑”。

个人经验:如果你需要做WiFi定位或热点收集,建议配合 WorkManager 来调度扫描任务。这样既能遵守系统限制,又能保证任务在合适的时间执行。我曾经用这种方式,在后台每5分钟扫描一次,稳定运行了几个月没出问题。

好了,关于WiFi扫描的核心内容就这些。记住:发起扫描、等待回调、获取结果,同时时刻留意系统限制。下一节我们会讲如何解析 ScanResult 里的详细信息,包括信号强度、频段、加密方式等。到时候见。


公众号:蓝海资料掘金营,微信deep3321