Berbagai jenis pekerjaan latar belakang banyak diminati dalam aplikasi seluler. Seringkali diperlukan untuk mendukung pekerjaan offline, menjadwalkan tugas yang panjang dan berulang untuk waktu tertentu, melakukan tugas "berat" tanpa terikat pada skenario interaksi pengguna.
Misalnya, di retail, merchandiser mungkin perlu mengirim laporan foto ke server di akhir setiap hari kerja dan menghapusnya dari memori telepon agar tidak memakan tempat. Dan agar checkout online berfungsi, Anda harus mengunduh direktori produk saat ini di latar belakang. Pada artikel ini, kita akan melihat salah satu alat paling populer untuk mengimplementasikan pekerjaan latar belakang - WorkManager dari Android Jetpack.
Ada banyak solusi asli untuk pekerjaan latar belakang di Android, seperti AlarmManager, Handler, IntentService, SyncAdapter, Loader. Namun, nasib mereka berbeda:
Penangan masih banyak digunakan, tetapi terutama untuk mengirim acara ke antrean acara utas utama.
Sistem Android memberlakukan lebih banyak batasan pada tindakan AlarmManager, dan juga memiliki API yang agak membengkak untuk dikerjakan.
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 .
! , .