Trabajo en segundo plano en Android: descripciĂłn general de las funciones de WorkManager

Varios tipos de trabajo en segundo plano tienen una gran demanda en las aplicaciones mĂłviles. A menudo es necesario apoyar el trabajo fuera de lĂ­nea, programar tareas largas y repetitivas durante un tiempo determinado, realizar tareas "pesadas" sin estar atado a los escenarios de interacciĂłn del usuario.





Por ejemplo, en el comercio minorista, es posible que los comerciantes deban enviar informes fotográficos al servidor al final de cada dĂ­a hábil y eliminarlos de la memoria del telĂ©fono para no ocupar espacio. Y para que el pago en lĂ­nea funcione, es necesario descargar el directorio de productos actual en segundo plano. En este artĂ­culo, veremos una de las herramientas más populares para implementar el trabajo en segundo plano: WorkManager de Android Jetpack. 





Hay muchas soluciones nativas para el trabajo en segundo plano en Android, como AlarmManager, Handler, IntentService, SyncAdapter, Loader. Sin embargo, su destino es diferente:





  • Handler todavĂ­a se usa ampliamente, pero principalmente para enviar eventos a la cola de eventos del hilo principal. 





  • El sistema Android impone cada vez más restricciones a las acciones del AlarmManager, y tambiĂ©n tiene una API bastante inflada con la que trabajar. 





  • 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