Jetpack WorkManager It is enough to read this article~

foreword

Recently, some readers reported that WorkManager was not mentioned in my new book "Android Jetpack Development: Principle Analysis and Application Practice" , because this thing is not very useful in China at present. Recently, I just studied it because of work needs, and shared it with readers as a supplementary chapter.

What is WorkManager

According to the official description, WorkManager is the recommended solution for persistent work. Work is persistent if it is always scheduled across application restarts and system restarts. Since most background processing operations are done with persistent work, WorkManager is the primary recommended API for background processing operations.

task type

The types of WorkManager tasks are divided into immediate running, long-term running, and deferred execution. The relationship between usage and cycle is as follows:

immediately Disposable OneTimeWorkRequest and Worker. To process expedited work, call setExpedited() on OneTimeWorkRequest.
long run one-time or regular Any WorkRequest or Worker. Call setForeground() in the worker to handle the notification.
can be extended one-time or regular PeriodicWorkRequest 和 Worker。

Next, let's look at the specific usage method.

Getting Started

Add dependent library

The code in this article is written in Kotlin, so only Kotlin-related libraries can be introduced here. Add the code in build.gradle as follows:

def work_version = "2.7.1"
implementation "androidx.work:work-runtime-ktx:$work_version"

How to quote if you are using the Java language? Listen to me, give up~

Define work Worker

Here we take the task of uploading log files as an example, create a new UploadLogWorker class, which inherits from Worker, and the code is as follows:

class UploadLogWorker(context: Context, workerParams: WorkerParameters) :
    Worker(context, workerParams) {

    override fun doWork(): Result {
        Log.d("打印线程", Thread.currentThread().name)
        return Result.success()
    }
}

Classes inherited from Worker need to rewrite the doWork method. We can perform specific tasks in this method. Here, the name of the thread is printed for demonstration results.

Result is used to return the execution result of the task. Result.success indicates that the execution is successful; Result.failure and Result.retry indicate the execution failure and retry after failure respectively.

Create a task request WorkRequest

Here we create a one-time execution task, the code is as follows:

val uploadLogWorkerRequset: WorkRequest = OneTimeWorkRequestBuilder<UploadLogWorker>()
            .build()

Submit tasks to the system

After creating the task, you can submit the task to the system and execute the request. The code is as follows:

WorkManager.getInstance(this).enqueue(uploadLogWorkerRequset)

Run the App, and the result is shown in the figure below.

Pass parameters to the task

Many times we need parameters when performing tasks. For example, to upload a log file, we need to know the path of the log file or other parameters. How do we pass the parameters to the Worker?

We can set parameters through the setInputData method of WorkRequest, the code is as follows:

val uploadLogWorkerRequset: WorkRequest = OneTimeWorkRequestBuilder<UploadLogWorker>()
            .setInputData(workDataOf("filePath" to "file://***", "fileName" to "log.txt"))
            .build()

Here we pass the file path filePath and the file name fileName, which are accepted by the Worker through the getInputData method. For example, we accept parameters and print them in doWork. The code looks like this:

override suspend fun doWork(): Result {
        val filePath = inputData.getString("filePath")
        val fileName = inputData.getString("fileName")
        Log.d("接受的参数", "$fileName:$filePath")
        return Result.retry()
    }

Run the program and print as shown in the figure below.

This completes one of the simplest WorkManager use cases. Then we come to further exploration.

What You Need to Know to Perform Expedited Jobs

Starting from WorkManager 2.7, we can call the setExpedited method to tell the system that my task is an urgent task, please execute it as soon as possible. Modify the code as follows:

val uploadLogWorkerRequset: WorkRequest = OneTimeWorkRequestBuilder<UploadLogWorker>()
    .setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST)
    .build()

The OutOfQuotaPolicy parameter in the setExpedited method has two enumeration values, and their meanings are as follows.

enumeration value

meaning

RUN_AS_NON_EXPEDITED_WORK_REQUEST

When the system cannot expedite the task, the task becomes a regular task

DROP_WORK_REQUEST

When the system cannot expedite the task, delete the task

So we can declare it as RUN_AS_NON_EXPEDITED_WORK_REQUEST here . Run the program again.

