Android WorkManager入门与实践

一. WorkManager介绍

WorkManager是Google推出的组件, 用于解决应用在退出或者设备重启后仍需要需要运行任务的问题.

如何管理后台工作

WorkManager内部会根据设备的API级别自动选择底层作业的调度服务. 下面上一张官方图, 图中清晰说明了WorkManager在各个版本的API时选择的调度服务.目前最低可支持API 14

image.png

和直接在应用中使用线程的区别

首先WorkManager的作用并不是取代线程在Android中的工作. Google在官方的文档专门为后台任务做出了定义

Google 将后台任务具体分为了四种Immediate Tasks, Exact Task, Expedited Task, Deferred Task

task-category-tree.svg

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]代表当前最新

三. 实践

device-2021-11-21-143555.gif

创建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 谢谢大家观看, 如有错误还请帮忙指出.

四. 参考

Define work requests|Android Developers

猜你喜欢

转载自juejin.im/post/7032920470328442910