Android后台调度任务与省电

本文最后更新于:2017年4月27日 上午

I. Handler:

在进程存活的期间有效使用, Google官方推荐使用。
相关机制可以参见: Android Handler Looper机制

  • 简单易用。
  • 稳定高效。

II. AlarmManager:

利用系统层级的闹钟服务(持有Wake Lock)。
在一些特定场景中,可以根据策略对进行省电优化,如微信的Mars中心跳机制

1. 使用场景

在大概的时间间隔(重复)运行指定任务。
在精确的时间间隔(重复)运行指定任务。

  • 需要精确的定时(重复)任务,如闹钟。
  • 非网络访问的,大概时间间隔的定时(重复)任务。
  • Google官方不建议网络请求相关的业务使用AlarmManager

2. 特征

  • 运行在系统的闹钟服务上的,注册以后,无论是自己的应用进程或组件是否存在,都会正常运作。
  • 所有注册的闹钟服务都会在系统重启后复位,因此如果需要保证任务,就需要注册RECEIVE_BOOT_COMPLETE广播,确保重启后,可以重新将任务注册到闹钟服务中。
  • AlarmManager处理的是一个PendingIntent
  • 考虑到电量损耗,建议非特殊情况使用大概时间间隔的规则,这样Android会尽量让几个任务打包在一起执行,防止频繁的唤起手机。

III. Job Scheduler:

JobScheduler官方文档

1. 使用场景

在指定特定场景下执行指定任务

  • Google官方建议网络请求相关业务放到Job Scheduler,由于其的省电的特性。
  • 一些与特定场景(JobInfo)绑定的任务。

2. 特征

  • Job Scheduler只有在Api21或以上的系统支持。
  • Job Scheduler是将多个任务打包在一个场景下执行。
  • 在系统重启以后,任务会依然保留在Job Scheduler当中,因此不需要监听系统启动状态重复设定。
  • 如果在一定期限内还没有满足特定执行所需情况,Job Scheduler会将这些任务加入队列,并且随后会进行执行。

3. 接口类型

1
2
3
4
5
6
7
8
9
10
boolean onStartJob(JobParams params) {
// 开始执行
// 注意这个方法是在主线程执行的,如果是耗时操作请抛到独立线程中
// jobFinished(JobParameters params) // 在完成任务并且决定是否还需要定时执行更多任务
// return 是否是在独立现在还有事务要执行
}

void onStopJob(){
// 用于清理数据,在结束任务后被回调。
}

IV. GCM(FCM)

GCM Network Manager实际上在 Api 21 或以上也是使用了 Job Scheduler,在此之前的版本使用的是Google Play Service中实现Job Scheduler的功能。
GCMNetworkManager中有很多利于省电的规则。
在中国内地,该服务被墙,无法正常使用。

1. 使用场景

  • 实时消息推送。
  • 非实时消息推送。

2. 特征

  • 系统级别维护的长链接,十分稳定。

3. 接口类型

  • 通过 OneoffTask.Builder()PeriodicTask.Builder()创建任务。
  • GcmTaskService#onRunTask(TaskParams params)是在后台线程执行的。

V. Sync Adapter

Transferring Data Using Sync Adapters

1. 使用场景

用于同步服务端与本地设备中的数据。

  • 通常是用于同步较多的数据,如系统联系人信息、Dropbox等。

2. 特征

  • 省电稳定。
  • 可绑定一个账户。
  • 利于大数据同步。
  • 通过提供ContentProvider,可以快捷的与服务端同步的数据库。
  • 只有在存在网络的时候才触发同步。
  • 不需要依赖Google Play Service。
  • 用户可以通过设置中主动查看同步的时间,以及触发同步,或者关闭同步。
  • Sync AdapterAPI7或以上就可以使用,因此在一些场景下这是Job SchedulerAPI21之前比较好的替代品。

3. 在一定的场景下触发同步

尽可能的打包所有需要同步的任务在一个周期中执行,以此来进行尽可能的节省手机电量。

  • 服务端或设备端数据发生变化。
  • 手机闲置时。
  • 一天。
  • 如果同步失败,会放到同步失败的队列中,在尽可能的时候进行同步。

VI. Doze Mode

Deep Doze Mode

API23中直接称其为Doze Mode

1. 特征

旨在: 在用户离开设备以后,尽可能的减少手机电量的消耗。

  • 无论应用指定的Target SDK是多少,只要设备是Android 6或以上会启用该模式。
  • 开发人员并不需要做特殊的适配,但是会对上面提到的所有Schedule的方式(Job SchedulerAlarmManagerSyncs Adapter)产生影响。

所有任务周期通过移动窗口打包任务执行,并且间隔时间会越来越久。

2. 进入条件

会同时满足以下情况一段时间(大约30分钟)以后生效:

  • 手机没有在充电
  • 屏幕被关闭
  • 手机各方状态保持稳定

退出条件是,进入条件中任意条件状态发生变化。

3. 在两个处理窗口之间的手机状态
  1. 对所有应用拒绝网络访问。
  2. 所有JobSchedulerSync-AdapterAlarmManager的任务都会被延后到窗口中执行。
  3. 系统会拒绝所有来自应用的Wake Lock
  4. 停止所有Wifi以及GPS扫描
  5. 减少位置事件从设备检测WiFi热点。

Light Doze Mode

Android 7或以上会启用该模式。

1. 特征
  • 相比Deep Doze Mode,打包执行任务的频率会更高些。

2. 进入条件