OK, works perfectly? ? ?

But my phone is Android 12, in order to make sure there is no problem, we have to execute it once on Android 11 or lower version. It didn't crash, but the task was not executed, and we saw the error log as shown in the figure below.

Emm.. a bunch of mess, the key information is in this sentence

Expedited WorkRequests require a ListenableWorker to provide an implementation for `getForegroundInfoAsync()`

We got this information from the official: Before Android 12, getForegroundInfoAsync()the and getForegroundInfo()methods in the worker allow WorkManager to display notifications setExpedited()when . All ListenableWorkers must implement getForegroundInfothe method .

Failure to implement the corresponding method may result in a runtime crash when . getForegroundInfosetExpedited

Knowing this, let's implement getForegroundInfo()the method and modify the UploadLogWorker code as follows:

class UploadLogWorker(context: Context, workerParams: WorkerParameters) :
    Worker(context, workerParams) {

    override fun doWork(): Result {
        Log.d("打印线程", Thread.currentThread().name)
        setForegroundAsync(getForegroundInfo())
        return Result.success()
    }

    @SuppressLint("RestrictedApi")
    override fun getForegroundInfoAsync(): ListenableFuture<ForegroundInfo> {
        val future = SettableFuture.create<ForegroundInfo>()
        future.set(getForegroundInfo())
        return future
    }


    fun getForegroundInfo(): ForegroundInfo {
        val notificationManager =
            applicationContext.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            val channel = NotificationChannel(
                "1",
                "hh",
                NotificationManager.IMPORTANCE_HIGH
            )
            notificationManager.createNotificationChannel(channel)
        }

        val notification = NotificationCompat.Builder(applicationContext, "1")
            .setSmallIcon(R.drawable.ic_launcher_background)
            .setContentTitle(applicationContext.getString(R.string.app_name))
            .setContentText("我是一个上传日志的任务")
            .build()
        return ForegroundInfo(1337, notification)
    }


}

Run the program on Android11 ​​again, and find that the log is printed out and a task notification is displayed, as shown in the figure below.

This point must be paid attention to when performing urgent work.

CoroutineWorker

1. Modify the inherited class to CoroutineWorker

2. Implement the getForegroundInfo method, the content is consistent with the above getForegroundInfo

Timed task PeriodicWorkRequest

In 3.2, we defined the one-time task OneTimeWorkRequestBuilder. Now we modify the task of uploading logs to a scheduled task. The code is as follows:

val uploadLogWorkerRequset: WorkRequest = PeriodicWorkRequestBuilder<UploadLogWorker>(15,TimeUnit.MINUTES)
            .build()

It is specified here that the period of the timed task is 15 minutes, and the shortest repeat interval that can be defined is 15 minutes. Developers need to pay attention to this when testing, and they cannot wait stupidly..., here I will be stupid Waited 15 minutes to make sure that the scheduled task is executable.

Work Constraints, Delayed Execution, and Retry Policies

work constraints

In many cases, we need to add work constraints to tasks. For example, the task of uploading logs must be performed under the condition of a network. The currently supported constraints are as follows.

NetworkType Constrains the type of network required to run the job. For example Wi-Fi (UNMETERED).
BatteryNotLow If set to true, jobs will not run when the device is in "low battery mode".
RequiresCharging If set to true, then jobs can only run while the device is charging.
DeviceIdle If set to true, requires the user's device to be idle in order to run the job. This constraint is useful when running batch operations; without this constraint, batch operations can degrade the performance of other applications that are actively running on the user's device.
StorageNotLow If set to true, the job will not run when there is insufficient storage space on the user's device.

For example, we now add constraints for one-time tasks to be executed when connected to wifi. First, construct a constraint instance with Constraints to put multiple constraints together. The code looks like this:

val constraints = Constraints.Builder()
            .setRequiresCharging(true)
            .build()

Here it is set to execute only when charging. Next add constraints to the task builder.

val uploadLogWorkerRequset: WorkRequest = OneTimeWorkRequestBuilder<UploadLogWorker>()
    .setConstraints(constraints)
    .build()

In this way, the task will be performed while only charging.

