19. 系统级功耗策略:Doze模式与相机、App Standby影响、电池优化白名单

说到系统级功耗策略,很多做相机开发的同事容易忽略一个事实——你的相机App并不是在真空中运行的。它活在Android系统的大生态里,而系统为了省电,会时不时地给App“使绊子”。

Doze模式、App Standby、还有那个让人又爱又恨的电池优化白名单,这三样东西,说白了就是系统用来管理App后台行为的三大法宝。但对我们相机App来说,它们有时候是帮手,有时候却是拦路虎。

核心观点:相机App必须主动适配系统级省电策略,否则用户可能遇到“打开相机黑屏3秒”、“扫码一直转圈”、“预览卡顿”等体验问题。这不是Bug,是系统在帮你省电。

19.1 Doze模式:相机App的“休眠杀手”

Doze模式从Android 6.0开始引入,到Android 9之后越来越严格。它的逻辑很简单:手机静止不动、屏幕关闭一段时间后,系统会限制App的网络访问、延迟Job和Alarm。

我遇到过最典型的场景:用户把手机放在桌上充电,屏幕关了,然后通过语音助手说“拍照”。结果相机App被Doze模式按住了,启动花了5秒多。用户骂的是相机卡,其实冤枉的是Doze。

Doze对相机的影响点

  • 网络访问受限:云相册上传、实时滤镜下载、地理位置获取都会被延迟
  • AlarmManager延迟:定时拍照、延时摄影的触发可能不准
  • JobScheduler暂停:后台处理RAW转JPEG的任务会被挂起
  • WakeLock失效:系统可能忽略部分WakeLock请求

我的经验:在Doze模式下,Camera2 API的CameraDevice仍然可以打开,但open()的耗时可能从正常的200ms飙升到1-2秒。这是因为系统在进入Doze时回收了部分Camera HAL资源。我个人习惯在onResume()之前先检查PowerManager.isDeviceIdleMode(),如果处于Doze状态,提前做一次“预热”操作。

如何绕过Doze限制(合法方式)

系统提供了两种机制让App在Doze模式下仍能工作:

  1. 使用高优先级FCM消息:通过Firebase Cloud Messaging的“高优先级”消息可以临时唤醒App,但相机场景下不太实用
  2. 申请临时白名单:调用PowerManager.requestPowerSaveMode()相关API,但需要用户授权
  3. 利用前台服务:相机App如果正在录制视频,系统不会进入深度Doze
// 检查当前是否处于Doze模式
PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
if (pm.isDeviceIdleMode()) {
    // 提示用户:相机可能启动较慢
    // 或者主动请求临时退出Doze
    Intent intent = new Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS);
    intent.setData(Uri.parse("package:" + getPackageName()));
    startActivity(intent);
}

警告:不要滥用REQUEST_IGNORE_BATTERY_OPTIMIZATIONS权限。Google Play审核会严格检查,如果你的App不是核心功能需要(比如相机、地图导航、健康监测),大概率会被拒。我曾经有个项目因为加了这行代码被拒了两次,最后改成了引导用户手动添加白名单。

19.2 App Standby:后台相机的“隐形枷锁”

App Standby是Android 6.0引入的另一套机制。系统会跟踪App的使用频率,把不常用的App标记为“Standby Bucket”中的低优先级。相机App如果被用户冷落几天,就会被扔到“Rare”桶里。

你想想看,一个被标记为“Rare”的相机App,后台能做什么?几乎什么都做不了。网络访问被限制,Job被推迟,连广播接收器都可能被延迟。

Standby Bucket等级

Bucket等级 网络限制 Job/Alarm延迟 典型场景
ACTIVE 无限制 无延迟 用户正在使用相机
WORKING_SET 10分钟内10次 2小时内 用户偶尔打开相机
FREQUENT 1小时内10次 8小时内 用户每周用几次
RARE 8小时内10次 24小时内 用户很久没用相机
NEVER 完全限制 无限延迟 用户主动限制后台

这里有个坑:如果相机App被标记为RARE,即使你通过CameraManager.openCamera()成功打开了摄像头,后续的CaptureRequest回调也可能因为网络限制而失败——比如你需要在拍照后上传缩略图到云端。

