モバイルアプリケーションでは、さまざまな種類のバックグラウンド作業が強く求められています。多くの場合、オフラインでの作業をサポートし、特定の時間に長く反復するタスクをスケジュールし、ユーザーの操作シナリオに縛られることなく「重い」タスクを実行する必要があります。
たとえば、小売業では、マーチャンダイザーは、スペースを占有しないように、各営業日の終わりにサーバーに写真レポートを送信し、電話のメモリからそれらを削除する必要がある場合があります。また、オンラインチェックアウトを機能させるには、現在の製品ディレクトリをバックグラウンドでダウンロードする必要があります。この記事では、バックグラウンド作業を実装するための最も人気のあるツールの1つであるAndroidJetpackのWorkManagerについて説明します。
AlarmManager、Handler、IntentService、SyncAdapter、Loaderなど、Androidのバックグラウンド作業用のネイティブソリューションは多数あります。しかし、彼らの運命は異なります:
ハンドラーは今でも広く使用されていますが、主にメインスレッドのイベントキューにイベントを送信するために使用されます。
Androidシステムは、AlarmManagerのアクションにますます多くの制限を課しており、操作するAPIもかなり肥大化しています。
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 .
! , .