Movatterモバイル変換


[0]ホーム

URL:


Skip to content
DEV Community
Log in Create account

DEV Community

Cover image for Updating widgets with Jetpack WorkManager
Thomas Künneth
Thomas Künneth

Posted on

     

Updating widgets with Jetpack WorkManager

Welcome to the second part ofUpdating widgets. In the first installment, we looked at the anatomy of Android's appwidgets. One important takeaway was, that, while widgets can request updates through their configuration file, the interval may not be smaller than 30 minutes. More frequent updates require a different approach. I somewhat vaguely said, that we could update widgets from activities and services. Still, what if a widget is not a companion but all the app contains? A Weather widget doesn't necessarily need a main activity. Neither does a Battery Meter. Which app component should trigger widget updates in such scenarios?

Let's find out.

Android has seen quite a few ways of allowing background jobs. For widget updates, we are particularly interested inpersistent work, which means, that the things to be done remain scheduled through app restarts and system reboots. Google recommendsJetpack WorkManager for persistent work.

Jetpack WorkManager and appwidgets

To use WorkManager, we first need to add an implementation dependency:

implementation("androidx.work:work-runtime-ktx:2.8.1")
Enter fullscreen modeExit fullscreen mode

The next step is to define aWorker. The actual work takes place insidedoWork().

privateconstvalWORK_NAME="update-battery-meter-widget"classBatteryMeterWorker(privatevalcontext:Context,workerParams:WorkerParameters,):Worker(context,workerParams){overridefundoWork():Result{context.getSharedPreferences(PREFS_NAME,Context.MODE_PRIVATE).edit().putLong(LAST_UPDATED,System.currentTimeMillis()).apply()context.updateXMLBatteryMeterWidget()returnResult.success()}}
Enter fullscreen modeExit fullscreen mode

The widget is updated by callingcontext.updateXMLBatteryMeterWidget(). This call won't take long. The same is true for accessing shared preferences. I will explain a little later why this is done.

Workers return aResult. I am taking it easy by always usingResult.success(). Depending on what a worker does, this may obviously be not always a clever thing to do. Now that we have defined our persistent work, let's think about how to start and stop it.

TheAppWidgetProvider class offers two related methods we can override:

  • onEnabled() is called when an appwidget is instantiated
  • onDisabled() will be invoked when the last widget instance is deleted
overridefunonEnabled(context:Context){super.onEnabled(context)enqueueUpdateXMLBatteryMeterWidgetRequest(context)}overridefunonDisabled(context:Context){super.onDisabled(context)cancelUpdateXMLBatteryWidgetRequest(context)}
Enter fullscreen modeExit fullscreen mode

Here is howenqueueUpdateXMLBatteryMeterWidgetRequest() andcancelUpdateXMLBatteryWidgetRequest() are implemented:

funenqueueUpdateXMLBatteryMeterWidgetRequest(context:Context){valrequest=PeriodicWorkRequestBuilder<BatteryMeterWorker>(MIN_PERIODIC_INTERVAL_MILLIS,TimeUnit.MILLISECONDS).build()WorkManager.getInstance(context).enqueueUniquePeriodicWork(WORK_NAME,ExistingPeriodicWorkPolicy.UPDATE,request)}funcancelUpdateXMLBatteryWidgetRequest(context:Context){WorkManager.getInstance(context).cancelUniqueWork(WORK_NAME)}
Enter fullscreen modeExit fullscreen mode

We are either creating (build()) and enqueuing (enqueueUniquePeriodicWork()), or cancelling (cancelUniqueWork()) arequest. As its name suggests,PeriodicWorkRequestBuilder allows us to define a work request that we want to be executed repeatedly. Please note, that the time between two runs must currently be at least 15 minutes (MIN_PERIODIC_INTERVAL_MILLIS).

This means, we get updates after half the time of what is possible using the appwidget configuration file (30 minutes). Please keep in mind, though, that the update won't necessarily appearexactly after 15 minutes.

Here's how my updated example looks like. You can find thesource code on GitHub. The app contains two versions of Battery Meter, a Glance widget and a version based onViews. For now we will be focusing on the latter one. I'll turn to Glance in a later part of this series.

The Battery Meter widget XML version

As you can see, the widget shows a date and a time. Why?

Recent Android versions limit what apps can do if they have not been in the foreground, that is, have beenactively used for some time. This begs an important question: will the widget still be updated?

