1. Introduction

When we want to implement the processing that is not urgent but needs to be done, it is difficult to implement the extra power consumption if using service, difficult to implement if using Broadcast and need to set trigger conditions. In order to solve this problem, Google introduced JobScheduler in Android 5.0 to replace the previous solution. As a further release of WorkManager on Android Jetpack, it will implement these requirements in different ways depending on the version of the system.

2. A brief introduction to WorkManager

WorkManager is an Android library that gracefully runs deferred background work when working triggers, such as appropriate network state and battery conditions, are met. Where possible, WorkManager uses the framework JobScheduler to help optimize battery life and batch jobs. On devices below Android 6.0 (API level 23), try Firebase JobDispatcher if the WorkManager already includes your application's dependencies. Otherwise, WorkManager returns to the custom AlarmManager implementation to gracefully handle your background work.

According to the official narrative, WorkManager automatically performs background tasks based on constraints. If the system version is higher than 6.0, JobScheduler is used; if AlarmManager+BroadCastReceiver is used, AlarmManager+BroadCastReceiver is used.

3. Usage

3.1 Adding a Dependency

Add the following dependencies to build.gradle in the Module.

implementation "Androidx. Work: work - the runtime - KTX: 2.3.3." "
Copy the code

3.2 Creating background Processing

We first need to create tasks that need to be executed. We need to inherit the Worker class, and we need to override the doWork method.

class CustomWorker(context: Context, workerParameters: WorkerParameters) :
    Worker(context, workerParameters) {
    override fun doWork(a): Result {
        Log.d("CustomWorker"."Worker is active in " + inputData.getString("data"))
        return Result.success()
    }
}
Copy the code

Inputdata.getstring (key) is to get the external passed value. Use the same method as an intent to pass values. The value we need to return is the enumerated value of Result. There are several kinds as follows.

  1. Result.success()The task is successfully executed.
  2. Result.retry()The task fails and needs to be tried again. Rules of retreat that can be described laterBackoffPolicyThe setting method is tried.
  3. Result.failure()Indicates that the task fails to be executed but will not be attempted again.

3.3 Creating trigger restrictions

We need to set constraints on the trigger by Constrains.

        val constraints = Constraints.Builder()
            .setRequiredNetworkType(NetworkType.CONNECTED)
            .setRequiresBatteryNotLow(true)
            .setRequiresStorageNotLow(true)
            .setRequiresCharging(true)
            .setRequiresDeviceIdle(true)
            .build()
Copy the code
3.3.1 setRequiredNetworkType

Set the required network type. There are several kinds as follows.

Enumerated values State that
NOT_REQUIRED You don’t need the Internet
CONNECTED Any available network
UNMETERED Need unmetered network, such as WiFi
NOT_ROAMING A non-roaming network is required
METERED Need a metering network, such as 4G
3.3.2 rainfall distribution on 10-12 setRequiresBatteryNotLow

Set the battery status of the mobile phone. The condition here is that the battery of the mobile phone should not be low during the task.

3.3.3 setRequiresStorageNotLow

Set the state of the phone’s memory space, as long as the phone’s memory space is not low when performing tasks.

3.3.4 setRequiresCharging

Set the charging state of the mobile phone. The condition here is that the mobile phone is in the charging state when performing the task.

3.3.5 setRequiresDeviceIdle

Set the state of the phone. The condition here is that the phone is idle when performing the task.

3.3.6 addContentUriTrigger (uri: uri, triggerForDescendants: Boolean)

Add a trigger that fires a task when there is an update in the specified URI. It is worth noting that this can only be set in OneTimeWorkRequest.

3.4 create WorkRequest

There are two types of WorkRequest. OneTimeWorkRequest is executed once, and PeriodicWorkRequest is executed periodically.

val oneTimeWorkRequest =
OneTimeWorkRequestBuilder<CustomWorker>().setConstraints(constraints)
    .setBackoffCriteria(
        BackoffPolicy.LINEAR,
        OneTimeWorkRequest.MIN_BACKOFF_MILLIS,
        TimeUnit.MILLISECONDS
    )
    .setInputData(inputData)
    .build()

val periodicWorkRequest =
PeriodicWorkRequestBuilder<CustomWorker>(10, TimeUnit.MILLISECONDS)
    .setConstraints(constraints)
    .setBackoffCriteria(
        BackoffPolicy.LINEAR,
        PeriodicWorkRequest.MIN_BACKOFF_MILLIS,
        TimeUnit.MILLISECONDS
    ).build()
Copy the code

In the PeriodicWorkRequest constructor, you also need to add an execution cycle, which validates the condition every 10 seconds. However, the interval between periodic tasks is at least 15 minutes. Therefore, even if a task is written for 10 seconds, it will be executed 15 minutes later.

3.4.1 track setConstraints

We need to set Constraints(the Constraints described above) in the WorkRequest.

3.4.2 setBackoffCriteria

We can also set retreat conditions. Retreat conditions have two properties:

  1. BackoffPolicy, which defaults to exponential, but can be set to linear.
  2. Duration. Default value: 30 seconds.
