我们经常会有这样的需求,如后台下载开屏广告,既不让用户发觉,也不影响用户正常功能使用,任务完成后能够即时关闭任务回收资源,之前通常的做法就是使用IntentService或者JobScheduler等组件来实现,如果没有采用合理的方式,会导致占用应用程序的大量内存资源,影响用户体验。Google提供了一个新的组件——WorkManager,用来实现那些需要在后台执行的不需要即时完成的任务,以节约程序资源和保证正常用户体验。
WorkManager有以下三个特点:
- 用来实现不需要即时完成的任务,如后台下载开屏广告、上传日志信息等;
- 能够保证任务一定会被执行;
- 兼容性强。
WorkManager能够根据系统版本,选择不同的方案来实现,在API低于23时,采用AlarmManager+Broadcast Receiver,高于23时采用JobScheduler。但无论采用哪种方式,任务最终都是交由Executor来执行。
WorkManager使用了数据库来保存任务信息,所以就算应用程序退出或者重启,都能够保证任务的完成。
使用
添加依赖:
// WorkManager
implementation 'androidx.work:work-runtime:2.4.0'
创建Worker类,在Worker类中实现任务:
class SplashAdWorker(context: Context, workerParameter: WorkerParameters) :
Worker(context, workerParameter) {
override fun doWork(): Result {
return Result.success()
}
}
这里我们创建一个用来下载开屏广告的任务Worker,在doWork()方法中实现任务,最终需要返回一个Result类型对象,表示任务执行结果,一般共有三种返回值:
- Result.success():任务执行成功;
- Result.failure():任务执行失败;
- Result.retry():任务需要重新执行。
使用WorkRequest配置任务,并提交给系统
// 触发条件
var constraints = Constraints.Builder()
.setRequiresCharging(true) // 正在充电
.setRequiredNetworkType(NetworkType.CONNECTED) // 链接网络
.setRequiresBatteryNotLow(true) // 电量充足
.build()
// WorkRequest设置触发条件 初始化单次执行的WorkRequest
var workRequest = OneTimeWorkRequest.Builder(SplashAdWorker::class.java)
.setConstraints(constraints) // 触发条件
.setInitialDelay(1000L, TimeUnit.MILLISECONDS) // 延迟执行
.setBackoffCriteria(
BackoffPolicy.LINEAR,
OneTimeWorkRequest.MIN_BACKOFF_MILLIS,
TimeUnit.MILLISECONDS
) // 指数退避
.addTag("SplashAdWorkRequest") // 设置tag
.build()
// 提交任务
WorkManager.getInstance(this@SplashAdActivity ).enqueue(workRequest)
首先需要创建Constraiints类,用来设置任务的一些触发条件,如电量充足、链接网络等等,之后构建WorkRequest对象,将触发条件设置给WorkRequest,还可以设置延迟初始化时间,避让策略等等。最终调用WorkManager的enqueue()方法提交任务。
WorkRequest有两个子类:OneTimeWorkRequest和PeriodicWorkRequest,前者实现只执行一次的任务,后者用来实现周期性任务。
周期性任务
上文介绍了如果使用WorkManager实现单一执行的任务,可以构建一个OneTimeWorkRequest来实现,如果想要执行周期性任务,可以使用PeriodicWorkRequest来实现,但要注意周期性任务执行的时间间隔不得少于15分钟:
var periodicWorkRequest =
PeriodicWorkRequest.Builder(SplashAdWorker::class.java, 15, TimeUnit.MINUTES)
.setConstraints(constraints) // 触发条件
.setInitialDelay(1000L, TimeUnit.MILLISECONDS) // 延迟执行
.setBackoffCriteria(
BackoffPolicy.LINEAR,
OneTimeWorkRequest.MIN_BACKOFF_MILLIS,
TimeUnit.MILLISECONDS
) // 指数退避
.addTag("SplashAdWorkRequest") // 设置tag
.build()
观察和取消任务
将任务提交给WorkManager之后,如果想要监听任务的执行状态,可以根据Worknfo的id、tag等数据获取状态,之前介绍了LiveData,能够时时监测数据变化,这里也可以使用LiveData:
WorkManager.getInstance(this@SplashAdActivity).getWorkInfoByIdLiveData(workRequest.id)
.observe(
this@SplashAdActivity,
Observer<WorkInfo> { t -> Log.e("jia", "onChanged: " + t?.toString()) })
当WokrInfo发生变化时,就可以收到通知。
如果要取消任务的执行,类似地,也可以通过id或者tag来取消:
// 取消任务
WorkManager.getInstance(this@SplashAdActivity).cancelWorkById(workRequest.id)
WorkManager与Worker之间的数据传递
Workmanager可以通过setInputData()方法向Worker来传递数据,数据使用Data封装,但是Data中只能保存一些基本类型变量,而且大小不宜过大:
var data = Data.Builder()
.putInt("intKey", 1)
.putString("stringKey", "value")
.build()
// WorkRequest设置触发条件 初始化单次执行的WorkRequest
var workRequest = OneTimeWorkRequest.Builder(SplashAdWorker::class.java)
.setConstraints(constraints) // 触发条件
.setInitialDelay(1000L, TimeUnit.MILLISECONDS) // 延迟执行
.setBackoffCriteria(
BackoffPolicy.LINEAR,
OneTimeWorkRequest.MIN_BACKOFF_MILLIS,
TimeUnit.MILLISECONDS
) // 指数退避
.addTag("SplashAdWorkRequest") // 设置tag
.setInputData(data)
.build()
构建好Data数据之后,调用WorkRequest.Builder的setInputData()方法,就可以向Worker传递数据。在Worker中接收数据时,调用getInputData()方法即可获得数据:
override fun doWork(): Result {
Log.e("jia", "doWork: ")
var string = inputData.getString("stringKey")
var int = inputData.getString("intKey")
return Result.success()
}
在Worker中执行任务完成后,也是通过Data向WorkerManager传回数据,构造Data数据,最终将其设置给Result.success()方法或者是Result.failure()方法即可:
override fun doWork(): Result {
Log.e("jia", "doWork: ")
var string = inputData.getString("stringKey")
var int = inputData.getString("intKey")
var outputData = Data.Builder()
.putString("resultKey", "resultVaule")
.build()
return Result.success(outputData)
}
WorkerManager如何监测Worker传回的数据呢,上文已经介绍过了,通过tag或id获取到WorkerInfo的LiveData来监听:
WorkManager.getInstance(this@SplashAdActivity).getWorkInfoByIdLiveData(workRequest.id)
.observe(
this@SplashAdActivity,
Observer<WorkInfo> { t ->
if (t.state == WorkInfo.State.SUCCEEDED) {
Log.e("jia", "success: " + t.outputData.getString("resultKey"))
} else if (t.state == WorkInfo.State.FAILED) {
Log.e("jia", "failed: " + t.outputData.getString("resultKey"))
}
})