The small banner shows when the worker was last executed. The widget picks up the value that is written into shared preferences insidedoWork().

Power optimizations

To see how the widget behaves, let's force the system into idle mode (Doze) by running the following command:

adb shell dumpsys deviceidle force-idle
Enter fullscreen modeExit fullscreen mode

As the worker runs every 15 minutes we should keep the app in Doze mode for at least 30 minutes. The widget won't be updated. After this period, we can exit idle mode by running these commands:

adb shell dumpsys deviceidle unforceadb shell dumpsys battery reset
Enter fullscreen modeExit fullscreen mode

The widget will be updated again.

While Doze mode is active, the worker will not run every 15 minutes. It may run at greater intervals, though. You can read more about Doze modehere. If the device is idle because it is lying on the desk with the screen turned off,not updating the widget is perfectly fine. After all, the user isn't looking at the screen and using the device.

There is, however, another (tongue in cheek) powerful power optimization feature calledApp Standby. Android checks several conditions to determine if an app is being actively used, for example

  • Was it recently launched by the user?
  • Does the app currently have a process in the foreground?
  • Has the app created a notification that is visible to the user?
  • Is the app an active device admin app?

Please refer toUnderstanding App Standby for further details.

Looking at the four bullet points above, none of them seem to apply to my sample, so it's very likely it will enter App Standby at some point. I believe this is a problem, because the user may be looking at a widget practically any time the home screen (launcher) is visible. To get an idea how the power optimizations will impact an app, please refer toPower management restrictions and have a look at table sectionApp Standby Buckets.

As mentioned inApp Standby Buckets,

App Standby Buckets helps the system prioritize apps' requests for resources based on how recently and how frequently the apps are used. Based on the app usage patterns, each app is placed in one of five priority buckets. The system limits the device resources available to each app based on which bucket the app is in.

The five buckets are:

  • Active
  • Working set
  • Frequent
  • Rare
  • Never

We can find out in which bucket an app currently is by invoking

adb shell am get-standby-bucket eu.thomaskuenneth.batterymeter
Enter fullscreen modeExit fullscreen mode

A macOS Terminal window

10 meansActive. Please refer toSTANDBY_BUCKET_ACTIVE and corresponding constants.

The documentation continues:

The app was used very recently, currently in use or likely to be used very soon. Standby bucket values that are ≤STANDBY_BUCKET_ACTIVE will not be throttled by the system while they are in this bucket.

If an app is in theWorking set bucket, it runs often but is not currently active. For this bucket, job execution is Limited to 10 minutes every 2 hours. Also, the app can schedule 10 alarms per hour.

According to the documentation, we can invoke

adb shell am set-standby-bucket eu.thomaskuenneth.batterymeter rare
Enter fullscreen modeExit fullscreen mode

to put an app into theRare bucket. However, during my experiments, issuingadb shell am get-standby-bucket immediately afterwords always returned10, wheresSTANDBY_BUCKET_RARE is40.

Now, where does this leave us?

Wrap up

Jetpack WorkManager is really easy to use. Scheduling and cancelling requests fits nicely with theAppWidgetProvider callbacks. Sadly, widgets are good candidates for App Standby if they don't have activities that are explicitly opened by the user. While during my testsBattery Meter was in theActive bucket, it is not obvious how long it stays this way.

So what do we do? Google notes that apps on the Doze allowlist are exempted from App Standby bucket-based restrictions. But that sounds like alast option. Are there other ones? Please stay tuned.

Top comments(1)

Subscribe
pic
Create template

Templates let you quickly answer FAQs or store snippets for re-use.

Dismiss
CollapseExpand
 
alexzaitsev profile image
Alex Zaitsev
  • Location
    Calgary, Canada
  • Joined

Thank you for this article. I'm curious, did you continue your research on the topic? It would be great to find answers to open questions about App Standby mode and others.

Are you sure you want to hide this comment? It will become hidden in your post, but will still be visible via the comment'spermalink.

For further actions, you may consider blocking this person and/orreporting abuse

Developer. Speaker. Listener. Loves writing. GDE Android. Confessing mobile computing addict ;-)
  • Location
    Germany
  • Work
    Senior Android Developer @snappmobile.io
  • Joined

More fromThomas Künneth

DEV Community

We're a place where coders share, stay up-to-date and grow their careers.

Log in Create account

[8]ページ先頭

©2009-2025 Movatter.jp