Introduction to WorkManager

WorkManager is a Google component designed to solve the problem of running tasks even after an application exits or the device restarts.

How to manage background work

WorkManager automatically selects the scheduling service for underlying jobs based on the API level of the device. The previous official figure below clearly illustrates the scheduling service selected by WorkManager for each version of the API. Currently, API 14 is supported at least

And using threads directly in your application

First of all, WorkManager is not meant to replace the work of threads in Android. Google defines background tasks in its official documentation

Google divides background Tasks into four types: Immediate Task, Exact Task, Expedited Task, and Deferred Task

Immediate Task

Tasks that need to be completed while the user is operating the APP can be classified as Imeediate tasks. It is recommended that you use Kotlin coroutines or Java threads to perform tasks in your APP

Exact Task

Tasks that need to run at Exact times can be classified as Exact tasks. AlarmManager is recommended

Expedited Task & Deferred Task

In addition to the above scenarios, a Task can be categorized as an Expedited Task if it needs to start as soon as possible, or as a Deferred Task if it does not. WorkManager is recommended

Starting with WorkManager 2.7.0, you can use setExpedited() to declare workers as urgent tasks. Corresponding to the Expedited Task above, you need to override the getForegroundInfoAsync method in the Worker as well

OneTimeWorkRequestBuilder<T>().apply {
    setInputData(inputData)
    setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST)
}.build()
Copy the code

Use of WorkManager

Worker & CoroutineWorker

The Worker class is used to define the work performed by the task. Inheriting the Worker class writes tasks that need to be performed in the doWork() method (CoroutineWorker can be used if you want to use a Kotlin coroutine

The return value of the doWork() method notifies WorkManager of the result of the task execution

  • Result.success()Task Executed Successfully
  • Result.failure()Task execution failure
  • Result.retry()The task needs to be executed again
@WorkerThread
public abstract @NonNull Result doWork(a);
Copy the code

WorkRequests

The function of WorkRequest class is to define the running mode of Worker (for example, constraints that Worker needs to meet for running, data transfer for Worker, Worker scheduling information configuration, etc.). WorkManager provides two implementations of WorkRequest

  • OneTimeWorkRequest(One-time work)
  • PeriodicWorkRequest(Periodic work)
// The argument passed to the Worker
val data = Data.Builder().putString(DownloadWorker.KEY_NAME, downloadContent).build()
// Constraints on Worker execution
val constraints = Constraints.Builder().setRequiredNetworkType(NetworkType.CONNECTED).build()
/ / create WorkRequest
OneTimeWorkRequestBuilder<DownloadWorker>()
    .addTag(DownloadWorker.TAG)
    .setInputData(data)
    .setConstraints(constraints)
    .build()
Copy the code

WorkManager

WorkManager is used to manage Work, such as adding tasks, canceling tasks, and listening to the execution of tasks

Join the task

mWorkManager.beginUniqueWork(
    DownloadWorker.TAG,
    ExistingWorkPolicy.REPLACE,
    OneTimeWorkRequestBuilder<DownloadWorker>()
        .addTag(DownloadWorker.TAG)
        .setInputData(data)
        .setConstraints(constraints)
        // This setting requires overwriting getForegroundInfo in the Worker
        .setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST)
        .build()
).enqueue()
Copy the code

Monitor Worker execution

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
            }
        }
    }
Copy the code

The obtained task result is List

, where List[0] indicates the latest

Practice 3.

Create Worker to simulate task execution

class DownloadWorker : CoroutineWorker {

    private lateinit var mNotificationBuilder: NotificationCompat.Builder

    constructor(appContext: Context, params: WorkerParameters) : super(appContext, params)

    //Worker executes the task
    override suspend fun doWork(a): Result {
        val data = fakeDownload()
        showSuccessNotification()
        val outData = Data.Builder().putString(OUTPUT_KEY, data).build()
        return Result.success(outData)
    }

    // Create ForegroundInfo Worker which will run as foreground service
    override suspend fun getForegroundInfo(a): 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)
                // Set the cancelWork button
                addAction(R.drawable.icon_cancel, "Cancel", cancel)
                mNotificationBuilder = this})}private suspend fun fakeDownload(a): 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(a) {
        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)
        }
    }

}
Copy the code

Create WorkRequest

Create WorkRequest set execution conditions, pass parameters, and queue the WorkRequest

private fun enqueueDownloadWork(a) {
    val downloadContent = The Avengers
    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()
}
Copy the code

Monitor the execution of tasks

Monitor the Worker’s execution through the TAG set when joining the task

mWorkManager.getWorkInfosByTagLiveData(DownloadWorker.TAG)
    .observe(this) {
        if (it.isNotEmpty()) {
            val workInfo = it[0]
            when(workInfo.state) {
                WorkInfo.State.SUCCEEDED -> {
                    mBinding.download.setText("Download again")
                    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 = "Downloading"
                    mBinding.download.text = "Cancel download"mBinding.download.setOnClickListener { mWorkManager.cancelUniqueWork(DownloadWorker.TAG) } } WorkInfo.State.CANCELLED ->  { mBinding.tips.visibility = View.GONE mBinding.download.text ="Start downloading"
                    mBinding.download.setOnClickListener {
                        enqueueDownloadWork()
                    }
                }
            }
        }
    }
Copy the code

Full Project Address

GitHub – Mao0509/WorkManagerDemo

4. Reference

Define work requests|Android Developers