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,推送完全收不到。用户打开应用时,发现新闻还是三天前的。

解决方案?我建议在应用被限制时,通过通知引导用户去设置里把应用加入「不受电池优化」的白名单。虽然这不是最佳实践,但有时候为了用户体验,不得不这么做。

一张图看懂区别

下面这张图是我自己整理的,把两个机制的触发、执行、退出流程画清楚了。你保存下来,以后遇到相关问题直接对照着看。

Doze模式 设备静止 + 屏幕关闭 + 未充电 进入Doze:限制网络、Alarm、GPS 维护窗口:短暂放行(几十秒) 退出:设备移动 / 亮屏 / 插电 App Standby 应用长时间未被用户主动使用 标记为Standby:限制后台网络频率 持续低速状态:允许间歇性网络访问 退出:用户主动打开应用 叠加效应:Doze + Standby 同时生效时,限制最严格 建议:使用FCM高优先级消息,或引导用户加入电池优化白名单

实际开发中的避坑指南

最后分享几个我踩过的坑:

  • 不要依赖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