Copyright notice: This article is the blogger’s original article, welcome to reprint!

But the reprint please indicate the source: blog.csdn.net/guiying712/… This article is from: [Zhang Huayang’s blog]


  • Schedule tasks using WorkManager
    • Relying on the WorkManager
    • Classes and concepts
    • Typical workflow
      • Task constraint
      • Cancel the task
    • Advanced features
      • Repetitive tasks
      • Chain task
      • Unique working sequence
      • Mark the Work
      • Input parameters and return values

Schedule tasks using WorkManager

First let’s look at the definition of WorkManager. The WorkManager API makes it easy to delay asynchronous tasks and when to run them. These apis allow us to create and deliver tasks to WorkManager to run immediately or at an appropriate time. For example, if an application may need to download new resources from the network from time to time, we can use the WorkManager API to set up a task, then select an environment suitable for it to run (for example, “only when the device is charging and networking”), and hand it over to WorkManager to run when the conditions are met. This task is guaranteed to run even if the application is forced to exit or the device is restarted.

Note: WorkManager is suitable for scenarios where the system can keep the task running even if the application exits, such as uploading application data to the server. It is not suitable for background work within an application process and can be safely terminated if the application process disappears, in which case you are recommended to use thread pools.

WorkManager selects the appropriate way to run our tasks based on factors such as the API level of the mobile device and the state of the application. If WorkManager executes one of the tasks while the application is running, WorkManager can start a new thread in the application process to run the task. If your application is not running, WorkManager will choose the appropriate way to schedule background tasks – depending on the device API level and the dependencies the application contains, WorkManager may use JobScheduler, Firebase JobDispatcher or AlarmManager. Rather than writing device logic to determine what the device has and choose the appropriate API, we can hand the task over to the WorkManager and let it choose the best option.

In addition, WorkManager provides several advanced features. For example, we can create a series of tasks, and when one task is completed, WorkManager processes the next task in sequence. We can also check the status of a task and its return value by observing the task’s LiveData, which is useful if you want to show the UI to indicate the progress of the task.

Relying on the WorkManager

The WorkManager class is already in the AndroidX. work package, but currently relies on Support Library 27.1 and the associated Arch component version, and a version of WorkManager with AndroidX dependencies will be released in the future.

