Background Work in Android: An Overview of WorkManager Features

Various types of background work are in high demand in mobile applications. Often it is necessary to support work offline, schedule any long and repetitive tasks for a certain time, perform "heavy" tasks without being tied to user interaction scenarios.





For example, in retail, merchandisers may need to send photo reports to the server at the end of each working day and delete them from the phone memory so as not to take up space. And for the online checkout to work, it is required to download the current product directory in the background. In this article we will take a look at one of the most popular tools for implementing background work - WorkManager from Android Jetpack. 





There are many native solutions for background work in Android, such as AlarmManager, Handler, IntentService, SyncAdapter, Loader. However, their fate is different:





  • Handler is still widely used, but mainly for sending events to the main thread's event queue. 





  • The Android system imposes more and more restrictions on the actions of the AlarmManager, and it also has a rather bloated API to work with. 





  • IntentService, , Android API 30 deprecated





  • Loader Activity/Fragment , , , , .





  • SyncAdapter , , .





  • Android 5.0 JobScheduler, ( , wi-fi ..). Service, , , , JobService . api 21.





, , , API , , . 2018 Android Jetpack, WorkManager ( , , ). 









WorkManager , , RxJava2, Jetpack , . API 14 .









1)

Worker doWork():





class MyWorker(context: Context, params: WorkerParameters) : Worker { -----------------------------------
   override fun doWork(): Result { 
       try {
   // 
       } catch (ex: Exception) {
          return Result.failure(); // Result.retry() 
       }
       return Result.success()
   }
}
      
      



doWork() WorkManager’a.





OneTimeWorkRequestBuilder.





val  myWorkRequest = OneTimeWorkRequestBuilder<MyWorker>().build()
      
      



PeriodicWorkRequestBuilder.





val  myWorkRequest = PeriodicWorkRequestBuilder<MyWorker>(30, TimeUnit.MINUTES, 25, TimeUnit.MINUTES).build()
      
      



generic- Worker’a, .





— 30 ( 15 ; 15 , WorkManager 15). flex — 25 . : , 25 30 .





, .





val myWorkRequest = OneTimeWorkRequestBuilder<MyWork>()
       .addTag(“tag”)
       .setInitialDelay(10, TimeUnit.SECONDS)
       .build()
      
      



WorkManager’a.





WorkManager.getInstance(context).enqueue(myWorkRequest)
      
      



2)

:





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



work request’a. 





:





  • setRequiresCharging (boolean requiresCharging) — : .





  • setRequiresBatteryNotLow (boolean requiresBatteryNotLow) — : ( 20, 16).





  • setRequiredNetworkType (NetworkType networkType) — : . , (NetworkType) . :





    • CONNECTED — WiFi Mobile Data





    • UNMETERD — WiFi





    • METERED — Mobile Data





    • NOT_ROAMING — ;





    • NOT_REQUIRED — .





  • setRequiresDeviceIdle (boolean requiresDeviceIdle) — : - “ ”. API 23 .





  • setRequiresStorageNotLow (boolean requiresStorageNotLow) — : , .





3)

WorkManager . , , , . , .





//  3 
WorkManager.getInstance(context)
       .enqueue(myWorkRequest1, myWorkRequest2, myWorkRequest3)

//  3 
WorkManager.getInstance(context)
       .beginWith(myWorkRequest1)
       .then(myWorkRequest2)
       .then(myWorkRequest3)
       .enqueue()
      
      



.





//    5 
WorkManager.getInstance(context)
       .beginWith(myWorkRequest1, myWorkRequest2)
       .then(myWorkRequest3, myWorkRequest4)
       .then(myWorkRequest5)
       .enqueue()
      
      



myWorkRequest1, myWorkRequest2. myWorkRequest3, myWorkRequest4. — myWorkRequest5. , . combine() WorkContinuation :





//  
val chain12 = WorkManager.getInstance(context)
       .beginWith(myWorkRequest1)
       .then(myWorkRequest2);
 
//  
val chain34 = WorkManager.getInstance(context)
       .beginWith(myWorkRequest3)
       .then(myWorkRequest4);
 
//  2  ,  5 
WorkContinuation.combine(chain12, chain34)
       .then(myWorkRequest5)
       .enqueue();
      
      



4)

. beginUniqueWork():





WorkManager.getInstance(context)
       .beginUniqueWork("work123", ExistingWorkPolicy.REPLACE, myWorkRequest1)
       .then(myWorkRequest3)
       .then(myWorkRequest5)
       .enqueue();
      
      



, ( ).





:





  • REPLACE – , ;





  • KEEP – , ;





  • APPEND – .





