Hot Article Guide | Click the title to read
Welcome to join the Java and Android architecture knowledge group
An Alipay App surprisingly uses over 30 open-source software
Awesome! 74 complete APP source codes!
Author: snwr611 Link: https://juejin.im/post/5af4aa91f265da0b8d41f714
On May 8th, at the I/O conference, two new Architecture Component libraries were launched: Navigation and WorkManager. Here we will introduce WorkManager.
1. A brief introduction to WorkManager
It is essentially “managing tasks that need to run in the background, – ensuring tasks can be executed even if your application is not started”.
1. Why not use JobScheduler or AlarmManager?
: This idea is actually correct. WorkManager also chooses whether to use JobScheduler or AlarmManager based on your version. JobScheduler is only available from Android 5.x. AlarmManager has always existed. Therefore, WorkManager will choose JobScheduler, Firebase’s JobDispatcher, or AlarmManager based on your device situation.
2. Why not use AsyncTask, ThreadPool, RxJava?
: This point needs special mention. These three are not alternatives to WorkManager. These three tools help you to run background threads in your application, but when the application is killed or closed, they cannot work anymore.
WorkManager, on the other hand, can ensure that the tasks assigned to it can be executed even if the application is killed or the device is restarted.
In fact, Google has also stated: “WorkManager is not designed for background threads within the application. For such needs, you should use ThreadPool”
2. Examples
Let’s show some code.
1. Import WorkManager
Add to app/build.gradle
[kotlin]
implementation "android.arch.work:work-runtime-ktx:1.0.0-alpha01"
[java]
implementation "android.arch.work:work-runtime:1.0.0-alpha01"
2. An example of periodic Pull
Taking a project I did in 2012 as an example, we had a requirement to regularly push some recommended items to users, but at that time the group did not have a push service component, so we chose the “pull strategy” to go live promptly. The client periodically pulls from the backend to see if there are new recommendations. We need to go through two steps. The first step is to determine what work to do (pull recommendation information from the backend); the second step is to queue this work. We also divide the code into two steps.
-
The Worker is the main executor. It only cares about the work it needs to do when it is its turn. It does not care about other things (when it is its turn, its ID, etc.).
Here we need to create a subclass of Worker and override its doWork() method.
class PullWorker : Worker() {
override fun doWork(): WorkerResult {
// Simulate whether the "accept push" option in the settings page is checked
val isOkay = this.inputData.getBoolean("key_accept_bg_work", false)
if(isOkay) {
Thread.sleep(5000) // Simulate long work
val pulledResult = startPull()
val output = Data.Builder().putString("key_pulled_result", pulledResult).build()
outputData = output
return WorkerResult.SUCCESS
} else {
return WorkerResult.FAILURE
}
}
fun startPull() : String{
return "szw [worker] pull messages from backend"
}
}
Wrap the Worker into a WorkRequest and queue it
WorkRequest has several new properties, such as:
-
ID (usually a UUID to ensure uniqueness),
-
When to execute,
-
Any restrictions (e.g., only execute this task when charging and connected to the internet),
-
Execution chain (when a task is completed, only then can it execute my task) WorkManager is responsible for queuing the WorkRequest
class PullEngine {
fun schedulePull(){
// Use PeriodicWorkRequest.Builder class in Java
val pullRequest = PeriodicWorkRequestBuilder<PullWorker>(24, TimeUnit.HOURS)
.setInputData(
Data.Builder()
.putBoolean("key_accept_bg_work", true)
.build()
)
.build()
WorkManager.getInstance().enqueue(pullRequest)
}
}
3. Explanation
-
1. The Worker class does the work. We generally create a subclass of Worker and override the doWork() method. However, the doWork() method does not have parameters. If we have parameter requirements, we can use the Worker.getInputData() method.
-
2. Similarly, the doWork() method returns void. If you want to pass a result out, you can use Worker.setOutputData().
-
3. The data types obtained/set by the above two methods are both Data. This Data is very similar to Bundle in Android, with methods like putInt(key, value), getString(key, defaultValue). Generally, Data is generated using Data.Builder class. For example:
val output = Data.Builder().putInt(key, 23).build()
-
4. The above mentioned WorkRequest is actually an entity that is queued, which wraps the Worker. However, we generally do not directly use the WorkRequest class, but rather its subclasses: OneTimeWorkRequest or PeriodicWorkRequest. Since our pull requirement needs to pull once a day, we did not use OneTimeWorkRequest, but constructed a PeriodicWorkRequest that repeats every 24 hours.
3. Advanced
1. Want to get results
WorkManager provides an interface for us to get results, which is WorkStatus. You can get the WorkStatus of the desired task by its ID. This WorkStatus actually knows that this task is not completed and what return value it has.
Due to the need for decoupling between the foreground and background, this work is actually completed by LiveData. Since there is LiveData, we certainly need a LifecycleOwner (usually our AppCompatActivity).
Let’s look at an example. Taking the above pull example, if we get a result, we will show a notification (here for simplicity, we just print a log when the result is received).
class PullEngine {
fun schedulePull(){
val pullRequest = PeriodicWorkRequestBuilder<PullWorker>(24, TimeUnit.HOURS).build()
WorkManager.getInstance().enqueue(pullRequest)
// The following two lines are newly added to store the task ID
val pullRequestID = pullRequest.id
MockedSp.pullId = pullRequestID.toString() // Simulate storing in SharedPreference
}
}
class PullActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// UUID implements Serializable interface. It can also convert between toString() and fromString() with String
val uuid = UUID.fromString(MockedSp.pullId)
WorkManager.getInstance().getStatusById(uuid)
.observe(this, Observer<WorkStatus> { status ->
if (status != null){
val pulledResult = status.outputData.getString("key_pulled_result", "")
println("szw Activity getResultFromBackend : $pulledResult")
}
})
}
}
Note, the observe() method is used to listen. Its parameters are: observer(LifecycleOwner, Observer
2. Summary of input/output parameters
-
Input: WorkRequest.Builder.setInputData()
-
Worker class: can getInputData(), and setOutputData()
-
Return value: listened via LiveData, can get WorkStatus. And WorkStatus has getOutputData() method, just note that the inputData and outputData mentioned here are not ordinary int, string. But Data class.
3. If the task is completed, but the application is not started, what to do? Will it forcibly start the application to display UI changes?
Good question. But strictly speaking, this is actually not a problem of WorkManager, but of LiveData. LiveData itself is bound to the lifecycle of the Activity. You don’t need to say that the application is killed, even if you exit the registered Activity, you won’t receive notifications from LiveData. Therefore, when your application is killed and the task is completed, there will be no UI notification, and it certainly won’t forcibly start your launcher. (That’s a bit rogue~)
4. Task chain
WorkManager.getInstance()
.beginWith(workA)
.then(workB)
.then(workC)
.enqueue()
In this way, workA, workB, workC will be executed in order. Only after workA is completed will workB be executed. WorkManager can even execute: A –> B –> C –> D in this way, that is, after A is executed, B is executed, and after C is executed, D is executed. Both B and D will be executed after they are completed.
5. What to do if there is already the same task when inserting a task?
WorkManager
can use beginUniqueWork()
to execute a unique work queue (“unique work sequence”). What to do if there are duplicate tasks? This is mainly a ExistingWorkPolicy
class. This class is also an enum in the WorkManager
package. Its values are:
-
REPLACE: Replace the existing task with a new task
-
KEEP: Keep the existing task. Ignore the new task
-
APPEND: Add the new task to the queue. Both new and old tasks exist in the queue.
4. Conclusion
Overall, WorkManager
is not meant to replace thread pools/AsyncTask/RxJava. Instead, it is somewhat similar to AlarmManager for scheduling tasks. It ensures that the tasks you assign can be completed even if your application is not open or the device is restarted.
WorkManager
is well designed. It does not confuse workers and tasks, but decouples them into Worker and WorkRequest. This layered structure is much clearer and easier to extend (e.g., if a new subclass of WorkRequest comes out in the future).
Finally, the input/output parameter design of WorkManager is good. WorkRequest
is responsible for placing parameters, Worker processes and places return values, and finally, WorkStatus extracts return values and notifies listeners via LiveData.
As for features like chained execution and unique work queues, they can also help you when you have similar needs.
If you have good articles to share with everyone, feel free to submit them by sending me the article link directly
Finally, welcome everyone to join our knowledge group, the second phase is hotly underway, with nearly 1000 people joining to learn:
We welcome everyone to join as soon as possible, this phase ends on March 10, 2019, so the earlier you join, the better. The current group fee has been raised from 89 yuan to 99 yuan, and when the number of members reaches 1000, the price will increase significantly, so hurry up!
Scan the WeChat QR code or click the QR code above to receive advanced resources for Android, Python, AI, Java, etc.
For more learning materials, click below on “Read the original text” to get them