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 .
! , .