Work chain

Work chain is also a very important feature of WorkManager.

You can use WorkManager to create work chains and queue them. A work chain is used to specify multiple dependent tasks and define the order in which these tasks are run. This feature is especially useful when you need to run multiple tasks in a particular order.

For example, suppose your application has three OneTimeWorkRequest objects: workA, workB, and workC. These tasks must be run in that order. To queue these tasks, use the workManager.BeginWith (OneTimeWorkRequest) method to create a sequence and pass the first OneTimeWorkRequest object; This method returns a Continuation object to define a sequence of tasks. Then, add the remaining OneTimeWorkRequest objects in turn using workcontinuation.then (OneTimeWorkRequest); Finally, queue the entire sequence with workcontinuation.enqueue () :

WorkManager.getInstance(myContext)
    .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 runs the tasks in the order requested, based on the constraints specified for each task. If any task returns result.failure (), the entire sequence ends.

You can also pass multiple OneTimeWorkRequest objects to any beginWith(List) and then(List) calls. If you pass multiple OneTimeWorkRequest objects to a single method call, the WorkManager runs all of these tasks in parallel and then runs the other tasks in the sequence. Such as:

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

You can use the WorkContinuation.combine(List) method to join chains of tasks to create more complex sequences. For example, suppose you want to run a sequence like this:

To set this sequence, create two separate chains and join them into a third chain:

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

In this case, WorkManager runs workA before workB. It also runs workC before workD. After both workB and workD are complete, WorkManager runs workE.

※ Note: Although the WorkManager runs the sub-chains sequentially, there is no guarantee how the tasks in Chain1 will overlap with the tasks in Chain2. For example, workB may run before or after workC, or both may run simultaneously. The only guarantee is that the tasks in each subchain will run sequentially, i.e. workB will be started after workA is finished.

Above we have introduced the basic knowledge and basic methods of work chain. Let’s look at an example. In this case, there are three different worker jobs configured to run, possibly in parallel. The results of these workers are then joined and passed to the worker job being cached. Finally, the output of the job is passed to the upload worker, which uploads the results to the remote server.

​
WorkManager.getInstance(myContext)
   // Candidates to run in parallel
   .beginWith(Arrays.asList(plantName1, plantName2, plantName3))
   // Dependent work (only runs after all previous work in chain)
   .then(cache)
   .then(upload)
   // Call enqueue to kick things off
   .enqueue();
​
Copy the code

Input merge

When you link the OneTimeWorkRequest instance, the output of the parent work request is passed in as input to the child. Therefore, in the example above, the output of plantName1, plantName2, and plantName3 will be passed in as input to the cache request.

To manage input from multiple parent work requests, the WorkManager uses InputMerger.

WorkManager offers two different types of InputMerger:

  • OverwritingInputMerger attempts to add all keys from all inputs to the output. If a conflict occurs, it overwrites the previously set key.
  • ArrayCreatingInputMerger will attempt to merge the inputs and create arrays if necessary.

OverwritingInputMerger

OverwritingInputMerger is the default merger method. If there is a key conflict during the merge, the latest value of the key overwrites all previous versions in the generated output data.

For example, if each plant’s input has a key that matches its respective variable name (“plantName1”, “plantName2”, and “plantName3”), the data passed to the cache worker will have three key-value pairs.

If there is a conflict, the last worker will “win” the contention and its value will be passed tocache.

Because the work requests run in parallel, there is no guarantee of their order of execution. In the example above,plantName1You can keep values"tulip""elm"Depending on which value was last written. If there is a possibility of a key conflict and you need to keep all the output data in the merge, thenArrayCreatingInputMergerMight be a better choice.

ArrayCreatingInputMerger

For the example above, assuming we want to keep the output of all the plant name workers, we should use ArrayCreatingInputMerger.

OneTimeWorkRequest cache = new OneTimeWorkRequest.Builder(PlantWorker.class)
       .setInputMerger(ArrayCreatingInputMerger.class)
       .setConstraints(constraints)
       .build();
Copy the code

ArrayCreatingInputMerger pairs each key with an array. If each key is unique, you get a series of unary arrays.

If there are any key conflicts, all the corresponding values are grouped into an array.

Links and working status

As long as the work completes successfully (that is, result.success () is returned), the OneTimeWorkRequest chain executes sequentially. At run time, work requests may fail or be cancelled, which has a downstream impact on dependent work requests.

When the first OneTimeWorkRequest is added to the work request chain queue, all subsequent work requests are blocked until the work of the first work request is completed.

After it is queued and all work constraints are met, the first work request runs. If the work completed successfully in the root OneTimeWorkRequest or List

(that is, returning result.success ()), the system queues the next set of dependent work requests.

As long as each work request completes successfully, the rest of the work requests in the work request chain follow the same pattern of operation until all the work in the chain is complete. This is the simplest use case and often the preferred use case, but it is equally important to handle the error state.

