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

Analysing_in_Depth_WorkManager.pdf

 Analysing_in_Depth_WorkManager.pdf

Deferrable Background work is extremely critical area for Android developers . With the onset of latest modifications in background processes/tasks in Oreo, Pie & even Q it has become very intricate & cumbersome dealing with background tasks within our app. Developers always struggle in choosing from different available alternatives like Alarm Manager, Firebase Job Dispatcher , Job Scheduler , GCM Network Manager which fall short in implementing background work in an effective manner .There was an absence of a clean & centralised solution for background processing which will work from Kitkat to the latest version of Android seamlessly.

In this talk, we will look into new Work Manager APIs introduced as new Android Architecture Components & part of Android Jetpack. We will understand where we should & should not use WorkManager. We will dive into the different mechanisms provided by WorkManager to monitor work via LiveData.
We will unravel how we constrain ,monitor and cancel work. Chained, Parallel, Periodic and Unique work will also be explained in detail with examples. Since the APIs are stable this will help us to analyse & use WorkManager effectively in our app for different situations.

Gaurav Bhatnagar

August 31, 2019
Tweet

More Decks by Gaurav Bhatnagar

Other Decks in Technology

Transcript

  1. How is battery utilized in Android Screen Off Screen On

    CPU Radios Screen e.g. wakelocks e.g. syncs, network activity Background Activity Source : http://bit.ly/30M4DBQ
  2. Design Principles Reduce Defer Coalesce Reduce all background activity If

    background activity must be performed, defer it to when device is on charger If it cannot be deferred, coalesce it with other background activity to reduce wakeup overhead Source : http://bit.ly/30M4DBQ
  3. Juxtaposition between Extended & Deep Doze Doze Extended Trigger Screen

    off, on battery, stationary Screen off , on battery Timing Successively increasing periods with maintenance windows Repeated N-minute periods with maintenance windows Restrictions No Network Access Jobs/Syncs deferred No Wakelocks Alarms deferred No GPS/WiFi Scans No Network Access Jobs/Syncs deferred Exit Motion, screen on, alarm clock or device charging Screen on or device charging Source : http://bit.ly/2L5MGc9
  4. Background Processing Limitations (Oreo) • To Improve RAM / Battery

    performance. • Restricting Background processes for different applications ◦ Services run freely in foreground ◦ Background services are stopped when idle. ◦ Bound services are not affected. • Limitations on Background Location Updates ◦ When in Background apps to receive location updates only a few times each hour. • Broadcast Restrictions (very short list of excluded implicit broadcasts)
  5. App Standby Buckets • Active • Working Set • Frequent

    • Rare • Never Source: https://goo.gl/9d4tys Application is in Active Bucket Has a notification from the app been clicked Is a sync adapter being used in foreground? Is there a foreground service running? Has an activity been launched? Is the application not often used? Is the application used regularly? Is the application used most days? Application is in Never Bucket Application is in Working Set Bucket Application is in Frequent Bucket Application is in Rare Bucket Yes Yes Yes Yes Yes Yes Yes No No No Application is not currently active No No No No
  6. Existing solutions for background tasks Async Task Job Scheduler Loaders

    Alarm Manager Sync Adapter Firebase JobDispatcher Service Executors/Threads Android Job (Evernote)
  7. Work Manager • WorkManager is the recommended solution for background

    execution, taking into account all OS background execution limits. • The work is guaranteed to run, even on app restart. • Work Manager is backward compatible to API 14. • Works with and without Google Play Services. • It should be used for all deferrable and guaranteed background work.
  8. Internal Mechanism of WorkManager Work Manager API 23+ Job scheduler

    Has play services? Alarm Manager and broadcast receiver GCM Network Manager Yes Yes No No
  9. Worker • Synchronous and runs on background thread by default

    • doWork() method is overridden and the task added in it. • doWork() is called exactly once per Worker instance. A new Worker is created if a unit of work needs to be rerun. • A Worker is given a maximum of ten minutes to finish its execution and return a ListenableWorker.Result. After this time has expired, the Worker will be signalled to stop.
  10. ListenableWorker • Work asynchronously • Instantiated at runtime by the

    WorkerFactory specified in the Configuration. • startWork() method performs all the task and runs on the main thread. • New instance of ListenableWorker is case of a new or restarted work. • Maximum of ten minutes to finish execution. • Return a ListenableWorker.Result
  11. Work Manager Request Types (2/2) 1. One Time Request a.

    It is used for non-repeating work which can be part of a chain b. It could have an initial delay c. It could be part of a chain or graph of work 2. Periodic Request a. Used for tasks that need to execute periodically but interval has to be more than 15 minutes which will be be in flex interval. b. It cannot be part of a chain or graph of work
  12. Demo // Creating an one time worker object val worker

    = OneTimeWorkRequest.from(CleanupWorker::class.java) Source : http://bit.ly/wmsamples
  13. Demo // Creating a periodic work request which will execute

    every 15 minutes val periodicWorkRequest = PeriodicWorkRequest.Builder(PeriodicWorker::class.java, 15, TimeUnit.MINUTES).build() Source : http://bit.ly/wmsamples
  14. Add Constraints // Create constraint for to specify battery level

    and network type val constraints = Constraints.Builder() .setRequiresBatteryNotLow(true) .setRequiredNetworkType(NetworkType.CONNECTED) .build() //Request object to report it to the server val builder = OneTimeWorkRequest.Builder(ReportToServerWorker::class.java) .setConstraints(constraints) .build() Source : http://bit.ly/wmsamples
  15. Start the task Before v2.1 // Enqueuing the work request

    to fire the Work request WorkManager.getInstance().enqueue(requestWorker) After v2.1 // Enqueuing the work request to fire the Work request WorkManager.getInstance(context).enqueue(requestWorker) Source: http://bit.ly/wmsamples
  16. Input/Output for the task // Input data for task while

    creating a Worker val data = workDataOf((Constants.DATA1 to 30), (Constants.DATA2 to “something”)) // Setting data as input for the worker OneTimeWorkRequest.Builder(SampleWorker::class.java).setInputData(data) // Reading data in Worker class val batteryStat = inputData.getString(Constants.DATA1) ?: "UNKNOWN" // Setting output from worker val data = Data.Builder().putString(Constants.DATA3, “something”).build() // Return the data back in Result Result.success(data) Source : http://bit.ly/wmsamples
  17. Delays and Retries Initial Delay val syncWorkRequest = OneTimeWorkRequestBuilder<SampleWorker>().setInitialDelay(5, TimeUnit.MINUTES)

    .build() Retries Result.retry() BackOff Policy : val uploadWorkRequest = OneTimeWorkRequestBuilder<UploadWorker>() .setBackoffCriteria(BackoffPolicy.LINEAR, OneTimeWorkRequest.MIN_BACKOFF_MILLIS, TimeUnit.MILLISECONDS) .build() Source : http://bit.ly/wmsamples
  18. Tagging of Tasks // Tags are used to identify or

    cancel the Work val task = OneTimeWorkRequest.Builder(SampleWorker::class.java) .addTag(Constants.DATA).build() Tags are meant to be used as categories, to identify/classify similar pieces of work that we might want to operate on as a bunch. Source : http://bit.ly/wmsamples
  19. Unique work Prevent duplicate tasks for the app. // For

    unique One-Time Work WorkManager.getInstance(this).enqueueUniqueWork(String, ExistingWorkPolicy, OneTimeWorkRequest) // For unique Periodic Work WorkManager.getInstance(this).enqueueUniquePeriodicWork(String, ExistingPeriodicWorkPolicy, PeriodicWorkRequest) ExistingWorkPolicy is used to denote the replacement of duplicate task Types - REPLACE/KEEP/APPEND ExistingPeriodicWorkPolicy is used to denote the replacement of duplicate task Types - REPLACE/KEEP Source : http://bit.ly/wmsamples
  20. Observe work status WorkManager.getInstance(this).getWorkInfoByIdLiveData(periodicWorkRequest.id) .observe(this, Observer { workInfo -> if

    (workInfo != null && workInfo.state == WorkInfo.State.SUCCEEDED) { // Do something } }) Source : http://bit.ly/wmsamples
  21. Retrieve WorkInfo WorkRequest Id WorkManager.getInstance(this).getWorkInfoById(UUID):ListenableFuture<List<WorkInfo>> WorkManager.getInstance(this).getWorkInfoByIdLiveData(UUID): LiveData<List<WorkInfo>> Tag WorkManager.getInstance(this).getWorkInfosByTag(String): ListenableFuture<List<WorkInfo>>

    WorkManager.getInstance(this).getWorkInfosByTagLiveData(String): LiveData<List<WorkInfo>> Unique Name WorkManager.getInstance(this).getWorkInfosForUniqueWork(String): ListenableFuture<List<WorkInfo>> WorkManager.getInstance(this).getWorkInfosForUniqueWorkLiveData(String): LiveData<List<WorkInfo>> Source : http://bit.ly/wmsamples
  22. Chaining Work Example Periodic Worker (Fired every 30 minutes) Get

    RemoteConfig Worker (gets remote configuration) BatteryStats Worker (reads device’s battery info) NetworkStats Worker (reads network info) ReportGenerator Worker (creates report and send to server) Initializes Remote Config Worker Gets info from network and set as Output data Read configurations from RemoteConfig Worker & output it’s data Read data from BatteryStats & NetworkStats worker Read configurations from RemoteConfig Worker & output it’s data One Time Worker Periodic Worker
  23. Chaining Work in Code Added in Periodic Worker // Request

    object to get the config from the server val continuation = workManager .beginUniqueWork( PeriodicTimeActivity.TAG_UNIQUE_WORK_NAME, ExistingWorkPolicy.REPLACE, OneTimeWorkRequest.from(GetConfigWorker::class.java)) // Chaining the GetConfigWorker with BatteryUsageWorker and NetworkUsageWorker .then(listOf(batteryStatBuilder.build(), netStatBuilder.build())) // Now, gathering analytics will happen in parallel .then(reportBuilder.build()) // Chaining the analytics request to server reporting Source : http://bit.ly/wmsamples
  24. Work Continuation Source : https://bit.ly/2NIA2BE val chain1 = WorkManager.getInstance() .beginWith(workA1)

    .then(workA2) val chain2 = WorkManager.getInstance() .beginWith(workB1) .then(workB2) val chain3 = WorkContinuation .combine(chain1, chain2) //Combines chain 1 and 2 .then(workC) chain3.enqueue()
  25. Cancelling work // Cancel work by work request ID WorkManager.getInstance(this).cancelWorkById(workRequest.id)

    // Cancel work by unique work tag WorkManager.getInstance(this).cancelAllWorkByTag(String) // Cancel work WorkManager.getInstance(this).cancelUniqueWork(String) Source : http://bit.ly/wmsamples
  26. Listenable Worker class ListenableWorkerExample(context: Context, workerParameters: WorkerParameters) : ListenableWorker(context, workerParameters)

    { override fun startWork(): ListenableFuture<Result> { return CallbackToFutureAdapter.getFuture { completer -> val callback = object : Callback { var successes = 0 override fun onFailure(call: Call, e: IOException) { completer.setException(e) } override fun onResponse(call: Call, response: Response) { completer.set(Result.success()) } } downloadAsynchronously("https://www.google.com", callback) } return@getFuture callback } } } Source : http://bit.ly/wmsamples
  27. Simple Worker class SampleWorker(context: Context, params: WorkerParameters) : Worker(context, params)

    { override fun doWork(): Result { // do some work // If successful, return // Success -> Result.success() // Failure -> Result.failure() // Retry -> Result.retry() return Result.retry() } } Source : http://bit.ly/wmsamples
  28. RxWorker class RxWorkerExample(val context: Context, val workerParameters: WorkerParameters) : RxWorker(context,

    workerParameters) { override fun createWork(): Single<Result> { // do some work // If successful, return // Success -> Result.success() // Failure -> Result.failure() // Retry -> Result.retry() return Single.create(Observable.range(0, 100) .toList().map { Result.success() }) } // Using computation thread, we can use others as well override fun getBackgroundScheduler(): Scheduler { return Schedulers.computation() } } Source : http://bit.ly/wmsamples
  29. Co-routine Worker class CoroutineWorkerExample (val context: Context, params: WorkerParameters) :

    CoroutineWorker (context, params) { override suspend fun doWork(): Result = coroutineScope { // Using IO thread, we can use others as well withContext(Dispatchers.IO) { return@withContext try { // do something Result.success() } catch (e: Exception) { Result.failure() } } } } Source : http://bit.ly/wmsamples
  30. Handle when Work is stopped • ListenableWorker's ListenableFuture is always

    cancelled when the work is expected to stop. • Accordingly you can use the cancellation listener also to receive the callback. • Override void onStopped() method for Listenable Worker. • By handling work stoppages ,we abide by the rules and facilitate clean up. • Return values or Future results will be ignored. • It is better to check for stoppages via boolean isStopped() method for ListenableWorker.
  31. Custom configuration Used for initializing WorkManager with custom configurations, like

    - • Factory for Worker and ListenableWorkers • Default executor for workers • Custom Logging • various Job Scheduler parameters Example class SampleApplication : Application(), Configuration.Provider { override fun getWorkManagerConfiguration() = Configuration.Builder() .setMinimumLoggingLevel(android.util.Log.INFO) .setExecutor(customThreadPoolExecutor) .build() } } Source : http://bit.ly/wmsamples
  32. Dependency Injection in Worker • WorkManager provides an abstract WorkerFactory

    class which our factory can use to instantiate workers. Source - https://bit.ly/2ZF8TBQ • Assisted Injection library provided by Square Source - http://bit.ly/wmsamples • Dagger2 Multi-binding Source - https://bit.ly/2L7Afwm
  33. Testing @RunWith(JUnit4::class) class RefreshMainDataWorkTest { private lateinit var context: Context

    @Before fun setup() { context = ApplicationProvider.getApplicationContext() } @Test fun testRefreshMainDataWork() { // Get the ListenableWorker val worker = TestListenableWorkerBuilder<SimpleWorker>(context).build() // Start the work synchronously val result = worker.startWork().get() assertThat(result, `is`(Result.success())) } } Source : http://bit.ly/wmsamples
  34. References • http://bit.ly/2ZuNRtT - Work Manager Series by Pietro Maggi

    • http://bit.ly/2L7ZNK1 - Work Manager with RxJava by Paulina Sadowska • http://bit.ly/2NLOkBk - Dagger 2 Setup with Work Manager by Tuan Kiet • http://bit.ly/2HwX3DS - Listenable Future Explained • http://bit.ly/2MKKUiL - Android Dev Summit Talk on Work Manager by Sumir Kataria & Rahul Ravikumar • http://bit.ly/30LlZPt - Workout Your tasks with WorkManager by Magada Miu • http://bit.ly/2MJKbyc - Android Developers Blog: Power series • http://bit.ly/2LkCdII - Schedule tasks with WorkManager | Android Developers • http://bit.ly/30M4DBQ - How does Android Optimize Battery Usage in New Releases? • http://bit.ly/2L5MGc9 - An ~extended~ Doze mode (Android Development Patterns S3 Ep 3) - YouTube • http://bit.ly/2ZC3n2P - Android Jetpack: easy background processing with WorkManager - YouTube