Rule 3.4.3 setInputData

We can pass values to the Worker through the setInputData method in WorkRequest. Use the same method as Intent, as follows.

 val inputData = Data.Builder().putString("data"."MainActivity").build()
Copy the code

3.5 Using WorkManager

Finally, we can implement the requirements specified above through WorkManager.

WorkManager.getInstance(this).enqueue(oneTimeWorkRequest)
Copy the code

3.6 Monitoring Worker execution

We can also monitor the execution of workers through LiveData. When the Worker is executed, the monitoring office is notified.

WorkManager.getInstance(this).getWorkInfoByIdLiveData(oneTimeWorkRequest.id).observe(this,
    Observer {
        count++
        txtId.text = "Work status is changed! $count"
        Toast.makeText(this."Work status is changed!",Toast.LENGTH_LONG).show()
    })
Copy the code

The source code for WorkInfo is as follows:

private @NonNull UUID mId;
    private @NonNull State mState;
    private @NonNull Data mOutputData;
    private @NonNull Set<String> mTags;
    private @NonNull Data mProgress;
    private int mRunAttemptCount;

    / * * *@hide* /
    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
    public WorkInfo(
            @NonNull UUID id,
            @NonNull State state,
            @NonNull Data outputData,
            @NonNull List<String> tags,
            @NonNull Data progress,
            int runAttemptCount) {
        mId = id;
        mState = state;
        mOutputData = outputData;
        mTags = new HashSet<>(tags);
        mProgress = progress;
        mRunAttemptCount = runAttemptCount;
    }
Copy the code

So let’s first look at State.

public enum State {

    ENQUEUED,// Join the queue
    RUNNING,/ / run
    SUCCEEDED,/ / has been successful
    FAILED,/ / fail
    BLOCKED,/ / hung
    CANCELLED;/ / cancel

    public boolean isFinished() {
        return (this == SUCCEEDED || this == FAILED || this== CANCELLED); }}Copy the code

When BLOCKED is BLOCKED, the Worker will be suspended if the constraints are not fully met.

3.7 Ending a Task

After the WorkRequest is listed, The WorkManager will assign a work ID to it. We can cancel or stop the task by using the work ID.

WorkManager.getInstance(this).cancelWorkById(workRequest.id)
Copy the code

WorkManager does not necessarily have the ability to end a task, because some tasks may have already completed. There are other ways to end a task besides the above methods:

  1. cancelAllWork(): Cancel all tasks
  2. cancelAllWorkByTag(tag:String): Cancels a set of tasks with the same label
  3. cancelUniqueWoork(uniqueWorkName:String): Cancels a unique task

3.8 Chain call

When we need to execute the WorkRequest sequentially or simultaneously, we can use the chain-called method to execute it.

3.8.1 Parallel Execution
val workRequest1 = OneTimeWorkRequestBuilder<CustomWorker>().build()
val workRequest2 = OneTimeWorkRequestBuilder<CustomWorker>().build()
val workRequest3 = OneTimeWorkRequestBuilder<CustomWorker>().build()

// execute simultaneously.
WorkManager.getInstance().beginWith(workRequest1,workRequest2,workRequest3).enqueue()
Copy the code
3.8.2 Sequential Execution

We can use the beginwith-then method for sequential execution. If one of the sequential tasks fails, subsequent tasks will not be executed.

// Execute sequentially
WorkManager.getInstance().beginWith(workRequest1).then(workRequest2).then(workRequest3).enqueue()
Copy the code
3.8.3 Group Execution

We can perform combinatorial execution using the Combine operator.

// combine execution
val chain1 = WorkManager.getInstance()
    .beginWith(workRequest1)
    .then(workRequest2)
val chain2 = WorkManager.getInstance()
    .beginWith(workRequest3)
    .then(workRequest4)
WorkContinuation
    .combine(chain1, chain2)
    .then(workRequest5)
    .enqueue()
Copy the code
The only chain 3.8.4

A unique chain, as its name implies, cannot have tasks with the same name in the execution queue at the same time.

val workRequest = OneTimeWorkRequestBuilder<CustomWorker>().build()
WorkManager.getInstance(this).beginUniqueWork("TAG", ExistingWorkPolicy.REPLACE, workRequest)
Copy the code

Note that the most variable length parameter is WorkRequest. The first parameter is the task name “TAG”. The second parameter is the execution policy for an existing task. Enumeration values are as follows.

public enum ExistingWorkPolicy {
    REPLACE, / / replace
    KEEP, // Hold, do nothing
    APPEND // Add to an existing task
}
Copy the code
  1. REPLACE: If a task with the same name exists and is suspended, cancel and delete the existing task and replace the new task.
  2. KEEP: If a task with the same name exists and is suspended, no operation is performed.
  3. APPEND: If a task with the same name exists and is suspended, a new task is added to the cache. When all tasks in the queue are executed, the new task is set to the first task.

Making: github.com/HyejeanMOON…