避坑指南:我曾经在开发一款行车记录仪App时,发现用户反馈“录像文件上传失败”。查了半天,原来是App被系统标记为FREQUENT,后台网络请求被限制了。解决方案是:在录像开始时启动前台服务,并绑定通知,这样系统会把App提升到ACTIVE桶。前台服务是相机App对抗App Standby最有效的武器。

19.3 电池优化白名单:相机App的“免死金牌”

电池优化白名单,就是系统设置里那个“忽略电池优化”的开关。用户可以在设置 > 应用 > 特殊访问权限 > 电池优化中找到它。

对于相机App来说,加入白名单意味着:

  • Doze模式下网络访问不受限
  • App Standby不会降级到RARE
  • AlarmManager和JobScheduler正常执行
  • WakeLock不会被系统忽略

哪些相机App应该申请白名单?

不是所有相机App都需要。我个人建议以下场景必须引导用户加入:

  1. 安防/监控类相机:需要后台持续运行,检测运动事件
  2. 行车记录仪:需要后台录制并上传视频
  3. 远程相机/婴儿监视器:需要保持网络连接
  4. 延时摄影App:需要定时触发拍照

普通拍照App其实不需要。你想想看,用户拍完照就退出了,后台没什么事要做,强行申请白名单反而会让用户觉得你的App“流氓”。

如何优雅地引导用户加入白名单?

// 检查是否已加入白名单
PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
if (!pm.isIgnoringBatteryOptimizations(getPackageName())) {
    // 弹出一个友好的对话框,解释为什么需要
    new AlertDialog.Builder(context)
        .setTitle("需要关闭电池优化")
        .setMessage("为了确保相机在后台能正常录制和上传,建议关闭电池优化。\n\n" +
                     "我们不会在后台做任何额外耗电操作,请放心。")
        .setPositiveButton("去设置", (dialog, which) -> {
            Intent intent = new Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS);
            intent.setData(Uri.parse("package:" + getPackageName()));
            startActivity(intent);
        })
        .setNegativeButton("不了", null)
        .show();
}

重要提醒:从Android 11开始,ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS的弹窗只能触发一次。如果用户拒绝了,后续再调用不会弹出任何界面。所以第一次弹窗时一定要把话说明白,别浪费机会。我见过有的App连续弹了3次,结果用户直接卸载了。

19.4 系统级策略的联动影响

这三个机制不是独立工作的。它们会互相影响,形成一套组合拳:

  • Doze模式 + App Standby = 双重限制,相机App几乎无法后台工作
  • 白名单可以同时绕过Doze和Standby的限制
  • 前台服务可以临时提升Standby等级,但不能完全绕过Doze
  • FCM高优先级消息可以唤醒Doze,但无法改变Standby等级

我画了一张图,帮你理清它们之间的关系:

系统级省电策略对相机App的影响关系图 Doze模式 屏幕关闭+静止 App Standby 使用频率分级 电池优化白名单 用户授权豁免 限制网络/Alarm 延迟Job/广播 相机App 预览/拍照/录像/上传 豁免限制 前台服务 提升Standby等级 FCM高优先级消息 临时唤醒Doze 红色箭头=限制,绿色虚线=豁免,紫色/青色=对抗策略

19.5 实战建议:如何设计相机App的省电策略

说了这么多理论,最后给几条实战建议:

  1. 区分场景:普通拍照App不需要白名单,监控类App必须引导用户加入
  2. 使用前台服务:录制视频或后台处理时,启动前台服务并绑定通知,这是最稳妥的方式
  3. 监听系统状态:注册ACTION_POWER_SAVE_MODE_CHANGEDACTION_DEVICE_IDLE_MODE_CHANGED广播,动态调整行为
  4. 合理使用JobScheduler:把非紧急任务(如缩略图生成、日志上传)放到JobScheduler中,让系统决定何时执行
  5. 测试要全面:在Doze模式和App Standby的各种桶等级下测试相机功能,别只在ACTIVE状态下测

我的习惯:每次发布新版本前,我会用adb shell dumpsys deviceidle force-idle强制进入Doze,再用adb shell am set-standby-bucket把App设成RARE,然后测试相机启动、拍照、录像、上传全流程。这个测试流程帮我发现过至少5个线上问题。

系统级功耗策略,说白了就是系统在帮你省电,但有时候省过头了。作为相机App开发者,我们要做的不是对抗系统,而是理解它的规则,然后优雅地适应它。嗯,这就是我这些年踩坑踩出来的经验。


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