Doze模式与App Standby的区别:两种省电机制的对比
Android系统里有两套省电机制,Doze模式和App Standby。很多开发者容易搞混,觉得它们差不多。其实不然,这两兄弟虽然目标一致——省电,但打法完全不同。
我刚开始接触时也犯过糊涂。有一次项目里,我们的IM应用在后台收不到消息,我第一反应是「肯定是Doze搞的鬼」。查了半天日志,发现根本不是Doze,而是App Standby把网络访问给限制了。嗯,从那以后,我就把这两个机制彻底分清楚了。
核心区别:一个管「什么时候睡」,一个管「谁该睡」
说白了,Doze模式管的是设备整体的状态。手机放那儿不动,屏幕关了,没插电,系统就会进入Doze。这时候所有应用都得乖乖听话,网络访问被限制,同步任务被推迟。
App Standby呢?它管的是单个应用的行为。你很久没打开某个应用了,系统就觉得「这应用估计用户不关心」,于是给它打上Standby标签,限制它的后台行为。
我习惯这样理解:Doze是「集体宿舍熄灯」,到点了大家都得安静;App Standby是「个别同学被点名」,谁不活跃谁被限制。
| 对比维度 | Doze模式 | App Standby |
|---|---|---|
| 触发条件 | 设备静止、屏幕关闭、未充电 | 应用长时间未被用户主动使用 |
| 作用范围 | 所有应用(全局) | 单个应用(局部) |
| 限制内容 | 网络、同步、Alarm、GPS | 网络访问、后台服务、JobScheduler |
| 退出条件 | 设备移动、亮屏、插电 | 用户主动打开应用 |
| API级别 | Android 6.0 (API 23) | Android 6.0 (API 23) |
触发条件:一个看「环境」,一个看「行为」
Doze的触发条件很「物理」:手机不动、屏幕黑着、没插充电器。这三个条件缺一不可。你想想看,手机放桌上充电,屏幕关了,但插着电,Doze就不会启动。这是Google的设计哲学——既然连着电源,就别省电了。
App Standby的触发条件就「主观」多了。系统会记录每个应用的最后使用时间。如果一个应用连续几天甚至几周没被用户主动打开,系统就会把它标记为Standby状态。
关键点:「主动打开」指的是用户点击图标启动,或者从最近任务切换过来。后台被系统拉起、被广播唤醒,这些都不算「主动使用」。
我在项目中遇到过一个问题:我们的天气应用每天后台更新数据,用户其实一直在看通知栏的天气信息,但从来没打开过应用本身。结果一周后,App Standby把它限制了,天气数据不更新了。用户投诉说「天气预报不准了」。这就是典型的「用户以为在用,系统认为没用」的尴尬。
限制力度:一个「断网」,一个「限速」
Doze模式下手比较狠。进入深度Doze后,网络访问基本被切断。Alarm也被推迟到下一个维护窗口(maintenance window)。GPS和WiFi扫描也被限制。说白了,应用在Doze期间几乎处于「离线状态」。
App Standby相对温和一些。它不会完全断网,但会限制网络访问的频率。系统允许Standby状态的应用每隔一段时间(通常是几小时)进行一次短暂的网络访问。这就像给应用开了个「限速通道」,能用,但别想畅快。
我的建议:如果你的应用需要频繁后台更新,优先考虑使用Firebase Cloud Messaging(FCM)的高优先级消息。FCM可以绕过Doze和App Standby的限制。但别滥用,否则会被Google Play下架。
维护窗口:Doze的「放风时间」
Doze模式有一个很有意思的设计——维护窗口。系统不会一直限制应用,而是每隔一段时间(大约几分钟到几十分钟)打开一个短暂的窗口,让所有待处理的网络请求、Alarm、同步任务集中执行。
这个窗口期很短,通常只有几十秒。所有应用都得在这段时间内完成自己的「汇报工作」。过了这个村就没这个店了,得等下一个维护窗口。
App Standby没有维护窗口的概念。它更像是一个「持续的低速状态」。应用随时可以发起网络请求,但系统会限制频率。如果频率过高,请求会被直接丢弃。
如何检测当前状态?
开发时怎么知道应用当前被哪个机制限制了?我一般用这两个方法:
// 检测是否处于Doze模式
PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
if (pm.isDeviceIdleMode()) {
// 设备处于Doze模式
Log.d("Power", "设备在Doze中,网络访问受限");
}
// 检测应用是否处于Standby状态
UsageStatsManager usm = (UsageStatsManager) getSystemService(Context.USAGE_STATS_SERVICE);
if (usm.isAppInactive(packageName)) {
// 应用被标记为Standby
Log.d("Power", "应用处于Standby状态,后台行为受限");
}
注意:isAppInactive()方法需要android.permission.PACKAGE_USAGE_STATS权限,并且用户需要在设置中手动授予「使用情况访问权限」。否则会一直返回false。我曾经因为这个坑排查了半天,最后发现是权限没给。
两者的叠加效应
最麻烦的情况是Doze和App Standby同时生效。设备进入Doze,同时你的应用又被标记为Standby。这时候限制是最严格的——不仅网络被切断,连维护窗口内的访问都会被进一步限制。
我遇到过最极端的案例:一个新闻应用,用户安装后只打开过一次,之后全靠后台推送。结果设备进入Doze,应用又处于Standby,推送完全收不到。用户打开应用时,发现新闻还是三天前的。
解决方案?我建议在应用被限制时,通过通知引导用户去设置里把应用加入「不受电池优化」的白名单。虽然这不是最佳实践,但有时候为了用户体验,不得不这么做。
一张图看懂区别
下面这张图是我自己整理的,把两个机制的触发、执行、退出流程画清楚了。你保存下来,以后遇到相关问题直接对照着看。
实际开发中的避坑指南
最后分享几个我踩过的坑:
- 不要依赖AlarmManager做精确唤醒。 Doze模式下,setExact()和setAlarmClock()以外的Alarm都会被推迟。我建议用setAndAllowWhileIdle(),但注意频率限制——每分钟最多一次。
- JobScheduler在Standby下会被延迟。 如果你的应用处于Standby状态,JobScheduler的任务执行间隔会被拉长。我习惯用WorkManager,它内部已经处理了这些兼容性问题。
- 测试时别用adb命令模拟。 我曾经用adb shell dumpsys deviceidle force-idle强行进入Doze,结果发现有些行为在真实场景下根本不会触发。后来我老老实实把手机放桌上等15分钟,模拟真实Doze进入流程。
- 用户反馈「收不到推送」时,先查日志。 用adb shell dumpsys battery stats看设备状态,用adb shell dumpsys appops看应用权限。很多时候不是代码问题,是用户自己把应用给「冻住」了。
Doze和App Standby,一个管全局,一个管个体。理解它们的区别,你才能在开发中做出正确的优化策略。别像我刚开始那样,一遇到后台问题就甩锅给Doze,结果发现是App Standby在捣乱。
公众号:蓝海资料掘金营,微信deep3321