Différents types de travaux de fond sont très demandés dans les applications mobiles. Il est souvent nécessaire de prendre en charge le travail hors ligne, de planifier des tâches longues et répétitives pendant un certain temps, d'effectuer des tâches «lourdes» sans être lié à des scénarios d'interaction utilisateur.
Par exemple, dans le commerce de détail, les marchands peuvent avoir besoin d'envoyer des rapports photo au serveur à la fin de chaque journée de travail et de les supprimer de la mémoire du téléphone afin de ne pas prendre de place. Et pour que le paiement en ligne fonctionne, il est nécessaire de télécharger le répertoire actuel des produits en arrière-plan. Dans cet article, nous examinerons l'un des outils les plus populaires pour la mise en œuvre du travail en arrière-plan - WorkManager d'Android Jetpack.

Il existe de nombreuses solutions natives pour le travail en arrière-plan sous Android, telles que AlarmManager, Handler, IntentService, SyncAdapter, Loader. Cependant, leur sort est différent:
Le gestionnaire est encore largement utilisé, mais principalement pour envoyer des événements à la file d'attente d'événements du thread principal.
Le système Android impose de plus en plus de restrictions sur les actions du AlarmManager, et il dispose également d'une API plutôt gonflée avec laquelle travailler.
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 .
! , .