5)

WorkManager :





  • cancelAllWork() — ( );





  • cancelAllWorkByTag(String tag) — ;





  • cancelUniqueWork(String uniqueWorkName) — ;





  • cancelWorkById(UUID id) — id.





6)

, , . :





  • ENQUEUED – ;





  • RUNNING – ;





  • SUCCEEDED (SUCCESS) – , ;





  • FAILED (FAILURE) – , , ;





  • RETRY – , ;





  • BLOCKED – , ;





  • CANCELLED – , .





:









FAILED, .





:









, : CANCELLED.





WorkManager Jetpack, LiveData:





WorkManager.getInstance(context)
   .getWorkInfoIdLiveData(myWorkRequest.id)
   .observe(this, {
         	Log.d(TAG, "onChanged: " + it.state);
})
      
      



7)

, .





val myData = Data.Builder()
       .putString("keyA", "value1")
       .putInt("keyB", 1)
       .build()
 
val myWorkRequest1 = new OneTimeWorkRequestBuilder<MyWorker>()
       .setInputData(myData)
       .build()
      
      



, :





val valueA = getInputData().getString("keyA", "")
val valueB = getInputData().getInt("keyB", 0)
      
      



Result.success() Result.failure() .





8)

. . myWorkRequest1 myWorkRequest2 , myWorkRequest3. . 





WorkManager.getInstance(context)
       .beginWith(myWorkRequest1, myWorkRequest2)
       .then(myWorkRequest3)
       .enqueue()

//  1 
val output = Data.Builder()
       .putString("keyA", "value1")
       .putInt("keyB", 1)
       .build()
Result.success(output)
//  2 
val output = Data.Builder()
       .putString("keyA", "value2")
       .putInt("keyB", 2)
       .build()
Result.success(output)
      
      



, , , . .





9) InputMerger

InputMerger. OverwritingInputMerger, . . , ArrayCreatingInputMerger. 





InputMerger . ArrayCreatingInputMerger myWorkRequest3 .





val myWorkRequest3 = new OneTimeWorkRequestBuilder<MyWorker>()
       .setInputMerger(ArrayCreatingInputMerger.class)
       .build()

 // :
val valueA = getInputData().getStringArray("keyA")
val valueB = getInputData().getIntArray("keyB")
val valueC = getInputData().getStringArray("keyC")
val valueD = getInputData().getStringArray("keyD")
      
      



keyA , ["value1", "value2"]. keyB — [1, 2].





10) WorkManager

WorkManager , WorkManagerInitializer, . , , InputMerger’ WorkerFactory ( Worker’, , Worker’a , WorkManager , , ). .





WorkManager’a. .





<provider android:authorities=”${applicationId}.workmanager-init”
android:name=”androidx.work.impl.WorkManagerInitializer”
tools.node=”remove” />
      
      



Configuration.Provider. . , Executor, Worker’, :





class TestProjectApplication : Application(), Configuration.Provider {
   override fun getWorkManagerConfiguration(): Configuration {
       return Configuration.Builder()
           .setExecutor(Executors.newFixedThreadPool(5))
           .setMinimumLoggingLevel(Log.DEBUG)
           .build()
   }
}
      
      



11)

Worker’





androidTestImplementation "androidx.work:work-testing:$work_version"
      
      



, . 





Worker, 2 . :





@Test
fun testAdditionWorker() {
   //     
   val inputData = workDataOf("first" to 1, "second" to 2)
  //   Worker’a
   val worker = TestListenableWorkerBuilder<MyWorker>(context, inputData).build()

   //  
   val result = worker.doWork()

   // ,        
   assertTrue(result is Success)
   assertEquals((result as Success).outputData.getInt("result", 0), 4)
}
      
      



, Worker’a , . . 





Worker, 2 . .





WorkManagerTestInitHelper.initializeTestWorkManager(context)
val testDriver = WorkManagerTestInitHelper.getTestDriver(context)!!
val workManager = WorkManager.getInstance(context)
val constraints = Constraints.Builder()
   .setRequiredNetworkType(NetworkType.UNMETERED)
   .setRequiresCharging(true)
   .build()
val workRequest = OneTimeWorkRequestBuilder<MyWorker>()
   .setConstraints(constraints)
   .build()

//        
workManager.enqueue(workRequest).result.get()
//     
testDriver.setAllConstraintsMet(workRequest.id)

//   
val workInfo = workManager.getWorkInfoById(workRequest.id).get()
assertEquals(workInfo.state, WorkInfo.State.SUCCEEDED)
      
      



WorkManager , , , , , , , . . , API 14, must-have .





! , .








All Articles