一. WorkManager介绍
WorkManager是Google推出的组件, 用于解决应用在退出或者设备重启后仍需要需要运行任务的问题.
如何管理后台工作
WorkManager内部会根据设备的API级别自动选择底层作业的调度服务. 下面上一张官方图, 图中清晰说明了WorkManager在各个版本的API时选择的调度服务.目前最低可支持API 14
和直接在应用中使用线程的区别
首先WorkManager的作用并不是取代线程在Android中的工作. Google在官方的文档专门为后台任务做出了定义
Google 将后台任务具体分为了四种Immediate Tasks
, Exact Task
, Expedited Task
, Deferred Task
Immediate Task
当任务需要在用户操作APP时就完成,则可归类为Imeediate Task. 推荐在APP中使用Kotlin协程或Java的线程来执行任务
Exact Task
当任务需要在精确的时间运行时,则可归类为Exact Task. 推荐使用AlarmManager
Expedited Task & Deferred Task
除以上情景之外, 如果任务需要尽可能快开始时,则可归类为Expedited Task, 如果不需要则归类为Deferred Task. 推荐使用WorkManager
从WorkManager 2.7.0版本开始可以使用setExpedited()
来申明Worker为加急任务. 对应上面的Expedited Task. 需要同时重写Worker中的getForegroundInfoAsync
方法
OneTimeWorkRequestBuilder<T>().apply {
setInputData(inputData)
setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST)
}.build()
复制代码
二. WorkManager使用
Worker & CoroutineWorker
Worker类作用为定义任务所执行的工作. 继承Worker类在doWork()
方法中编写所需要执行的任务(如果想要使用Kotlin协程可以使用CoroutineWorker
doWork()
方法返回值通知WorkManager任务执行的结果
Result.success()
任务执行成功Result.failure()
任务执行失败Result.retry()
任务需重新执行
@WorkerThread
public abstract @NonNull Result doWork();
复制代码
WorkRequests
WorkRequest类作用为定义工作Worker
的运行方式(例如: Worker
运行所需要满足的约束条件, 为Worker
传递数据, Worker
调度信息配置等). WorkManager提供了两种WorkRequest的实现
- OneTimeWorkRequest(一次性工作)
- PeriodicWorkRequest(定期工作)
// 传递给Worker的参数
val data = Data.Builder().putString(DownloadWorker.KEY_NAME, downloadContent).build()
// Worker执行的约束条件
val constraints = Constraints.Builder().setRequiredNetworkType(NetworkType.CONNECTED).build()
// 创建WorkRequest
OneTimeWorkRequestBuilder<DownloadWorker>()
.addTag(DownloadWorker.TAG)
.setInputData(data)
.setConstraints(constraints)
.build()
复制代码
WorkManager
WorkManager作用为管理Work. 例如加入任务,取消任务,以及监听任务的执行
加入任务
mWorkManager.beginUniqueWork(
DownloadWorker.TAG,
ExistingWorkPolicy.REPLACE,
OneTimeWorkRequestBuilder<DownloadWorker>()
.addTag(DownloadWorker.TAG)
.setInputData(data)
.setConstraints(constraints)
//此设置需要在Worker中重写getForegroundInfo
.setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST)
.build()
).enqueue()
复制代码
监听Worker的执行
mWorkManager.getWorkInfosByTagLiveData(DownloadWorker.TAG)
.observe(this) {
if (it.isNotEmpty()) {
val workInfo = it[0]
when(workInfo.state) {
WorkInfo.State.SUCCEEDED,
WorkInfo.State.BLOCKED,
WorkInfo.State.ENQUEUED,
WorkInfo.State.RUNNING,
WorkInfo.State.CANCELLED
}
}
}
复制代码
获取到的任务结果为List<WorkInfo>
, List[0]代表当前最新
三. 实践
创建Worker模拟任务执行
class DownloadWorker : CoroutineWorker {
private lateinit var mNotificationBuilder: NotificationCompat.Builder
constructor(appContext: Context, params: WorkerParameters) : super(appContext, params)
//Worker执行任务
override suspend fun doWork(): Result {
val data = fakeDownload()
showSuccessNotification()
val outData = Data.Builder().putString(OUTPUT_KEY, data).build()
return Result.success(outData)
}
//创建ForegroundInfo Worker将会作为前台服务运行
override suspend fun getForegroundInfo(): ForegroundInfo {
val context = applicationContext
return ForegroundInfo(
START_DOWNLOAD_NOTIFICATION_ID,
createNotification(context) {
setContentTitle("Start Download")
setSmallIcon(R.drawable.ic_launcher_foreground)
setContentText("Start Download ${inputData.getString(INPUT_KEY)}")
priority = NotificationCompat.PRIORITY_DEFAULT
val cancel = WorkManager.getInstance(context).createCancelPendingIntent(id)
//设置cancelWork按钮
addAction(R.drawable.icon_cancel, "Cancel", cancel)
mNotificationBuilder = this
}
)
}
private suspend fun fakeDownload(): String {
Log.i(TAG, "Thread:${Thread.currentThread().name}")
for (i in 0..100) {
delay(100L)
mNotificationBuilder.setContentText("Start Download ${inputData.getString(INPUT_KEY)} $i%")
notifyNotification(applicationContext, START_DOWNLOAD_NOTIFICATION_ID, mNotificationBuilder.build())
}
Log.i(TAG, "Download Succeed")
return "Download Succeed"
}
private fun showSuccessNotification() {
notifyNotification(applicationContext, DOWNLOAD_SUCCEED_NOTIFICATION_ID) {
setContentTitle("Download Succeed")
setSmallIcon(R.drawable.ic_launcher_foreground)
setContentText("Download ${inputData.getString(INPUT_KEY)} Succeed")
priority = NotificationCompat.PRIORITY_DEFAULT
setAutoCancel(true)
val intent = Intent(applicationContext, MainActivity::class.java)
val pendingIntent = PendingIntent.getActivity(applicationContext, 0, intent, PendingIntent.FLAG_IMMUTABLE)
setContentIntent(pendingIntent)
}
}
}
复制代码
创建WorkRequest
创建WorkRequest 设置执行条件,参数传递, 并加入任务队列
private fun enqueueDownloadWork() {
val downloadContent = "复仇者联盟"
val constraints = Constraints.Builder().setRequiredNetworkType(NetworkType.CONNECTED).build()
val data = Data.Builder().putString(DownloadWorker.INPUT_KEY, downloadContent).build()
mWorkManager.beginUniqueWork(
DownloadWorker.TAG,
ExistingWorkPolicy.REPLACE,
OneTimeWorkRequestBuilder<DownloadWorker>()
.addTag(DownloadWorker.TAG)
.setInputData(data)
.setConstraints(constraints)
.setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST)//
.build()
).enqueue()
}
复制代码
监听任务执行情况
通过先前加入任务时设置的TAG监听Worker的执行情况
mWorkManager.getWorkInfosByTagLiveData(DownloadWorker.TAG)
.observe(this) {
if (it.isNotEmpty()) {
val workInfo = it[0]
when(workInfo.state) {
WorkInfo.State.SUCCEEDED -> {
mBinding.download.setText("重新下载")
mBinding.tips.visibility = View.VISIBLE
mBinding.tips.text = workInfo.outputData.getString(DownloadWorker.OUTPUT_KEY)
mBinding.download.setOnClickListener {
enqueueDownloadWork()
}
}
WorkInfo.State.BLOCKED,
WorkInfo.State.ENQUEUED,
WorkInfo.State.RUNNING, -> {
mBinding.tips.text = "正在下载"
mBinding.download.text = "取消下载"
mBinding.download.setOnClickListener {
mWorkManager.cancelUniqueWork(DownloadWorker.TAG)
}
}
WorkInfo.State.CANCELLED -> {
mBinding.tips.visibility = View.GONE
mBinding.download.text = "开始下载"
mBinding.download.setOnClickListener {
enqueueDownloadWork()
}
}
}
}
}
复制代码
完整项目地址
GitHub - Mao0509/WorkManagerDemo 谢谢大家观看, 如有错误还请帮忙指出.