Hintergrundarbeit in Android: Ein Ăśberblick ĂĽber die WorkManager-Funktionen

Verschiedene Arten von Hintergrundarbeiten sind in mobilen Anwendungen sehr gefragt. Oft ist es notwendig, die Offline-Arbeit zu unterstĂĽtzen, lange und sich wiederholende Aufgaben fĂĽr eine bestimmte Zeit zu planen und "schwere" Aufgaben auszufĂĽhren, ohne an Benutzerinteraktionsszenarien gebunden zu sein.





Im Einzelhandel mĂĽssen Merchandiser beispielsweise am Ende eines jeden Arbeitstages Fotoberichte an den Server senden und diese aus dem Telefonspeicher löschen, um keinen Speicherplatz zu beanspruchen. Damit die Online-Kaufabwicklung funktioniert, muss das aktuelle Produktverzeichnis im Hintergrund heruntergeladen werden. In diesem Artikel werden wir uns eines der beliebtesten Tools zur Implementierung von Hintergrundarbeiten ansehen - WorkManager von Android Jetpack. 





Es gibt viele native Lösungen für die Hintergrundarbeit in Android, z. B. AlarmManager, Handler, IntentService, SyncAdapter, Loader. Ihr Schicksal ist jedoch anders:





  • Der Handler wird immer noch häufig verwendet, hauptsächlich jedoch zum Senden von Ereignissen an die Ereigniswarteschlange des Hauptthreads. 





  • Das Android-System schränkt die Aktionen des AlarmManagers immer mehr ein und verfĂĽgt auch ĂĽber eine ziemlich aufgeblähte API, mit der gearbeitet werden kann. 





  • 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