delayed execution

Delayed execution is applicable to one-time tasks and scheduled tasks, but the application is effective for the first execution when the scheduled task is in progress. Why? Because it's a scheduled task

We set the delay time for one-time tasks to 5 seconds, the code is as follows:

val uploadLogWorkerRequset: WorkRequest = OneTimeWorkRequestBuilder<UploadLogWorker>()
            .setConstraints(constraints)
            .setInitialDelay(5,TimeUnit.SECONDS)
            .build()

Run the program, and you can see that the program prints the log after 5 seconds, so I won’t demonstrate it here.

retry policy

In defining Work in 3.2, we mentioned that Result.retry can allow tasks to be retried, and we can also customize the retry strategy and backoff policy of tasks, which we explain through specific examples.

val uploadLogWorkerRequset: WorkRequest = OneTimeWorkRequestBuilder<UploadLogWorker>()
            .setBackoffCriteria(
                BackoffPolicy.EXPONENTIAL, OneTimeWorkRequest.MIN_BACKOFF_MILLIS,
                TimeUnit.MILLISECONDS
            )
            .build()

The minimum backoff delay time is set to the minimum value allowed, which is 10 seconds. Since the policy is LINEAR, the retry interval increases by about 10 seconds each time a retry is attempted. For example, the first run ends with Result.retry() and retries after 10 seconds; then, if the job continues to return Result.retry() after subsequent attempts, then the next 20 seconds, 30 seconds, 40 seconds Then try again, and so on.

The print log is as shown in the figure below.

We can see that the re-execution was delayed for 10 seconds after the first task failed, the second time was delayed by 20 seconds, and the third time was delayed by 40 seconds...

Observation of work execution results

After the task is completed, I may need to update the UI or business logic operations. We can observe the changes of WorkInfo by registering a listener. Taking the WorkInfo status query based on the ID as an example, the code is as follows:

WorkManager.getInstance(this).getWorkInfoByIdLiveData(uploadLogWorkerRequset.id).observe(this){
            if (it.state == WorkInfo.State.SUCCEEDED){
                Toast.makeText(this,"任务执行成功,更新UI",Toast.LENGTH_LONG).show()
            }else{
                //任务失败或重试
            }
        }

In addition to getWorkInfoByIdLiveData, there are also conversion methods based on tags, names, etc., here readers can view the API by themselves.

Run the program, the result is shown in the figure below.

Similarly, we can also cancel the execution of tasks through methods such as cancelWorkById. No demo here. In addition, there are some other features that interested readers can practice on their own.

Summarize

Features and Precautions

  • In API versions earlier than Android 12, expedited jobs were performed by foreground services, and starting with Android 12, they will be implemented by expedited jobs. So in section 4, the notification bar will not be displayed on Android 12 by default

  • WorkManager is just a tool for processing scheduled tasks

  • WorkManager is compatible to API 14 (Android 4.0) at the earliest

  • Periodic tasks registered with WorkManager cannot be guaranteed to be executed on time. This is not a bug, but the system may execute several tasks whose trigger time is close to each other in order to reduce power consumption, which can greatly reduce CPU usage. The number of wake-ups, thus effectively extending the battery life

  • Although WorkManager officially claims that it can guarantee that even if the app is exited or the phone is restarted, the previously registered tasks will still be executed. But it is impossible in domestic mobile phones, because the system has made changes by itself. However, after the domestic on-board test (OPPO) exits, it will also perform the previous tasks when it comes in again. At this time, there may be repeated task execution.

question

  • After the task is added to the queue, before the execution starts, it is not feasible to call the method of canceling the task in onDestory. In this case, there will still be repeated tasks to start executing next time. (Native system and domestic machine are the same), this is because the cancellation method is executed asynchronously, and it is necessary to pay attention to the occurrence of this situation in the business

I encountered such a problem here, and when this article was published, I didn't know how to solve it. I hope that the big guys who know can give me advice~

If you want to learn more about Jetpack, welcome to buy my new book "Android Jetpack Development: Principle Analysis and Application Combat"

Guess you like

Origin blog.csdn.net/huangliniqng/article/details/126061658
Recommended