If an error occurs while the worker is processing a work request, you can retry the request according to the retreat policy you have defined. Retrying a request in the request chain means that the system will only retry the request using the input data provided to the request. All other jobs running in parallel will not be affected.

If the retry policy is undefined or exhausted, or if you have otherwise reached a state where OneTimeWorkRequest returns result.Failure (), the work request and all dependent work requests are marked as FAILED.

The same logic applies when OneTimeWorkRequest is cancelled. Any work dependency request will also be marked CANCELLED and unable to perform its work.

Note that if you attach more work requests to a chain of FAILED or CANCELLED work requests, the new additional work requests will also be marked FAILED or CANCELLED, respectively. If you want to extend the work of an existing chain, you need APPEND_OR_REPLACE in ExistingWorkPolicy.

Now let’s verify the working state:

        Data data1 = new Data.Builder().putString("key", "1").build();
        OneTimeWorkRequest uploadWorkRequest1 =
                new OneTimeWorkRequest.Builder(UploadWorker.class)
                        .setInputData(data1)
                        .addTag("work")
                        // Additional configuration
                        .build();
​
        Data data2 = new Data.Builder().putString("key", "2").build();
        OneTimeWorkRequest uploadWorkRequest2 =
                new OneTimeWorkRequest.Builder(UploadWorker.class)
                        .setInputData(data2)
                        .addTag("work")
                        // Additional configuration
                        .build();
​
        Data data3 = new Data.Builder().putString("key", "3").build();
        OneTimeWorkRequest uploadWorkRequest3 =
                new OneTimeWorkRequest.Builder(UploadWorker.class)
                        .setInputData(data3)
                        .addTag("work_work")
                        // Additional configuration
                        .build();
        Log.d(TAG, "WorkRequest 3 id is " + uploadWorkRequest3.getId());
​
        Data data4 = new Data.Builder().putString("key", "4").build();
        OneTimeWorkRequest uploadWorkRequest4 =
                new OneTimeWorkRequest.Builder(UploadWorker.class)
                        .setInputData(data4)
                        .addTag("work_work")
                        // Additional configuration
                        .build();
        Log.d(TAG, "WorkRequest 4 id is " + uploadWorkRequest4.getId());
​
        WorkManager workManager = WorkManager.getInstance(MainActivity.this);
​
        workManager.beginWith(Arrays.asList(uploadWorkRequest1,uploadWorkRequest2))
                .then(uploadWorkRequest3)
                .then(uploadWorkRequest4)
                .enqueue();
Copy the code

As shown in the code above, the Log is printed as follows:

The 2021-01-12 23:17:05. 106, 30630-30665 / com. Example. Myapplication D/MainActivity: UploadWorker doWork and key is 1 2021-01-12 23:17:05. 110, 30630-30666 / com. Example. Myapplication D/MainActivity: UploadWorker doWork and key is 2 2021-01-12 23:17:05. 194, 30630-30669 / com. Example. Myapplication D/MainActivity: UploadWorker doWork and key is 3 2021-01-12 23:17:05. 222, 30630-30670 / com. Example. Myapplication D/MainActivity: UploadWorker doWork and key is 4Copy the code

Through the above Log, we find the following:

①: It is true that 1 and 2 are executed first, and then 3 is executed again.

(二)

If we just add a delay to OneTimeWorkRequest1, we change the code to look like this:

OneTimeWorkRequest uploadWorkRequest1 = new OneTimeWorkRequest.Builder(UploadWorker.class) .setInitialDelay(10, TimeUnit.SECONDS) .setInputData(data1) .addTag("work") // Additional configuration .build(); .Copy the code

The Log is as follows:

The 2021-01-12 23:24:02. 731, 31097-31130 / com. Example. Myapplication D/MainActivity: UploadWorker doWork and key is 2 2021-01-12 23:24:12. 652, 31097-31149 / com. Example. Myapplication D/MainActivity: UploadWorker doWork and key is 1 2021-01-12 23:24:12. 730, 31097-31150 / com. Example. Myapplication D/MainActivity: UploadWorker doWork and key is 3 2021-01-12 23:24:12. 763, 31097-31151 / com. Example. Myapplication D/MainActivity: UploadWorker doWork and key is 4Copy the code

Through the above Log:

(1) It is found that 2 is executed first, which indicates that 1 and 2 are executed in no order.

2: Execute 3 and 4 after 1 is executed.

(三)

Fix the above code again and add cancel for OneTimeWorkRequest1 on the next line after the Worker is submitted to the system

. workManager.beginWith(Arrays.asList(uploadWorkRequest1,uploadWorkRequest2)) .then(uploadWorkRequest3) .then(uploadWorkRequest4) .enqueue(); workManager.cancelWorkById(uploadWorkRequest1.getId());Copy the code

The Log is as follows:

The 2021-01-12 23:29:10. 977, 31585-31618 / com. Example. Myapplication D/MainActivity: UploadWorker doWork and key is 2Copy the code

