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.

44a168e6578c2cc83aaf54a38458ade9?s=128

Magda Miu

April 23, 2019
Tweet

Transcript

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

    Developer at Orange Android Google Developer Expert
  2. A User’s Nightmare...

  3. A User’s Nightmare...

  4. A User’s Nightmare...

  5. 53% Users who uninstalled a mobile app with crashes, freezes

    or errors
  6. Users who stopped to use a mobile app due to

    heavy battery usage 36%
  7. 1 4 3 2 Agenda Android Memory Model Background Processing

    Solutions WorkManager Summary
  8. 1 4 3 2 Agenda Android Memory Model Background Processing

    Solutions WorkManager Summary
  9. Android is for everyone

  10. Android is for everyone apps

  11. Android is for everyone apps devices

  12. Android is for everyone apps devices users

  13. Android is for everyone apps devices users

  14. Android is for everyone apps devices users

  15. 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)
  16. memory free

  17. memory free page reclaim (kswapd)

  18. memory free page reclaim (kswapd) low memory killer (lmk)

  19. 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)
  20. Android process list Native Init kswapd netd logd adb ...

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

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

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

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

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

    System system_server Persistent Foreground Perceptible Service Home Previous Cached
  26. Android battery optimizations

  27. Android battery optimizations Doze mode App standby

  28. Android battery optimizations Doze mode App standby Doze on the

    go
  29. Android battery optimizations Doze mode App standby Doze on the

    go Background service limitations
  30. Android battery optimizations Doze mode App standby Doze on the

    go Background service limitations App standby buckets
  31. 1 4 3 2 Agenda Android Memory Model Background Processing

    Solutions WorkManager Summary
  32. Exact Timing Deferrable Guaranteed Execution Best-Effort Credit: Android Jetpack: easy

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

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

    Coroutines RxJava JobScheduler JobDispatcher AlarmManager BroadcastReceivers WorkManager Foreground Service time/importance
  35. 4 3 Agenda WorkManager Summary 1 2 Android Memory Model

    Background Processing Solutions
  36. “WorkManager is a library for managing deferrable and guaranteed background

    work.”
  37. 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
  38. 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?
  39. Dependencies dependencies { def work_version = "2.2.0" implementation "androidx.work:work-runtime-ktx:$work_version" }

  40. WorkManager - main components Worker WorkRequest WorkManager

  41. Create Worker JobScheduler API 23+? AlarmManager + Broadcast Receiver NO

    YES Play Services? Firebase JobDispatcher NO YES Internal mechanism
  42. Worker

  43. Worker doWork() Result FAILURE SUCCESS RETRY

  44. class SyncWorker(c: Context, wp: WorkerParameters):Worker(c, wp) { override fun doWork():

    Result { loadData() return Result.success() } }
  45. class SyncWorker(c: Context, wp: WorkerParameters):Worker(c, wp) { override fun doWork():

    Result { loadData() return Result.success() } } Runs synchronously on a background thread
  46. WorkRequest

  47. Create Worker OneTimeWorkRequest Is it periodic ? PeriodicWorkRequest NO YES

  48. val syncOnlyOnce = OneTimeWorkRequestBuilder<SyncWorker>().build() val syncPeriodically = PeriodicWorkRequestBuilder<SyncWorker>(1, TimeUnit.HOURS).build()

  49. 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
  50. 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
  51. 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 )
  52. Constraints

  53. Set constraints setRequiredNetworkType(requiredNetworkType: NetworkType) setRequiresBatteryNotLow(requiresBatteryNotLow: Boolean) setRequiresCharging(requiresCharging: Boolean) setRequiresDeviceIdle(requiresDeviceIdle: Boolean)

    setRequiresStorageNotLow(requiresStorageNotLow: Boolean)
  54. 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
  55. val constraints = Constraints.Builder() .setRequiresCharging(true) .setRequiresStorageNotLow(true) .setRequiredNetworkType(NetworkType.CONNECTED) .build() val syncOnlyOnce

    = OneTimeWorkRequestBuilder<SyncDataWorker>() .setConstraints(constraints) .build()
  56. Input & Output Data

  57. 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)
  58. 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)
  59. Tagging Work

  60. 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()
  61. 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()
  62. Unique Work

  63. WorkManager.getInstance(context) .enqueueUniquePeriodicWork( Constants.UNIQUE_NAME, ExistingPeriodicWorkPolicy.KEEP, syncPeriodically ) Unique work sequence

  64. WorkManager.getInstance(context) .beginUniqueWork(Constants.UNIQUE_NAME, ExistingWorkPolicy.REPLACE, task1) .then(task2) .then(task3) .enqueue() Unique work chain

  65. 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
  66. Delays and Retries

  67. /* 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
  68. // 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()
  69. Work Status

  70. WorkInfo UUID State Data Tags BLOCKED CANCELLED ENQUEUED FAILED RUNNING

    SUCCEEDED
  71. WorkInfo Id WorkManager.getWorkInfoById(UUID) WorkManager.getWorkInfoByIdLiveData(UUID)

  72. WorkInfo Id WorkManager.getWorkInfoById(UUID) WorkManager.getWorkInfoByIdLiveData(UUID) Tag WorkManager.getWorkInfosByTag(String) WorkManager.getWorkInfosByTagLiveData(String)

  73. WorkInfo Unique Name WorkManager.getWorkInfosForUniqueWork(String) WorkManager.getWorkInfosForUniqueWorkLiveData(String) Id WorkManager.getWorkInfoById(UUID) WorkManager.getWorkInfoByIdLiveData(UUID) Tag WorkManager.getWorkInfosByTag(String)

    WorkManager.getWorkInfosByTagLiveData(String)
  74. LiveData & WorkManager

  75. WorkManager.getInstance().getWorkInfoByIdLiveData(syncOnlyOnce.id) .observe(this, Observer { workInfo -> if (workInfo != null

    && workInfo.state == State.SUCCEEDED) { displayMessage("Sync finished!") } }) Id
  76. WorkManager.getInstance().getWorkInfoByIdLiveData(syncOnlyOnce.id) .observe(this, Observer { workInfo -> if (workInfo != null

    && workInfo.state == State.SUCCEEDED) { displayMessage("Sync finished!") } }) LiveData<WorkInfo> Id
  77. 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
  78. 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
  79. 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
  80. 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
  81. Happy path - One Time Work BLOCKED RUNNING SUCCEEDED ENQUEUED

  82. “Unhappy” path - One Time Work FAILED CANCELLED CANCEL FAILURE

    RETRY BLOCKED RUNNING ENQUEUED SUCCEEDED SUCCESS
  83. “Unhappy” path - Periodic Work RUNNING ENQUEUED CANCELLED CANCEL SUCCESS

    RETRY, FAILURE
  84. WorkManager

  85. enqueue beginWith beginUniqueWork cancel WorkManager

  86. Chaining Tasks Task 1 Task 2 Task 3 Input Output

    Input Output Input Output Outputs are Inputs to child tasks
  87. WorkManager.getInstance(context) .beginWith(task1) .then(task2) .then(task3) .enqueue()

  88. WorkManager.getInstance(context) .beginWith(task1) .then(task2) .then(task3) .enqueue() WorkContinuation

  89. Task 1 Task 2 Task 3

  90. WorkManager.getInstance(context) .beginWith(listOf(task1, task2)) .then(task3) .enqueue() WorkContinuation Parallel execution

  91. Task 1 Task 2 Task 3 Task 4 Task 5

  92. 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()
  93. InputMerger +merge(): Data ArrayCreatingInputMerger OverwritingInputMerger

  94. Key Value user_id 23 name “Android Pie” ArrayCreatingInputMerger

  95. Key Value user_id 23 name “Android Pie” Key Value user_id

    37 name “Android 10” token “211-war-qw2-akjda” ArrayCreatingInputMerger
  96. 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”}
  97. Key Value user_id 23 name “Android Pie” Key Value user_id

    “37” name “Android 10” token “211-war-qw2-akjda” ArrayCreatingInputMerger
  98. 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”}
  99. Key Value user_id 23 name “Android Pie” points 350 OverwritingInputMerger

  100. Key Value user_id 23 name “Android Pie” points 350 Key

    Value user_id “37” name “Android 10” token “211-war-qw2-akjda” OverwritingInputMerger
  101. 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
  102. 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”
  103. val merge = OneTimeWorkRequestBuilder<SyncWorker>() .setInputMerger(ArrayCreatingInputMerger::class.java) .setConstraints(constraints) .build()

  104. // running work WorkManager.getInstance(context).enqueue(syncOnlyOnce) // cancelling work WorkManager.getInstance().cancelAllWork() WorkManager.getInstance().cancelAllWorkByTag(Constants.TAG_SYNC) WorkManager.getInstance().cancelUniqueWork(Constants.UNIQUE_NAME)

    WorkManager.getInstance().cancelWorkById(UUID.fromString(Constants .UNIQUE_UDID))
  105. // running work WorkManager.getInstance(context).enqueue(syncOnlyOnce) // cancelling work WorkManager.getInstance(context).cancelAllWork() WorkManager.getInstance().cancelAllWorkByTag(Constants.TAG_SYNC) WorkManager.getInstance().cancelUniqueWork(Constants.UNIQUE_NAME)

    WorkManager.getInstance().cancelWorkById(UUID.fromString(Constants .UNIQUE_UDID))
  106. // running work WorkManager.getInstance(context).enqueue(syncOnlyOnce) // cancelling work WorkManager.getInstance(context).cancelAllWork() WorkManager.getInstance(context).cancelAllWorkByTag(Constants.TAG_SYNC) WorkManager.getInstance().cancelUniqueWork(Constants.UNIQUE_NAME)

    WorkManager.getInstance().cancelWorkById(UUID.fromString(Constants .UNIQUE_UDID))
  107. // running work WorkManager.getInstance(context).enqueue(syncOnlyOnce) // cancelling work WorkManager.getInstance(context).cancelAllWork() WorkManager.getInstance(context).cancelAllWorkByTag(Constants.TAG_SYNC) WorkManager.getInstance(context).cancelUniqueWork(Constants.UNIQUE_NAME)

    WorkManager.getInstance().cancelWorkById(UUID.fromString(Constants .UNIQUE_UDID))
  108. // running work WorkManager.getInstance(context).enqueue(syncOnlyOnce) // cancelling work WorkManager.getInstance(context).cancelAllWork() WorkManager.getInstance(context).cancelAllWorkByTag(Constants.TAG_SYNC) WorkManager.getInstance(context).cancelUniqueWork(Constants.UNIQUE_NAME)

    WorkManager.getInstance(context).cancelWorkById(UUID.fromString(Constants .UNIQUE_UDID))
  109. Threading options 1. ListenableWorker 2. Worker 3. CoroutineWorker 4. RxWorker

  110. Threading in WorkManager Worker WorkRequest Executor Internal TaskExecutor Database enqueue

    execute Constraints met WorkerFactory Credit: Working with WorkManager Presentation Android Developer Summit 2018
  111. androidx.work.workdb

  112. WorkSpec table

  113. ListenableWorker +startWork(): ListenableFuture<ListenableWorker.Result> Worker CoroutineWorker RxWorker

  114. 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
  115. 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
  116. 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
  117. 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
  118. 4 Agenda Summary 1 3 2 Android Memory Model Background

    processing solutions WorkManager
  119. 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
  120. 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
  121. Thank you! Magda Miu @magdamiu Squad Lead Developer at Orange

    Android Google Developer Expert