16. 适配Doze模式的最佳实践:使用WorkManager、避免长连接、合理使用Alarm
说实话,Doze模式刚出来那会儿,我团队里好几个项目都踩了坑。用户反馈说「消息收不到」、「闹钟不响」,搞得我们焦头烂额。后来我花了两周时间,把Google的文档翻了个底朝天,又做了大量实测,才总结出今天要讲的这三条核心实践。
这三条实践,说白了就是:用WorkManager替代后台Service、别让长连接在Doze下硬撑、把Alarm交给系统管理。咱们一条一条拆开聊。
16.1 使用WorkManager:你的后台任务救星
我个人习惯,从Android 6.0开始,所有后台任务都优先考虑WorkManager。为什么?因为它能自动适配Doze模式。
WorkManager会把你的任务交给系统调度。系统会根据Doze状态、电量、网络条件,决定什么时候执行。你想想看,这比你自己写个Service在那死撑,要优雅得多。
举个例子,假设你要定期上传日志:
// 定义任务
val uploadWork = OneTimeWorkRequestBuilder<LogUploadWorker>()
.setConstraints(
Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.setRequiresBatteryNotLow(true)
.build()
)
.build()
// 提交任务
WorkManager.getInstance(context).enqueue(uploadWork)
我在项目中遇到过一个问题:有个同事用JobScheduler自己写调度逻辑,结果在Doze下任务被无限延迟。换成WorkManager后,问题迎刃而解。因为WorkManager内部已经处理了Doze的窗口期问题。
16.2 避免长连接:别在Doze下硬撑
长连接,比如WebSocket、MQTT,在Doze模式下是个大坑。系统会限制网络访问,你的连接会被频繁断开重连,反而更耗电。
我曾经有个项目,在Doze下保持长连接,结果用户手机一晚上掉了30%的电。后来我们改成了FCM推送 + 定时心跳,电量消耗降到了5%以下。
那具体怎么做?我建议:
- 用FCM替代长连接:Google的推送服务,天然适配Doze
- 如果必须用长连接:在Doze进入前主动断开,等窗口期再重连
- 别用心跳包硬撑:系统会把它当成普通网络请求,照样受限
你想想看,Doze的设计初衷就是省电。你非要跟它对着干,结果只能是用户卸载你的App。
16.3 合理使用Alarm:别滥用setExact
AlarmManager在Doze下有两种模式:setExact 和 setAndAllowWhileIdle。前者在Doze下会被延迟,后者虽然能唤醒设备,但频率受限。
我记得有一次,产品经理要求闹钟必须精确到秒。我跟他解释:Doze下做不到。后来我们妥协了,允许1分钟的误差,用setAndAllowWhileIdle实现。
这里有个表格,帮你快速决策:
| 场景 | 推荐API | 说明 |
|---|---|---|
| 闹钟、提醒 | setAlarmClock | 最高优先级,会退出Doze |
| 定时任务 | setAndAllowWhileIdle | 每分钟最多一次,Doze下可用 |
| 非紧急任务 | setExact | Doze下会被延迟到窗口期 |
| 周期性任务 | setInexactRepeating | 系统会合并触发,省电 |
嗯,这里要注意:setAndAllowWhileIdle 虽然能唤醒设备,但系统会限制频率。如果你频繁调用,系统会忽略你的请求。所以,别用它做心跳。
16.4 知识体系总览
说了这么多,咱们用一张图来总结。这张图展示了Doze适配的三大核心实践,以及它们之间的关系:
这张图你看懂了吗?三个模块是并列关系,但底层逻辑一致:尊重系统的省电策略。WorkManager帮你自动适配,避免长连接是主动让步,合理使用Alarm是精准控制。
最后说一句:Doze适配没有银弹。但只要你遵循这三条实践,90%的问题都能解决。剩下的10%,嗯,那就要看具体业务场景了。到时候咱们再细聊。