WorkManager
Android为后台执行任务提供了多种解决方案:JobScheduler
、Loader
、Service
等。但它们可能会消耗大量的电量。Android在耗电问题上又做了各种尝试,从Doze到App Standby。
WorkManager为应用程序中那些不需要及时完成的任务(如后台执行任务)提供统一的解决方案,以便在设备电量和用户体验之间达到一个较好的平衡。
WorkManager的3个重要特点
针对的是不需要及时完成的任务。
例如,发送应用程序日志、同步应用程序数据、备份用户数据等,站在业务需求的角度,这些任务都不需要立即完成。如果我们自己管理这些任务,逻辑会很复杂,若API使用不恰当,会消耗大量电量。
保证任务一定会被执行。
WorkManager能保证任务一定会被执行,即使应用程序当前不在运行中,设置在设备重启过后,任务仍然会在适当的时刻被执行。这是因为WorkManager有自己的数据库,关于任务的所有信息和数据都保存在该数据库中。因此,只要任务交给了WorkManager,哪怕应用程序彻底退出,或者设备被重启,WorkManager仍能保证完成你交给它的任务。
兼容范围广。
WorkManager最低能兼容API Level 14,并且不需要你的设备安装 Google Play Services。因此,不用过于担心兼容性问题,因为 API Level 14 已经能兼容几乎100%的设备。
WorkManager的兼容方案
WorkManager依据设备的情况会选择不同的执行方案。在API Level 23以上的设备会通过 JobScheduler 完成任务;在 API Level 23以下的设备中会通过 AlarmManager 和 BroadcastReceiver 组合来完成任务。这两种方案,任务最终都是交由 Executor 来执行。
注意:WorkManager不是一种新的工作线程,它的出现不是为了替代其他类型的工作线程。工作线程通常立即运行,并在任务完成后给用户反馈。而WorkManager不是即时的,不能保证任务能立即得到执行。
WorkManager的基本使用方法
在 app/build.gradle
中添加 WorkManager 所需的依赖
1 | dependencies{ |
使用 Worker 类定义任务
假设要实现将日志上传到服务器的需求。新建一个名为UploadLogWorker的类,该类继承自Worker类,并且覆盖doWork()方法,所有需要在任务中执行的代码都在该方法中进行编写。
1 | public class UploadLogWorker extends Worker{ |
doWork() 方法有3中类型的返回值
- 若执行成功,则返回Result.success()。
- 若执行失败,则返回Result.failure()。
- 若需要重新执行,则返回Result.retry()。
使用WorkRequest配置任务
配置任务就是在告诉系统,任务何时运行及如何运行。
设置任务触发条件。
例如,我们可以设置当设备处于充电,网络已连接,且电池电量充足的状态下,才触发任务。
1
2
3
4
5Constraints constraints = new Constraints.Builder()
.setRequiresCharging(true)
.setRequiredNetworkType(NetworkType.CONNECTED)
.setRequiresBatteryNotLow(true)
.build();将任务触发条件设置到WorkRequest。
WorkRequest是一个抽象类,它有两种实现方式——OneTimeWorkRequest和PeriodicWorkRequest,分别对应的是一次性任务和周期性任务。
1
2
3
4OneTimeWorkRequest uploadWorkRequest = new OneTimeWorkRequest.Builder(UploadLogWorker.class)
//设置触发条件
.setConstraints(constraints)
.build();设置延迟执行任务。
假设没有设置触发条件,或者所设置的触发条件此刻符合系统的执行要求,此时,系统有可能会立刻执行该任务。但如果你希望能够延迟任务的执行,那么可以通过setInitialDelay()方法,对任务进行延后。
1
2
3
4OneTimeWorkRequest uploadWorkRequest = new OneTimeWorkRequest.Builder(UploadLogWorker.class)
//符合触发条件后,延迟10s执行
.setInitialDelay(10, TimeUnit.SECONDS)
.build();设置指数退避策略。
假如Worker线程的执行出现了异常,如服务器宕机。你可能希望过一段时间后,重试该任务。那么可以在Worker的doWork()方法中返回Result.retry(),系统会有默认的指数退避策略来帮你重试任务,也可以通过setBackoffCriteria()方法来自定义指数退避策略。
1
2
3
4OneTimeWorkRequest uploadWorkRequest = new OneTimeWorkRequest.Builder(UploadLogWorker.class)
//设置指数退避算法
.setBackoffCriteria(BackoffPolicy.LINEAR, OneTimeWorkRequest.MIN_BACKOFF_MILLIS, TimeUnit.MILLISECONDS)
.build();为任务设置tag标签。
设置tag标签后,你就可以
通过该标签跟踪任务的状态:WorkManager.getWorkInfosByTagLiveData(String tag);
也可以取消任务:WorkManager.cancelAllWorkByTag(String tag)。
1
2
3OneTimeWorkRequest uploadWorkRequest = new OneTimeWorkRequest.Builder(UploadLogWorker.class)
.addTag("UploadTag")
.build();
将任务提交给系统
将任务配置好之后,需要将其提交给系统,WorkManager.enqueue()方法用于将配置好的WorkRequest交给系统来执行。
1 | WorkManager.getInstance(this).enqueue(uploadWorkRequest); |
观察任务的状态
任务在提交给系统后,可以通过WorkInfo
获知任务的状态。WorkInfo
包含任务的id
、tag
、Worker
对象传递过来的outputData
,以及任务当前的状态。有3种方式可以得到WorkInfo
对象。
WorkManager.getWorkInfosByTag()
WorkManager.getWorkInfoById()
WorkManager.getWorkInfosForUniqueWork()
如果希望实时获知任务的状态,这3个方法还有对应的LiveData
方法。
WorkManager.getWorkInfosByTagLiveData()
WorkManager.getWorkInfoByIdLiveData()
WorkManager.getWorkInfosForUniqueWorkLiveData()
通过LiveData
,我们便可以在任务状态发生变化时收到通知。
1 | WorkManager.getInstance(this).getWorkInfoByIdLiveData(uploadWorkReuqest.getId()).observe(MainActivity.this, new Observer<WorkInfo>(){ |
取消任务
与观察任务类似,我们也可以根据id
或tag
取消某个任务,或者取消所有的任务。
1 | WorkManager.getInstance(MainActivity.this).cancelAllWork(); |
WorkManager与Worker之间的参数传递
WorkManager
通过setInputData()
方法向Worker
传递数据。数据的传递通过Data
对象来完成。需要注意的是,Data
只能用于传递一些小的基本类型的数据,且数据最大不能超过10KB。
1 | Data inputData = new Data.Builder().putString("input_data", "Hello World!").build(); |
Worker
通过getInputData()
方法接收数据,并在任务完成后,向WorkManager
返回数据。
1 |
|
WorkManager
通过LiveData
得到从Worker
返回的数据。
1 | WorkManager.getInstance(this).getWorkInfoByIdLiveData(uploadWorkRequest.getId()).observe(MainActivity.this, new Observer<WorkInfo>(){ |
周期性任务PeriodicWorkRequest
前面提到过,WorkRequest
有两种实现方式:OneTimeWorkRequest
和PeriodicWorkRequest
,分别对应的是一次性任务和周期性任务。一次性任务在任务成功执行后,便彻底结束。而周期性任务则会按照设定的时间定期执行。二者使用起来没有太大差别。需要注意的是,周期性任务的间隔时间不能少于15分钟。
1 | PeriodicWorkRequest uploadWorkRequest = new PeriodWorkRequest |
任务链
如果有一系列的任务需要按顺序执行,那么可以利用WorkManager.beginWith().then().then()...enqueue()
的方式构建任务链。例如,在上传数据之前,可能需要先对数据进行压缩。
1 | WorkManager.getInstance(this) |
假设除了压缩数据,还需要更新本地数据。压缩数据与更新本地数据二者没有先后顺序的区别,但与上传数据存在先后顺序。
1 | WorkManager.getInstance(this) |
假设有更复杂的任务链,那么还可以考虑使用WorkContinuation.combine()
方法,将任务链组合起来使用。在下面的代码中,任务的执行顺序为A、B、C、D、E。任务链流程如图7-2所示。
1 | WorkContinuation workContinuation1 = WorkManager.getInstance(this) |