Dependencies {def work_version = "implementation" dependencies {def work_version = "implementation" dependencies {def work_version = "implementation -ktx for Kotlin // optional - Firebase JobDispatcher support implementation "android.arch.work:work-firebase:$work_version" // optional - Test helpers androidTestImplementation "android.arch.work:work-testing:$work_version" }Copy the code

Classes and concepts


The WorkManager API uses several different classes, and in some cases we need to inherit one of the API classes.

Let’s look at some of the most important classes:

Typical workflow


Let’s say we’re developing a photo library application and the application needs to periodically compress its stored images. We want to use the WorkManager API to schedule an image compression task. In this case, we don’t need to care when the compression happens, we just need to set the task and forget about it.

First, we need to define our Worker class, and then override the doWork() method of this class. We need to specify how the Worker class performs this operation, but there should be no information about when the task will run.

public class CompressWorker extends Worker {
    @Override
    public Worker.WorkerResult doWork() {

        // Do the work here--in this case, compress the stored images.
        // In this example no parameters are passed; the task is
        // assumed to be "compress the whole library."
        myCompress();

        // Indicate success or failure with your return value:
        return WorkerResult.SUCCESS;

        // (Returning RETRY tells WorkManager to try this task again
        // later; FAILURE says not to try again.)
    }
}Copy the code

Next, we create a OneTimeWorkRequest object based on the Worker and use the WorkManager to enqueue the task:

OneTimeWorkRequest compressionWork = new OneTimeWorkRequest.Builder(CompressWorker.class).build();
WorkManager.getInstance().enqueue(compressionWork);Copy the code

The WorkManager chooses the appropriate time to run the task, balancing considerations such as system load, whether devices are plugged in, and so on. In most cases, if we don’t specify any constraints, WorkManager will run our task immediately. If we need to check the status of the task, we can get the WorkStatus object by getting a handle to the appropriate LiveData

. For example, if we wanted to check that the task was complete, we could use the following code:

WorkManager.getInstance().getStatusById(compressionWork.getId())
    .observe(lifecycleOwner, workStatus -> {
        // Do something with the status
        if (workStatus != null && workStatus.getState().isFinished())
        { ... }
    });Copy the code

Task constraint

If we want, we can also limit how long tasks run. For example, we might want to specify that the task only runs when the device is idle and powered up. In this case, we need to create a OneTimeWorkRequest. Builder object, and use the constructor to create the actual OneTimeWorkRequest:

// Create a Constraints that defines when the task should run Constraints myConstraints = new Constraints.Builder() .setRequiresDeviceIdle(true) .setRequiresCharging(true) // Many other constraints are available, see the // Constraints.Builder reference .build(); / /... then create a OneTimeWorkRequest that uses those constraints OneTimeWorkRequest compressionWork = new OneTimeWorkRequest.Builder(CompressWorker.class) .setConstraints(myConstraints) .build();Copy the code

We then pass the new OneTimeWorkRequest object to workManager.enqueue () as in the previous code, and the WorkManager takes our constraints into account when it looks up the time to run the task.

Cancel the task

Once we have the task listed, we can also cancel the task. To cancel a task, we need the Work ID of the task, which can be obtained from the WorkRequest object. For example, the following code cancels the compressionWork request from the previous section:

UUID compressionWorkId = compressionWork.getId();
WorkManager.getInstance().cancelByWorkId(compressionWorkId);Copy the code

WorkManager will do its best to cancel the task, but essentially this is indeterminate – the task may already be running or completed when we try to cancel it. WorkManager also provides methods to cancel all tasks in a unique work sequence (a concept covered below), or to do your best to cancel all tasks with specified tags.

Advanced features


In addition to the core functionality of the WorkManager API, which enables developers to create simple, take-it-and-forget tasks, the API also provides advanced capabilities that allow us to set more precise requests.

Repetitive tasks

We may need to perform a task repeatedly; for example, a photo management application does not want to compress its photos only once. More likely, it will want to check the photos every once in a while and see if there are any new or changed images that need to be compressed, and either this loop task can compress the images it finds, or it can launch a new “compress images” task.

To create a task, to use PeriodicWorkRequest. Builder class to create a PeriodicWorkRequest object, then PeriodicWorkRequest with OneTimeWorkRequest object in the same way as shown. For example, if we defined a PhotoCheckWorker class to identify images that need to be compressed, we could create a PeriodicWorkRequest object like this if we wanted to run the task every 12 hours:

new PeriodicWorkRequest.Builder photoWorkBuilder = new PeriodicWorkRequest.Builder(PhotoCheckWorker.class, 12, TimeUnit.HOURS); / /... if you want, you can apply constraints to the builder here... // Create the actual work object: PeriodicWorkRequest photoWork = photoWorkBuilder.build(); // Then enqueue the recurring task: WorkManager.getInstance().enqueue(invWork);Copy the code

The WorkManager tries to run the tasks at the intervals we request, subject to the limitations we impose and other requirements.

Chain task

Sometimes we want an application to run multiple tasks in a particular order. WorkManager allows us to create and queue work sequences of multiple tasks and in what order they should be run.

For example, if our application has three OneTimeWorkRequest objects: workA, workB, and workC, the tasks must be executed in that order. To queue them, create a sequence using the workManager.BeginWith () method and pass the first OneTimeWorkRequest object, which returns a WorkContinuation object that defines a series of tasks. Then add the remaining OneTimeWorkRequest objects using workcontinuation.then (), and finally sort the entire sequence using workcontinuation.enqueue () :

WorkManager.getInstance()
    .beginWith(workA)
        // Note: WorkManager.beginWith() returns a
        // WorkContinuation object; the following calls are
        // to WorkContinuation methods
    .then(workB)    // FYI, then() returns a new WorkContinuation instance
    .then(workC)
    .enqueue();Copy the code

WorkManager according to specify each task constraints to run the task request order, if any task to return to the Worker WorkerResult. FAILURE, the end of the sequence.

We can also pass multiple OneTimeWorkRequest objects to either beginWith() or.then() calls. If we pass multiple OneTimeWorkRequest objects to a single method call, the WorkManager will run all of these tasks (in parallel) before running the rest of the tasks in the sequence. Such as:

WorkManager.getInstance() // First, run all the A tasks (in parallel): .beginWith(workA1, workA2, workA3) // ... when all A tasks are finished, run the single B task: .then(workB) // ... then run the C tasks (in any order): .then(workC1, workC2) .enqueue();Copy the code

We can create more complex sequences by concatenating multiple chains using the workcontinuation.bine () method. For example, suppose we want to run a sequence like the following:

Figure 1. Using a WorkContinuation to set up complex chained tasks.

To establish this sequence, create two separate chains and then join them together to form a third chain:

WorkContinuation chain1 = WorkManager.getInstance()
    .beginWith(workA)
    .then(workB);
WorkContinuation chain2 = WorkManager.getInstance()
    .beginWith(workC)
    .then(workD);
WorkContinuation chain3 = WorkContinuation
    .combine(chain1, chain2)
    .then(workE);
chain3.enqueue();Copy the code

In this case, the WorkManager runs workA before workB, which also runs workC before workD, and after both workB and workD are complete, the WorkManager runs workE.

Note: While WorkManager runs each subchain in turn, there is no guarantee that tasks in chain 1 overlap with tasks in chain 2; for example, workB may run before or after workC, or they may run at the same time. The only guarantee is that the tasks in each subchain will run sequentially, that is, workB will start after workA completes.

There are many variations on the WorkContinuation method that can provide a brief explanation for a particular situation. For example, there is a WorkContinuation.combine(OneTimeWorkRequest, WorkContinuation…) Method that instructs the WorkManager to complete all the specified WorkContinuation chains and then the specified OneTimeWorkRequest.

Unique working sequence

To create a unique work sequence, simply call beginUniqueWork() instead of beginWith(). To start the sequence. Each unique work sequence has a name, and WorkManager allows only one work sequence at a time to use that name. When we create a new unique work sequence, specify what the WorkManager should do if there is already an unfinished work sequence with the same name:

  • Cancel an existing sequence and replace it with a new one
  • Keep the current order and ignore new requests
  • Append a new sequence to an existing sequence and run the first task of the new sequence after the last task of the existing sequence completes

A unique work sequence can be useful if we have a task that should not be assigned more than once. For example, if our application needs to synchronize its data to the network, we might arrange a sequence named “sync” and specify that if a sequence with that name already exists, our new task should be ignored. A unique sequence of work can also be useful if we need to build up a long-term chain of tasks over time. For example, a photo editing application might ask the user to undo a long list of actions. Each undo action may take a while, but must be performed in the right order. An application can create an undo chain and append each undo action to the chain as needed.

Mark the Work

We can group our tasks logically by assigning string labels to any WorkRequest object. To set up the label, please call WorkRequest. Builder. AddTag (), such as:

OneTimeWorkRequest cacheCleanupTask =
        new OneTimeWorkRequest.Builder(MyCacheCleanupWorker.class)
    .setConstraints(myConstraints)
    .addTag("cleanup")
    .build();Copy the code

The WorkManager class provides several utility methods that let us operate on all tasks using specific tags. . For example, the WorkManager cancelAllWorkByTag () can be cancelled with a specific tag all the tasks, and the WorkManager. GetStatusesByTag () returns with the tag all the WorkStatus lists all the tasks.

Input parameters and return values

For more flexibility, we can also pass parameters to our task and have the task return results. Pass and return values are key/value pair, to pass parameters to the task, before creating WorkRequest objects called WorkRequest. Builder. SetInputData () method, this method use the Data. The Builder to create Data objects. The Worker class can access these parameters by calling worker.getinputData (). To output a return value, the task can call worker.setOutputData () with a Data object. We can get the output by observing the task’s LiveData

.

For example, suppose we have a Worker class that performs a time-consuming calculation. The Worker class looks something like this:

// Define the Worker class: public class MathWorker extends Worker { // Define the parameter keys: public static final String KEY_X_ARG = "X"; public static final String KEY_Y_ARG = "Y"; public static final String KEY_Z_ARG = "Z"; / /... and the result key: public static final String KEY_RESULT = "result"; @Override public Worker.WorkerResult doWork() { // Fetch the arguments (and specify default values): int x = getInputData().getInt(KEY_X_ARG, 0); int y = getInputData().getInt(KEY_Y_ARG, 0); int z = getInputData().getInt(KEY_Z_ARG, 0); / /... do the math... int result = myCrazyMathFunction(x, y, z); / /... set the output, and we're done! Data output = new Data.Builder() .putInt(KEY_RESULT, result) .build(); setOutputData(output); return WorkerResult.SUCCESS; }}Copy the code

To create a job and pass parameters, use the following code:

// Create the Data object: Data myData = new Data.Builder() // We need to pass three integers: X, Y, and Z .putInt(KEY_X_ARG, 42) .putInt(KEY_Y_ARG, 421) .putInt(KEY_Z_ARG, 8675309) // ... and build the actual Data object: .build(); / /... then create and enqueue a OneTimeWorkRequest that uses those arguments OneTimeWorkRequest.Builder argsWorkBuilder = new OneTimeWorkRequest.Builder(MathWorker.class) .setInputData(myData); OneTimeWorkRequest mathWork = argsWorkBuilder.build(); WorkManager.getInstance().enqueue(mathWork);Copy the code

The return value is available in the WorkStatus of the task:

WorkManager.getInstance().getStatusById(mathWork.getId())
    .observe(lifecycleOwner, status -> {
         if (status != null) {
           int myResult =
               status.getOutputData().getInt(KEY_RESULT,
                  myDefaultValue));
    // ... do something with the result ...
         }
    });Copy the code

If we link tasks, the output of one task can be used as the input for the next task in the chain. If it is a simple chain, and OneTimeWorkRequest is followed by another OneTimeWorkRequest, the first task returns the result by calling setOutputData(), and the next task retrieves the result by calling getInputData(). If the chain is more complex, for example, where multiple tasks all send outputs to a subsequent task, we can define an InputMerger on ontemeWorkrealth.Builder to specify what should happen if different tasks return outputs with the same key.


Last revision date: 14 May 2018.