28、WiFi与后台限制:Android 9+ 后台扫描限制、前台服务与WiFi扫描、WorkManager调度WiFi任务
做WiFi开发这么多年,后台扫描限制这块,可以说是踩坑最多的一个点。Android从9.0开始,对后台扫描的限制越来越严。说白了,Google就是想让App别在后台偷偷摸摸地频繁扫描,省电又省资源。
我刚开始接手一个WiFi定位项目时,就遇到了这个问题。App一退到后台,扫描结果就停了,用户反馈说定位不准。嗯,那时候我才真正意识到,后台扫描限制不是闹着玩的。
Android 9+ 的后台扫描限制
先说说这个限制到底是什么。Android 9(API 28)引入了后台扫描限制,核心规则就两条:
- App处于后台时,每2分钟最多只能发起4次WiFi扫描
- 如果App被系统标记为「空闲」状态,扫描频率会更低
为什么会这样?因为每次WiFi扫描都会唤醒WiFi芯片,消耗电量。Google统计过,频繁的后台扫描能让手机续航缩短30%以上。所以这个限制,其实是为了保护用户。
核心要点:后台扫描限制只影响「后台状态」的App。如果你的App在前台,或者有前台服务在运行,扫描频率不受限制。
我记得有一次,测试同事跟我说:「为什么我的测试机扫描正常,你的就不行?」后来发现,他的测试机是Android 8.0,我的已经是Android 10了。嗯,版本差异就是这么坑。
如何判断App是否在后台?
系统判断后台状态,主要看这几点:
- App没有可见的Activity
- 没有运行前台服务(Foreground Service)
- 没有被其他App绑定(Bound Service)
说白了,只要你的App有一个可见的界面,或者有一个前台服务在跑,系统就认为你在前台,扫描限制就不会生效。
前台服务 + WiFi扫描:最常用的方案
我个人习惯的做法是:用前台服务来执行WiFi扫描。这样既保证了扫描频率,又不会触发后台限制。
来看一个简单的实现:
public class WifiScanService extends Service {
private static final int NOTIFICATION_ID = 1001;
private WifiManager wifiManager;
private ScanCallback scanCallback;
@Override
public void onCreate() {
super.onCreate();
wifiManager = (WifiManager) getApplicationContext()
.getSystemService(Context.WIFI_SERVICE);
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
// 构建前台通知
Notification notification = new NotificationCompat.Builder(this, "wifi_channel")
.setContentTitle("WiFi扫描中")
.setContentText("正在后台扫描WiFi信号")
.setSmallIcon(android.R.drawable.ic_menu_search)
.build();
startForeground(NOTIFICATION_ID, notification);
// 开始扫描
startWifiScan();
return START_STICKY;
}
private void startWifiScan() {
scanCallback = new ScanCallback() {
@Override
public void onScanResults(List<ScanResult> results) {
// 处理扫描结果
Log.d("WifiScan", "扫描到 " + results.size() + " 个WiFi");
}
};
wifiManager.registerScanCallback(scanCallback, new Handler());
wifiManager.startScan();
}
@Override
public void onDestroy() {
if (scanCallback != null) {
wifiManager.unregisterScanCallback(scanCallback);
}
super.onDestroy();
}
@Override
public IBinder onBind(Intent intent) {
return null;
}
}
小提示:前台服务的通知不能取消,用户会一直看到。如果你的App需要长时间后台扫描,建议在通知里显示扫描状态,让用户知道你在干什么。
我在项目中遇到过一个问题:前台服务启动后,扫描确实正常了,但用户反馈说手机发热。后来排查发现,是因为扫描间隔太短,WiFi芯片一直在工作。解决方案是加了一个扫描间隔控制,比如每30秒扫描一次。
WorkManager调度WiFi任务
如果你不需要实时扫描,只是想在特定时间点或者特定条件下执行WiFi扫描,那WorkManager是更好的选择。
WorkManager是Android官方推荐的异步任务调度库,它能自动适配系统限制。比如:
- App被杀后,任务仍然会执行
- 系统会合并多个任务,减少唤醒次数
- 支持延迟执行、周期性执行
来看一个WorkManager调度WiFi扫描的例子:
public class WifiScanWorker extends Worker {
private WifiManager wifiManager;
public WifiScanWorker(@NonNull Context context,
@NonNull WorkerParameters params) {
super(context, params);
wifiManager = (WifiManager) context
.getApplicationContext()
.getSystemService(Context.WIFI_SERVICE);
}
@NonNull
@Override
public Result doWork() {
// 检查WiFi是否开启
if (!wifiManager.isWifiEnabled()) {
return Result.success();
}
// 执行扫描
boolean scanSuccess = wifiManager.startScan();
if (scanSuccess) {
List<ScanResult> results = wifiManager.getScanResults();
// 处理结果,比如保存到数据库
saveResults(results);
return Result.success();
} else {
return Result.retry();
}
}
private void saveResults(List<ScanResult> results) {
// 保存扫描结果到本地数据库
}
}
调度这个Worker也很简单:
// 每30分钟执行一次WiFi扫描
PeriodicWorkRequest scanRequest =
new PeriodicWorkRequest.Builder(WifiScanWorker.class,
30, TimeUnit.MINUTES)
.setConstraints(new Constraints.Builder()
.setRequiredNetworkType(NetworkType.NOT_ROAMING)
.build())
.build();
WorkManager.getInstance(context)
.enqueueUniquePeriodicWork(
"wifi_scan",
ExistingPeriodicWorkPolicy.KEEP,
scanRequest
);
注意:WorkManager的最小间隔是15分钟。如果你需要更频繁的扫描,还是得用前台服务。另外,WorkManager不保证任务准时执行,系统可能会延迟执行以节省电量。
我曾经在一个项目中,用WorkManager来做WiFi指纹采集。每天凌晨2点,当用户充电且连接WiFi时,自动采集一次WiFi信号数据。这样既不影响用户体验,又能保证数据更新。
三种方案的对比
| 方案 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
| 前台服务 | 需要实时、高频扫描 | 不受后台限制,扫描频率高 | 用户可见通知,耗电较高 |
| WorkManager | 定时、条件触发扫描 | 系统优化,省电,存活率高 | 最小间隔15分钟,不保证准时 |
| 普通后台扫描 | 不推荐使用 | 实现简单 | 受严格限制,扫描频率极低 |
核心逻辑流程图
你想想看,如果你的App只是每隔几小时采集一次WiFi数据,用前台服务就太浪费了。WorkManager完全够用,而且系统会帮你优化调度。反过来,如果你在做实时定位导航,那前台服务就是唯一的选择。
嗯,最后说一句:不管用哪种方案,记得在AndroidManifest.xml里声明权限。WiFi扫描需要ACCESS_FINE_LOCATION或ACCESS_COARSE_LOCATION权限,而且从Android 10开始,还需要ACCESS_BACKGROUND_LOCATION权限才能在后台扫描。
总结一下:
- Android 9+ 对后台WiFi扫描有严格限制
- 前台服务可以绕过限制,适合高频扫描场景
- WorkManager适合定时、条件触发的扫描任务
- 普通后台扫描基本不可用,别指望它
我曾经踩过一个坑:在Android 11上,即使用了前台服务,如果用户手动关闭了「位置信息」开关,WiFi扫描也会失败。所以记得在代码里检查位置开关状态,并引导用户开启。
公众号:蓝海资料掘金营,微信deep3321