Jetpack系列——WorkManager

1,358 阅读4分钟

我们经常会有这样的需求,如后台下载开屏广告,既不让用户发觉,也不影响用户正常功能使用,任务完成后能够即时关闭任务回收资源,之前通常的做法就是使用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"))
                    }
                })