Two new Architeture Component libraries were introduced at THE I/O conference on May 8: Navigation and WorkManager. Here’s an introduction to WorkManager.
WorkManager WorkManager
“Manage tasks that need to work in the background — ensuring that they can be executed even if your application isn’t started.”
1. Why not use JobScheduler and AlarmManger?
: Actually, that’s a good idea. The underlying WorkManager depends on what version of the workScheduler you are working on. JobScheduler is available on Android 5.x. And AlarmManager always exists. So WorkManager at the bottom, depending on your device, selects JobScheduler, Firebase JobDispatcher, or AlarmManager
2. Why not use AsyncTask, ThreadPool, RxJava?
This point needs to be made in particular. These three relationships with WorkManager are not substitutive. These three tools can help you start background threads in your application, but they don’t work when your application is killed or shut down. WorkManager, on the other hand, ensures that the tasks you assign it will be executed even after the application is killed and the device is restarted.
In fact, Google itself says :”WorkManager is not designed for in-app background threads. You should use ThreadPool for this requirement.”
Examples examples
Just show me the code.
1. Import the WorkManager
In the app/build. Gradle to join
[kotlin]
Implementation "android. The arch. The work: work - the runtime - KTX: 1.0.0 - alpha01"
[java]
Implementation "android. The arch. The work: work - the runtime: 1.0.0 - alpha01"
2. An example of regular Pull
Take, for example, a project I did in 2012, when I was working on an e-commerce project. We had a requirement to regularly push some items we recommended to users. However, at that time, the group did not have push service components, so we adopted the “pull strategy “in order to get online in time. The client periodically pulls from the background to see if there are any new recommendations.
This is where we take two steps. The first step is to determine what to do (pull recommendations in the background). The second step is to let this live into the queue.
We also have two steps in the code
- Worker is the main body of work. It just gets the job done when it comes to it. Regardless of other things (how its turn, its ID…) .
Create a new subclass of Worker and override its doWork() method.
class PullWorker : Worker() {
override fun doWork(a): WorkerResult {
// Whether "Accept push" is checked in the simulation Settings page
val isOkay = this.inputData.getBoolean("key_accept_bg_work".false)
if(isOkay) {
Thread.sleep(5000) // Simulate long working hours
val pulledResult = startPull()
val output = Data.Builder().putString("key_pulled_result", pulledResult).build()
outputData = output
return WorkerResult.SUCCESS
} else {
return WorkerResult.FAILURE
}
}
fun startPull(a) : String{
return "szw [worker] pull messages from backend"}}Copy the code
- Wrap Worker as a WorkRequest and merge it into a column
- WorkRequest has some new attributes:
- ID(typically a UUID to ensure uniqueness),
- When to execute,
- Are there restrictions (such as only performing this task when charging and connecting to the Internet),
- Execution chain (when a task is finished, it is my turn to execute it)
- The WorkManager is responsible for putting the WorkRequest into the column
- WorkRequest has some new attributes:
class PullEngine {
fun schedulePull(a){
/ / Java. Please use PeriodicWorkRequest Builder class
val pullRequest = PeriodicWorkRequestBuilder<PullWorker>(24, TimeUnit.HOURS)
.setInputData(
Data.Builder()
.putBoolean("key_accept_bg_work".true)
.build()
)
.build()
WorkManager.getInstance().enqueue(pullRequest)
}
}
Copy the code
3. The interpretation of the
1. The Worker class. We typically create a subclass of Worker and override the doWork() method. However, the doWork() method takes no parameters. We sometimes have parameter requirements, what do we do? This is where the worker.getinputData () method comes in.
2. Similarly, the doWork() method returns void. If you have a result you want to pass along, use worker.setoutputData ().
3. The above two methods get/set the Data type as Data. This Data is very similar to our Android Bundle, including putInt(key, value) and getString(key, defaultValue) methods.
General Data generation, is to use Data.Builder class. Such as:
val output = Data.Builder().putInt(key, 23).build()
Copy the code
4. As mentioned above, WorkRequest is actually an entity in the column, which packages Worker. But PeriodWorkReqeust is not used directly, but periodWorkRequest or PeriodWorkReqeust.
Since our pull requirement is to pull once a day, we did not use OneTimeWorkRequest here, but instead built a PeriodicWorkReqeust that works repeatedly 24 hours a day.
3. Advanced
1. You want results
WorkManager provides an interface to get results. This is called WorkStatus. You can get the WorkStatus of the task you want from the id. The WorkStatus of the task is to know what return value the task has not completed.
Because the front and back are decoupled, this work is actually done by LiveData. Since we have LiveData, we definitely need to have a LifecycleOwner (which is generally our AppcompatActivity).
For example, in the pull example above, if we pull the result, we will display a notification (in this case, print the log after receiving the result).
[PullEngine.kt]
class PullEngine {
fun schedulePull(a){
val pullRequest = PeriodicWorkRequestBuilder<PullWorker>(24, TimeUnit.HOURS).build()
WorkManager.getInstance().enqueue(pullRequest)
// The following two lines are newly added to store the ID of the task
val pullRequestID = pullRequest.id
MockedSp.pullId = pullRequestID.toString() // The simulation exists in SharedPreference}}Copy the code
[PullActivity.kt]
class PullActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?). {
super.onCreate(savedInstanceState)
// UUID implements Serializable interface. ToString (), fromString() and String can also be interrotated
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")}})}}Copy the code
Observe that the observe() method is used to listen. Its arguments are observer(LifecycleOwner, observer
)
2. Summarize the input/return values
The ginseng: WorkRequest. Builder. SetInputData ()
Worker class: getIntpuData() and setOutputData()
Return value: WorkStatus. WorkStatus has the getOutputDat() method
Just notice that inputData and outputDat are not ordinary inputData and string. It’s the Data class.
3. What if the task is finished but the application is not started? Will I forcibly launch the application to show UI changes?
: Good question, but strictly speaking, this isn’t really a WorkManager problem, it’s a LiveData problem. LiveData itself is bound to the Activity lifecycle. You don’t have to say that the app was killed. Even if you quit the registered Activity, you won’t receive notifications from LiveData. So when your app is killed and the task is finished, there is no NOTIFICATION from the UI, let alone forcing your startup.
4. The task chain
WorkManager.getInstance()
.beginWith(workA)
.then(workB)
.then(workC)
.enqueue()
Copy the code
WorkA is executed in the order of workA, workB, and workC. WorkB is executed after workA is executed.
WorkManager can even perform:
A --> B
--> E
C --> D
Copy the code
In this form, B is executed only after A is executed, C is executed only after D. B and D are executed only after E is executed.
5. What can I do if the same task already exists when I insert a task?
WorkManager can use beginUniqueWork() to execute a unique work sequence. What if there is duplication of tasks?
This is primarily an ExistingWorkPolicy class. This class is also a class in the WorkManager package. It is actually an Enum. The values are:
- REPLACE: REPLACE existing tasks with new ones
- KEEP: KEEP existing tasks. Ignoring new Tasks
- APPEND: The new task is added to the column. Both new and old tasks exist in the queue.
4. To summarize
In general, WorkManager is not intended to replace thread pools /AsyncTask/RxJava. AlarmManager is used to do scheduled tasks. Make sure the task you give it can be done, even if your app is not opened or the device is restarted.
WorkManager is better at design. Worker and task are not confused, but decoupled into worker and WorkRequest. This will make the layering clearer and easier to extend (e.g., subclasses of WorkRequest).
Finally, WorkManager’s input and output parameters are well designed. WorkReqeust is responsible for putting parameters, Worker processes and places return values, and finally, WorkStaus retrieves return values, and LiveData informs listeners.
As for chained execution, unique work queues are also features that can help you if you have similar needs.