Log discovery:

When OneTimeWorkRequest1 is cancelled, OneTimeWorkRequest3 and OneTimeWorkRequest4 that are executed at the end of the work chain are cancelled.

(四)

I want to verify the next ExistingWorkPolicy APPEND_OR_REPLACE.

Modify the code as follows:

. WorkContinuation chain1 = workManager.beginUniqueWork("work&work", ExistingWorkPolicy.APPEND_OR_REPLACE, Arrays.asList(uploadWorkRequest1,uploadWorkRequest2)); WorkContinuation chain2 = workManager.beginUniqueWork("work&work", ExistingWorkPolicy.APPEND_OR_REPLACE, Arrays.asList(uploadWorkRequest3,uploadWorkRequest4)) .then(uploadWorkRequest5); chain1.enqueue(); chain2.enqueue(); workManager.cancelWorkById(uploadWorkRequest1.getId());Copy the code

The Log is as follows:

The 2021-01-13 03:52:08. 084, 10457-10457 / com. Example. Myapplication D/MainActivity: WorkRequest 3 is cancelled the 2021-01-13 03:52:08. 084, 10457-10457 / com. Example. Myapplication D/MainActivity: WorkRequest 2 is enqueued 03:52:08. 2021-01-13, 084, 10457-10457 / com. Example. Myapplication D/MainActivity: WorkRequest 4 is cancelled the 2021-01-13 03:52:08. 085, 10457-10457 / com. Example. Myapplication D/MainActivity: WorkRequest 1 is cancelled the 2021-01-13 03:52:08. 094, 10457-10491 / com. Example. Myapplication D/MainActivity: UploadWorker doWork and key is 2 2021-01-13 03:52:08. 123, 10457-10457 / com. Example. Myapplication D/MainActivity: WorkRequest 2 is succeededCopy the code

Through the above Log found:

(1) : as the first performed WorkContinuaton1, then gave the Cancel the WorkRequest1 WorkContinuaton1, so the subsequent WorkRequest3 are affected, 4, has been Cancel.

②: WorkRequest2 can be executed normally due to APPEND_OR_REPLACE.

(五)

If chain1.enqueue() and chain2.enqueue() are executed in reverse order. Do it again.

The Log is as follows:

The 2021-01-13 04:04:33. 951, 10938-10938 / com. Example. Myapplication D/MainActivity: WorkRequest 1 is cancelled the 2021-01-13 04:04:33. 951, 10938-10938 / com. Example. Myapplication D/MainActivity: WorkRequest 3 is enqueued 04:04:33. 2021-01-13, 951, 10938-10938 / com. Example. Myapplication D/MainActivity: WorkRequest 4 is enqueued 04:04:33. 2021-01-13, 962, 10938-11023 / com. Example. Myapplication D/MainActivity: UploadWorker doWork and key is 3 2021-01-13 04:04:33. 968, 10938-11024 / com. Example. Myapplication D/MainActivity: UploadWorker doWork and key is 4 2021-01-13 04:04:34. 011, 10938-10938 / com. Example. Myapplication D/MainActivity: WorkRequest 3 is succeeded 04:04:34. 2021-01-13, 011, 10938-10938 / com. Example. Myapplication D/MainActivity: WorkRequest 5 is enqueued 04:04:34 2021-01-13. 011, 10938-10938 / com. Example. Myapplication D/MainActivity: WorkRequest 4 is succeeded 04:04:34. 2021-01-13, 020, 10938-11026 / com. Example. Myapplication D/MainActivity: UploadWorker doWork and key is 5 2021-01-13 04:04:34. 046, 10938-10938 / com. Example. Myapplication D/MainActivity: WorkRequest 5 is running the 2021-01-13 04:04:34. 057, 10938-11027 / com. Example. Myapplication D/MainActivity: UploadWorker doWork and key is 2 2021-01-13 04:04:34. 057, 10938-10938 / com. Example. Myapplication D/MainActivity: WorkRequest 5 is succeeded 04:04:34 2021-01-13. 057, 10938-10938 / com. Example. Myapplication D/MainActivity: WorkRequest 2 is enqueued 04:04:34. 2021-01-13, 074, 10938-10938 / com. Example. Myapplication D/MainActivity: WorkRequest 2 is succeededCopy the code

Through the above Log found:

Continuation2 is not affected by the Cancel of WorkRequest1, if WorkContinuation2 is executed first.

②: WorkRequest2 of WorkContinuation1 is not affected by the cancel of WorkRequest1 because APPEND_OR_REPLACE.

※ Examples (4) and (5) above need special attention in the actual use. When using multiple work chains, it is necessary to pay attention to the influence of the state of the previous work chain on the later work chain.

References:

Link work | | Android Developers Android Developers (Google, cn)

The WorkManager advanced topic | | Android Developers Android Developers (Google, cn)