Upgrade to Pro — share decks privately, control downloads, hide ads and more …

WorkManager: the Beginning

WorkManager: the Beginning

at Android Dev Summit 2019 extended Seoul
https://festa.io/events/744

Sungyong An

December 21, 2019
Tweet

More Decks by Sungyong An

Other Decks in Programming

Transcript

  1. Before WorkManager AlarmManager API 1 • ౠ੿ द੼ী জ੉ प೯غب۾

    ৘ডೞח ӝמ • ߈ࠂ੸ੋ ੘স प೯ оמ • জ੉ प೯غ૑ ঋওਸ ٸب ࢎਊ оמ • API 19+ ੿ഛೠ दрী प೯غח Ѫਸ ࠁ੢ೞ૑ ঋ਺ (ߓఠܻ ࢎਊ ୭ࣗച ݾ੸)
  2. Before WorkManager AlarmManager API 1 JobScheduler API 21 • ౠ੿

    ઑѤী प೯غب۾ ੘সਸ ৘ডೞח ӝמ (֎౟ਕ௼ ࢚క, ؀ۚ੸ੋ प೯दр ١) • Doze ݽ٘ ؀਽ (ߓఠܻ ࢎਊ ѐࢶ) • API 21+ ࢎਊ оמ
  3. Before WorkManager GCM 2015 AlarmManager API 1 JobScheduler API 21

    GCMNetworkManager Doze mode API 23 • API 14+ ࢎਊ оמ • GCM Service ӝ߈ (Google Playо ࢸ஖غযঠ ೣ)
  4. Before WorkManager GCM Evernote 2015 2015 AlarmManager API 1 JobScheduler

    API 21 GCMNetworkManager Doze mode API 23 android-job • API 14+ ࢎਊ оמ • JobScheduler (21+) • GCM Service (21-) • Not Official
  5. Before WorkManager Firebase GCM Evernote 2015 2016 2015 AlarmManager API

    1 JobScheduler API 21 GCMNetworkManager Doze mode API 23 JobDispatcher android-job • API 14+ ࢎਊ оמ • JobScheduler (21+) • GCM Service (21-)
  6. Before WorkManager Firebase GCM Evernote 2015 2016 2015 AlarmManager API

    1 JobScheduler API 21 GCMNetworkManager Doze mode API 23 JobDispatcher android-job Deprecated Now WorkManager Jetpack
  7. 1.0 • Scheduling background work • execution of guaranteed •

    constraint-aware • Backwards compatible up to API 14 • 23+: JobScheduler • 14~22: BroadcastReceiver + AlarmManager • Supports Task Chainining • Supports Rx, Coroutine • Depends on Room
  8. Basic class MyWorker(appContext: Context, workerParams: WorkerParameters) : Worker(appContext, workerParams) {

    override fun doWork(): Result { // do something! return Result.success() } } val constraints = Constraints.Builder().build() val request = OneTimeWorkRequest.Builder(MyWorker::class.java) .setConstraints(constraints) .build() WorkManager.getInstance().enqueue(request)
  9. Basic - Internal WorkManager.getInstance().enqueue(request) -> WorkManagerImpl.enqueue(...) -> WorkContinuationImpl(...).enqueue() -> EnqueueRunnable(...).run()

    -> // Store in WorkManager DB workDatabase.workSpecDao().insertWorkSpec(...) workDatabase.dependencyDao().insertDependency(...) workDatabase.workTagDao().insert(...) workDatabase.workNameDao().insert(...) -> scheduleWorkInBackground() After force-stop, reschedule!!
  10. Constraints val constraints = Constraints.Builder() .setRequiresCharging(true) .setRequiresDeviceIdle(true) // API 23+

    .setRequiredNetworkType(NetworkType.CONNECTED) .setRequiresBatteryNotLow(true) .setRequiresStorageNotLow(true) .build()
  11. WorkRequests val uploadWorkRequest = OneTimeWorkRequestBuilder<UploadWorker>() .setInitialDelay(10, TimeUnit.MINUTES) .setBackoffCriteria( BackoffPolicy.LINEAR, OneTimeWorkRequest.MIN_BACKOFF_MILLIS,

    TimeUnit.MILLISECONDS) .setInputData(workDataOf(KEY_IMAGE_URI to imageUriString)) .setConstraints(constraints) .addTag("cleanup") .build() val uploadWorkRequest = PeriodicWorkRequestBuilder<UploadWorker>(1, TimeUnit.HOURS) .setConstraints(constraints) .build()
  12. Task Chaining val request: OneTimeWorkRequest = ... val nextRequest: OneTimeWorkRequest

    = … // W/o Chaining WorkManager.getInstance().enqueue(request) // With Chaining WorkManager.getInstance() .beginWith(request) .then(nextRequest) .then(listOf(request, request, request)) .enqueue()
  13. Cancel Work WorkManager.getInstance(this).cancelAllWork() val request = OneTimeWorkRequestBuilder<...>().addTag("tag_name").build() WorkManager.getInstance(this).cancelAllWorkByTag("tag_name") val request

    = OneTimeWorkRequestBuilder<...>().build() WorkManager.getInstance(this).cancelWorkById(request.id) WorkManager.getInstance(this).enqueueUniqueWork("uniqueWorkName", ...) WorkManager.getInstance(this).enqueueUniquePeriodicWork("uniqueWorkName", ...) WorkManager.getInstance(this).cancelUniqueWork("uniqueWorkName")
  14. Worker public abstract class ListenableWorker { @MainThread public abstract @NonNull

    ListenableFuture<Result> startWork(); public abstract static class Result { ... } } public abstract class Worker extends ListenableWorker { @WorkerThread public abstract @NonNull Result doWork(); } public abstract class RxWorker extends ListenableWorker { @MainThread public abstract Single<Result> createWork(); } abstract class CoroutineWorker(...) : ListenableWorker(...) { abstract suspend fun doWork(): Result } work-runtime work-rxjava2 work-runtime-ktx
  15. 2.1 • Supports On-Demand Initialization • Supports more testing •

    TestWorkerBuilder • TestListenableWorkerBuilder
  16. Initialization WorkManager Initializer Application WorkManager initialize() onCreate() onCreate() Auto On

    Demand WorkManager getInstance(context) initialize() Application getWorkManager Configuration() Application onCreate()
  17. Auto On Demand // do nothing <provider android:name="androidx.work.impl.WorkManagerInitializer" android:authorities="${applicationId}.workmanager-init" tools:node="remove"

    /> class MyApplication() : Application(), Configuration.Provider { override getWorkManagerConfiguration() = Configuration.Builder() .setMinimumLoggingLevel(android.util.Log.INFO) .build() } Initalization
  18. Testing CoroutineWorker class TestWorker(...) : CoroutineWorker(context, parameters) { override suspend

    fun doWork(): Result { return Result.success() } } @Test fun testCoroutineWorkerTest() { runBlocking { val worker = TestListenableWorkerBuilder<TestWorker>(context) .setInputData(workDataOf("key" to 10)) .setRunAttempCount(2) .build() val result = worker.startWork().await() assertThat(result).isEqualTo(Result.success()) } }
  19. Testing Worker class TestWorker(...) : Worker(context, parameters) { override fun

    doWork(): Result { return Result.success() } } @Test fun testWorkerTest() { runBlocking { val executor = Executors.newSingleThreadExecutor() val worker = TestWorkerBuilder<TestWorker>(context, executor) .setInputData(workDataOf("key" to 10)) .setRunAttempCount(2) .build() val result = worker.startWork().await() assertThat(result).isEqualTo(Result.success()) } }
  20. 2.2 • GCMNetworkManager ૑ਗ (API 14 ~ 22) def work_version

    = "2.2.0" implementation "androidx.work:work-runtime:$version" implementation "androidx.work:work-gcm:$version" Link: https://developers.google.com/android/reference/com/google/android/gms/gcm/GcmNetworkManager ❓
  21. // in 'work’ // androidx.work.impl.Schedulers public static final String GCM_SCHEDULER

    = "androidx.work.impl.background.gcm.GcmScheduler"; // in 'work-gcm' @Nullable private static Scheduler tryCreateGcmBasedScheduler(@NonNull Context context) { try { Class<?> klass = Class.forName(GCM_SCHEDULER); Scheduler scheduler = (Scheduler) klass.getConstructor(Context.class).newInstance(context); Logger.get().debug(TAG, String.format("Created %s", GCM_SCHEDULER)); return scheduler; } catch (Throwable throwable) { Logger.get().debug(TAG, "Unable to create GCM Scheduler", throwable); return null; } } Java Reflection!!
  22. Dependency work-runtime work-runtime-ktx work-gcm work-lint work-rxjava2 work-testing room lifecycle android

    platform JobScheduler (API 23+) AlarmManager + BroadcastReceiver (API 14 ~ 22) GCM NetworkManager (API 14 ~ 22) Google Play
  23. Worker Progress class ProgressWorker(context: Context, parameters: WorkerParameters) : CoroutineWorker(context, parameters)

    { override suspend fun doWork(): Result { setProgress(workDataOf(PROGRESS to 25)) ... setProgress(workDataOf(PROGRESS to 50)) ... return Result.success() } companion object { const val PROGRESS = "Progress" } }
  24. Observing val request = OneTimeWorkRequestBuilder<ProgressWorker>().build() WorkManager.getInstance(this) .getWorkInfoByIdLiveData(request.id) .observe(this, Observer {

    workInfo: WorkInfo? -> if (workInfo != null) { val progress = workInfo.progress val value = progress.getInt(PROGRESS, 0) // Do something with progress information } }) Worker Progress
  25. class ForegroundWorker(context: Context, parameters: WorkerParameters) : CoroutineWorker(context, parameters) { private

    val notificationManager: NotificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) override suspend fun doWork(): Result { setForeground(createForegroundInfo()) // do long-running tasks return Result.success() } private fun createForegroundInfo(): ForegroundInfo { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { // Create a Notification channel } val notification = NotificationCompat.Builder(applicationContext, CHANNEL_ID) Foreground Worker
  26. override suspend fun doWork(): Result { setForeground(createForegroundInfo()) // do long-running

    tasks return Result.success() } private fun createForegroundInfo(): ForegroundInfo { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { // Create a Notification channel } val notification = NotificationCompat.Builder(applicationContext, CHANNEL_ID) .setSmallIcon(R.drawable.ic_launcher_foreground) .setContentTitle("ForegroundWorker") .setContentText("In Progress ...") .setOngoing(true) .build() return ForegroundInfo(0, notification) } } Foreground Worker Notification ID
  27. return Result.success() } private fun createForegroundInfo(): ForegroundInfo { if (Build.VERSION.SDK_INT

    >= Build.VERSION_CODES.O) { // Create a Notification channel } val notification = NotificationCompat.Builder(applicationContext, CHANNEL_ID) .setSmallIcon(R.drawable.ic_launcher_foreground) .setContentTitle("ForegroundWorker") .setContentText("In Progress ...") .setOngoing(true) .addAction(android.R.drawable.ic_delete, "Cancel", WorkManager.getInstance(applicationContext) .createCancelPendingIntent(id)) .build() return ForegroundInfo(0, notification) } } Foreground Worker with Cancel action Worker ID
  28. Summary • WorkManager = ⏰ AlarmManager + JobScheduler (+ GCM)

    • WorkManagerח force-stop غ؊ۄب, জਸ ੤प೯ೞݶ ੘সਸ ׮द ৘ডೠ׮. • GCM, Evernote, Firebase ١੄ Job ӝמਸ ࢎਊೞҊ ੓঻׮ݶ, ੉ઁח WorkManager۽ ߸҃೧ঠ ೡ ٸ! • 2.3ীࢲח Workerী Progress, Foreground ӝמب ઁҕೠ׮.