Watts App Part 2 — Fixing the update frequency
In Part 1 I wrote about my inspiration and my first version of a simple little app that would show me the battery level of my phone on my Android Wear watch, while the phone was charging. Some problems became apparent once I’d finished. The main one was that the
JobScheduler on Nougat enforces a minimum of fifteen-minutes between running jobs. In a world where quick-charging can give you five hours worth of battery life in five minutes, restricting battery charge updates to fifteen minutes makes the app all but useless.
Let’s try the AlarmManager
JobScheduler proved itself to be overkill for my simple use case. Its optimisations to the running of repeated background tasks made it unsuitable for short-term frequent updates. Maybe this was because the job I wanted to schedule is much more basic than the type of operations it’s intended for (network calls etc).
I decided to try out AlarmManager instead. This has much less aggressive batching of scheduled tasks, and uses a
BroadcastReceiver rather than a
Service. The documentation contains this interesting note:
The Alarm Manager is intended for cases where you want to have your application code run at a specific time, even if your application is not currently running. For normal timing operations (ticks, timeouts, etc) it is easier and much more efficient to use
The first sentence sounds about right — I want my application code to run at a specific time, even if it’s not running. The second part makes me wonder if
Handler would work better for me, or if the documentation is referring to things like updating something on-screen during the lifetime of an
Activity. I’ll have to try that approach sometime. For the moment, I decided to go with
AlarmManager. It’s an API I’ve not used before, and I was curious about it.
AlarmManager — Implementation
The first step was to replace my old
JobService with a
BroadcastReceiver. The receiver has to be registered in the
AndroidManifest, but doesn’t require any intent filters, so the declaration is simple:
The reason it doesn’t need any intent filters is because we’ll creating a
PendingIntent which calls the receiver directly, like so:
val alarmIntent = Intent(context, AlarmReceiver::class.java)
val pendingIntent = PendingIntent.getBroadcast(context, JOB_ID, alarmIntent, 0)
AlarmReceiver class itself looks almost exactly like the old
PowerService class. It gets the
NotificationManager system service, gets the latest battery information, and then fires a notification:
PowerConnectionReceiver, I replaced the scheduling of a
JobScheduler with the scheduling of my
PendingIntent on the
AlarmManager system service. I’ve supplied a value of zero for the
triggerAtMillis parameter and a value of one minute (my
JOB_INTERVAL constant) for
intervalMillis, telling the
AlarmManager to fire the first broadcast as soon as the charging event is detected, and repeat at one-minute intervals. Now it looks like this:
Side note: The
batteryStatus variable in this snippet is the
Intent which holds the latest battery info, but I’ve added a couple of extension functions to it so I can perform operations directly on the object, rather than passing it to another method as a parameter. In case you haven’t seen this before, it’s a lovely (and very powerful) feature of the Kotlin programming language. Try it out!
AlarmManager kind of threatens to be a harsh mistress, but isn’t really
As per the documentation:
Note: Beginning with API 19 (
KITKAT) alarm delivery is inexact: the OS will shift alarms in order to minimize wakeups and battery use… Applications whose
targetSdkVersionis earlier than API 19 will continue to see the previous behavior in which all alarms are delivered exactly when requested.
This sounds familiar! There’s definitely some overlap with
JobScheduler's batching optimisations, and these go back as far as Kitkat! But
AlarmManager seems to be a lot more relaxed than
JobScheduler. It might be “inexact”, but it’s not “a minimum of fifteen minutes”. I haven’t run any actual timing tests, but it’s at least been in the right ballpark. The shifting is likely to be more visible if working with shorter intervals.
Once I’d swapped out the
AlarmManager (and remembered to register my
AlarmReceiver in the Manifest, which was another brain-far moment), it worked like a charm! Again, most of the caveats and drawbacks of an
AlarmManager strategy are not applicable to my use case, since my app only runs when the device is charging anyway.
But there’s still more room for improvement. The notification shows on both devices, so maybe I’ll create a Wear module so I can show the notification only on the watch. I realised I’d also like this functionality in reverse — a way to tell if my watch is charged yet, from my phone.