会同时满足以下情况一段时间(大约几分钟)以后生效:

  • 手机没有在充电
  • 屏幕被关闭
  • 处于稳定状态/不稳定状态

或者在Deep Doze Mode的情况下同时满足以下条件下生效:

  • 屏幕关闭
  • 手机没有在充电
  • 手机不再处于稳定状态
3. 退出条件
  • 屏幕打开
  • 手机开始充电
  • 进入Deep Doze Mode
4. 在两个处理窗口之间的手机状态
  • 对所有应用拒绝网络访问。
  • 所有JobSchedulerSync Adapter的任务都会被延后到窗口中执行。
  • 不会对AlarmManager中的任务进行影响,但是将无网络访问(如果你的任务需要网络访问,是时候改用JobSchedulerSync Adapter了,这样才会保证在任务窗口执行会有网络)

中断/避开Doze

以下所有情况,Google官方都建议不在特殊情景,不要去使用,由于中断了Doze Mode的省电规则。

1. AlarmManager
  • 在精确的时间间隔中运行的任务: setAndAllowWhileIdle()setExactAndAllowWhileIdle()。但是在非窗口期间并不解除无网络访问的限制,并且只有10s的时间给予处理。
  • 指定闹钟事件AlarmManager.setAlarmClock()的事件会在闹钟结束前,令系统短暂的完全退出Doze模式,并且正常处理事件,系统为了突显该闹钟事件,将会在系统的Status Bar上显示物理闹钟的ICON。
2. FCM/GCM

(Firebase Cloud Messaging,旧版中称为Google Cloud Messaging(GCM))。

FCM/GCM中高优先级的任务配置中("priority" : "high") 的消息,在Doze模式下可以正常及时到达。

3. 白名单

白名单官方文档
官方建议可考虑加入白名单的情况

  • 主动请求加入白名单,用户同意以后才加入白名单;
  • 用户也可以主动将应用从白名单中删除或将应用添加到白名单中;
  • 应用可以通过isIgnoringBatteryOptimizations()来获知是否在白名单中;
  • 白名单的应用可以访问网络与持有有效的Wake Lock,但是其他Doze的约束依然存在(如延后的Job SchedulerSyncs-AdapterAlarmManager);

白名单的请求方式:

4. 特殊情况

前台服务(Foreground Service)将不会受到Doze模式影响。

Doze模式测试

Google官方提供了一些adb命令用于测试Doze模式,而非需要通过等待来进入Doze模式的。

1. 进入Doze模式
  • 准备一台系统是在Android Nougat Developer Preview4或以上版本的设备。
  • 将其连接连接到电脑。
  • 通过执行adb shell dumpsys battery unplug命令让设备进入未连接充电的模式。
  • 通过执行adb shell dumpsys deviceidle step [light|deep]强行进入Doze模式。

退出Doze模式,让手机恢复正常需要复位充电模式:adb shell dumpsys battery reset

2. 其他指令
  • 获取设备状态:adb shell dumpsys deviceidle get [light|deep|force|screen|charging|network]

Android Nougat Developer Preview 4中,Doze模式的状态周期是:

1
2
Light: ACTIVE -> IDLE -> IDLE_MAINTENANCE -> OVERRIDE
Deep: ACTIVE -> IDLE_PENDING -> SENSING -> LOCATING -> IDLE -> IDLE_MAINTENANCE

VII. Background Limit - Android O

在Android O中引入了Background Limit,主要从以下三个方面让手机更加省电,更少的资源开销。

Android O的Background Limit中定义的后台应用与内存管理时的进程级别不同,这里的应用在后台的定义是: 没有可见的Activity && 没有前台服务 && 没有前台应用绑定当前应用的其中一个服务 && 没有前台应用通过ContentProvider绑定当前应用

后台位置限制

无论目标SDK版本为何,都会对后台应用检索用户当前位置的频率进行限制。应用在后台每小时只能接收几次位置更新。

具体内容可参见: 后台位置限制

后台服务限制

当目标SDK版本大于25,当应用进入后台时,在一个持续数分钟的时间窗内,应用仍可以创建和使用服务。 在该时间窗结束后,应用将被视为处于_空闲_状态。 此时,系统将停止应用的后台服务,就像应用已经调用服务的 Service.stopSelf()方法一样。

具体内容可参见: 后台服务限制

广播限制

隐式广播是一种不专门针对该应用的广播,一般是系统的一些通用事件(除了一些例外的)与应用发送给其他应用的广播(非LocalBroadcastmanager的)

由于有太多的应用注册那些基于系统事件的广播来保活,使得容易造成内存瓶颈以及电量消耗的很快的问题,因此 当目标SDK版本大于25,无论有没有插电源,该功能都会被开启。

隐式广播只有在运时使用Context.registerReceiver()注册接收器才能接收的到,如果你是通过AndroidManifest注册的,在Logcat中会受到一个BroadcastQueue警告级别的日志,而收不到对应的广播。

建议: 一些需要隐式广播的地方,可以考虑使用JobScheduler来执行,或者替换为非隐式广播、如果是需要服务常驻,可以考虑使用前台服务而非后台服务。如果你有监听隐式广播,那么是时候思考下应该如何适配targetSDKVersion大于25的场景了。

具体内容可参见: 广播限制




Android后台调度任务与省电
https://blog.dreamtobe.cn/2016/08/15/android_scheduler_and_battery/
作者
Jacksgong
发布于
2016年8月15日
更新于
2017年4月27日
许可协议