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

Workout your tasks with WorkManager

Workout your tasks with WorkManager

Until now, in order to perform background work in Android, developers had to choose between multiple execution options. At Google I/O 2018, the Android team launched WorkManager as part of the Android Jetpack. This library provides a simple and clean interface to specify deferrable, asynchronous tasks and when they should run.
The presentation will include an overview about the existing background solutions, what’s happening behind the scenes and why and when we should use WorkManager. Also, we will discover details about the architecture of the WorkManager library and about the main components(Worker, WorkRequest, WorkManager, WorkInfo) of it. Finally, we will highlight how to use WorkManager for scenarios like chained sequences of tasks that run in a specified order, unique named sequences, tasks that pass and return values and how to apply constraints in order to decide when to run the task.

Magda Miu

April 23, 2019
Tweet

More Decks by Magda Miu

Other Decks in Programming

Transcript

  1. Workout your tasks with WorkManager Magda Miu @magdamiu Squad Lead

    Developer at Orange Android Google Developer Expert
  2. Physical memory on device Used pages Memory actively being used

    Cached pages Memory backed by device storage. Can be reclaimed if needed. Free pages Memory not currently being used for anything. Source: Understanding Android memory usage (Google I/O '18)
  3. Android process list Native Init kswapd netd logd adb ...

    System system_server Persistent Foreground Perceptible Service Home Previous Cached Source: Understanding Android memory usage (Google I/O '18)
  4. Android process list Native Init kswapd netd logd adb ...

    System system_server Persistent Foreground Perceptible Service Home Previous Cached
  5. Android process list Native Init kswapd netd logd adb ...

    System system_server Persistent Foreground Perceptible Service Home Previous Cached
  6. Android process list Native Init kswapd netd logd adb ...

    System system_server Persistent Foreground Perceptible Service Home Previous Cached
  7. Android process list Native Init kswapd netd logd adb ...

    System system_server Persistent Foreground Perceptible Service Home Previous Cached
  8. Android process list Native Init kswapd netd logd adb ...

    System system_server Persistent Foreground Perceptible Service Home Previous Cached
  9. Android process list Native Init kswapd netd logd adb ...

    System system_server Persistent Foreground Perceptible Service Home Previous Cached
  10. Android battery optimizations Doze mode App standby Doze on the

    go Background service limitations App standby buckets
  11. Exact Timing Deferrable Guaranteed Execution Best-Effort Credit: Android Jetpack: easy

    background processing with WorkManager (Google I/O '18) time/importance
  12. Exact Timing Deferrable Guaranteed Execution Best-Effort ThreadPool Coroutines RxJava ThreadPool

    Coroutines RxJava JobScheduler JobDispatcher AlarmManager BroadcastReceivers Foreground Service time/importance
  13. Exact Timing Deferrable Guaranteed Execution Best-Effort ThreadPool Coroutines RxJava ThreadPool

    Coroutines RxJava JobScheduler JobDispatcher AlarmManager BroadcastReceivers WorkManager Foreground Service time/importance
  14. Why to use WorkManager? • Backward compatibility with different OS

    versions (API level 14+) • Follows system health best practices • Supports one-off and periodic tasks • Supports chained tasks with input/output • Defines constraints on when the task runs • Guarantees task execution, even if the app or device restarts
  15. Fetching data periodically Uploading images/files to a server Syncing data

    between app and server Sending logs to a server Executing expensive operations on data When to use WorkManager?
  16. Create Worker JobScheduler API 23+? AlarmManager + Broadcast Receiver NO

    YES Play Services? Firebase JobDispatcher NO YES Internal mechanism
  17. class SyncWorker(c: Context, wp: WorkerParameters):Worker(c, wp) { override fun doWork():

    Result { loadData() return Result.success() } } Runs synchronously on a background thread
  18. OneTimeWorkRequest • It is used for non-repeating work • It

    could have an initial delay • It could be part of a chain or graph of work
  19. PeriodicWorkRequest • Used for tasks that need to execute periodically

    • It could have an initial delay • The minimum repeat interval that can be defined is 15 minutes (same as the JobScheduler API) • It cannot be part of a chain or graph of work • The execution may be delayed because WorkManager is subject to OS battery optimizations, such as doze mode
  20. val periodicRefreshRequest = PeriodicWorkRequest.Builder( SyncWorker::class.java, // the worker class 30,

    // repeating interval TimeUnit.Minutes, 15, // flex interval - worker will run somehow within this period of time, but at the end of repeating interval TimeUnit.MINUTES )
  21. NetworkType CONNECTED // Any working network connection METERED // A

    metered network connection NOT_REQUIRED // A network is not required for this work. NOT_ROAMING // A non-roaming network connection UNMETERED // An unmetered network connection
  22. putBoolean / getBoolean putBooleanArray / getBooleanArray putDouble / getDouble putDoubleArray

    / getDoubleArray putFloat / getFloat putFloatArray / getFloatArray putInt / getInt putIntArray / getIntArray putLong / getLong putLongArray / getLongArray putString / getString putStringArray / getStringArray putByte / getByte putByteArray / getByteArray Data and Data.Builder* (key-value pairs)
  23. val userIdData = Data.Builder() .putInt(DATA_USER_ID, userId) .build() val syncOnlyOnce =

    OneTimeWorkRequestBuilder<SyncWorker>() .setInputData(userIdData) .build() val userIdInput = inputData.getInt(Constants.DATA_USER_ID, 0) // ktx val outputData = workDataOf(Constants.DATA_SENT to isDataSent)
  24. val userIdData = Data.Builder() .putInt(DATA_USER_ID, userId) .build() val userIdInput =

    inputData.getInt(Constants.DATA_USER_ID, 0) val syncOnlyOnce = OneTimeWorkRequestBuilder<SyncWorker>() .setInputData(userIdData) .addTag(Constants.WORKER_SYNC) .build()
  25. val userIdData = Data.Builder() .putInt(DATA_USER_ID, userId) .build() val userIdInput =

    inputData.getInt(Constants.DATA_USER_ID, 0) val syncOnlyOnce = OneTimeWorkRequestBuilder<SyncWorker>() .setInputData(userIdData) .addTag(Constants.WORKER_SYNC) .addTag(Constants.ONE_SYNC) .build()
  26. Existing Work Policy enums • KEEP - keeps the existing

    unfinished WorkRequest. Enqueues it if one does not already exist. • REPLACE - always replace the WorkRequest. Cancels and deletes the old one, if it exists. • APPEND - appends work to an existing chain or create a new chain. KEEP + REPLACE + APPEND = ExistingWorkPolicy KEEP + REPLACE = ExistingPeriodicWorkPolicy
  27. /* Used to indicate that WorkManager should increase the backoff

    time exponentially */ EXPONENTIAL /* Used to indicate that WorkManager should increase the backoff time linearly */ LINEAR BackoffPolicy enum
  28. // add initial delay for OneTimeWorkRequest val syncOnlyOnce = OneTimeWorkRequestBuilder<SyncWorker>()

    .setInitialDelay(15, TimeUnit.MINUTES) .build() // backoff delay and policy val syncOnlyOnce = OneTimeWorkRequestBuilder<SyncWorker>() .setBackoffCriteria(BackoffPolicy.LINEAR, OneTimeWorkRequest.MIN_BACKOFF_MILLIS, TimeUnit.MICROSECONDS) .build()
  29. WorkManager.getInstance().getWorkInfoByIdLiveData(syncOnlyOnce.id) .observe(this, Observer { workInfo -> if (workInfo != null

    && workInfo.state == State.SUCCEEDED) { displayMessage("Sync finished!") } }) LiveData<WorkInfo> Id
  30. WorkManager.getInstance() .getWorkInfosByTagLiveData(Constants.TAG_SYNC) .observe(this, Observer<List<WorkStatus>> { workStatusList -> val currentWorkStatus =

    workStatusList ?.getOrNull(0) if (currentWorkStatus ?.state ?.isFinished == true) { displayMessage("Sync finished!") } }) Tag
  31. WorkManager.getInstance() .getWorkInfosByTagLiveData(Constants.TAG_SYNC) .observe(this, Observer<List<WorkStatus>> { workStatusList -> val currentWorkStatus =

    workStatusList ?.getOrNull(0) if (currentWorkStatus ?.state ?.isFinished == true) { displayMessage("Sync finished!") } }) LiveData<List<WorkInfo>> Tag
  32. WorkManager.getInstance() .getWorkInfosForUniqueWorkLiveData(Constants.UNIQUE_NAME) .observe(this, Observer<List<WorkStatus>> { workStatusList -> val currentWorkStatus =

    workStatusList ?.getOrNull(0) if (currentWorkStatus ?.state ?.isFinished == true) { displayMessage("Sync finished!") } }) Unique name
  33. WorkManager.getInstance() .getWorkInfosForUniqueWorkLiveData(Constants.UNIQUE_NAME) .observe(this, Observer<List<WorkStatus>> { workStatusList -> val currentWorkStatus =

    workStatusList ?.getOrNull(0) if (currentWorkStatus ?.state ?.isFinished == true) { displayMessage("Sync finished!") } }) LiveData<List<WorkInfo>> Unique name
  34. “Unhappy” path - One Time Work FAILED CANCELLED CANCEL FAILURE

    RETRY BLOCKED RUNNING ENQUEUED SUCCEEDED SUCCESS
  35. Chaining Tasks Task 1 Task 2 Task 3 Input Output

    Input Output Input Output Outputs are Inputs to child tasks
  36. val leftChain = WorkManager.getInstance(context) .beginWith(task1) .then(task2) val rightChain = WorkManager.getInstance(context)

    .beginWith(task3) .then(task4) val resultChain = WorkContinuation .combine(listOf(leftChain, rightChain)) .then(task5) resultChain.enqueue()
  37. Key Value user_id 23 name “Android Pie” Key Value user_id

    37 name “Android 10” token “211-war-qw2-akjda” ArrayCreatingInputMerger
  38. Key Value user_id 23 name “Android Pie” Key Value user_id

    37 name “Android 10” token “211-war-qw2-akjda” ArrayCreatingInputMerger Key Value user_id {23, 37} name {“Android Pie”, “Android 10”} token {“211-war-qw2-akjda”}
  39. Key Value user_id 23 name “Android Pie” Key Value user_id

    “37” name “Android 10” token “211-war-qw2-akjda” ArrayCreatingInputMerger
  40. Key Value user_id 23 name “Android Pie” Key Value user_id

    “37” name “Android 10” token “211-war-qw2-akjda” ArrayCreatingInputMerger Key Value user_id IllegalArgumentException name {“Android Pie”, “Android 10”} token {“211-war-qw2-akjda”}
  41. Key Value user_id 23 name “Android Pie” points 350 Key

    Value user_id “37” name “Android 10” token “211-war-qw2-akjda” OverwritingInputMerger
  42. Key Value user_id 23 name “Android Pie” points 350 Key

    Value user_id “37” name “Android 10” token “211-war-qw2-akjda” OverwritingInputMerger Key Value user_id 23 name “Android Pie” points 350
  43. Key Value user_id 23 name “Android Pie” points 350 Key

    Value user_id “37” name “Android 10” token “211-war-qw2-akjda” OverwritingInputMerger Key Value user_id “37” name “Android 10” points 350 token “211-war-qw2-akjda”
  44. Threading in WorkManager Worker WorkRequest Executor Internal TaskExecutor Database enqueue

    execute Constraints met WorkerFactory Credit: Working with WorkManager Presentation Android Developer Summit 2018
  45. ListenableWorker Overview • A ListenableWorker only signals when the work

    should start and stop • The start work signal is invoked on the main thread, so we go to a background thread of our choice manually • A ListenableFuture is a lightweight interface: it is a Future that provides functionality for attaching listeners and propagating exceptions Stop work • It is always cancelled when the work is expected to stop. Use a CallbackToFutureAdapter to add a cancellation listener
  46. Worker Overview • Worker.doWork() is called on a background thread,

    synchronously • The background thread comes from the Executor specified in WorkManager's Configuration, but it could also be customised Stop work • Worker.onStopped() is called. This method could be overridden or we could call Worker.isStopped() to checkpoint the code and free up resources when necessary
  47. CoroutineWorker Overview • For Kotlin users, WorkManager provides first-class support

    for coroutines • Instead of extending Worker, we should extend CoroutineWorker • CoroutineWorker.doWork() is a suspending function • The code runs on Dispatchers.Default, not on Executor (customisation by using CoroutineContext) Stop work • CoroutineWorkers handle stoppages automatically by cancelling the coroutine and propagating the cancellation signals
  48. RxWorker Overview • For RxJava2 users, WorkManager provides interoperability •

    Instead of extending Worker, we should extend RxWorker • RxWorker.createWork() method returns a Single<Result> indicating the Result of the execution, and it is called on the main thread, but the return value is subscribed on a background thread by default. Override RxWorker.getBackgroundScheduler() to change the subscribing thread. Stop work • Done by default
  49. WorkManager - Recap • WorkManager is a wrapper for the

    existing background processing solutions • Create one time or periodic work requests • Identify our tasks by using ids, tags and unique names • Add constraint, delay and retry policy • Use input/output data and merge them • Create chains of tasks • Use the available threading options
  50. Resources • Official Documentation and Official Reference • Working with

    WorkManager (Android Dev Summit ’18) • Android Jetpack: easy background processing with WorkManager (Google I/O '18) • https://medium.com/@magdamiu • Icons from https://www.flaticon.com