Have you ever put your Android smartphone or tablet to one side, only to come back to it a few hours later and discover that it’s burnt through way more battery power than you were expecting?
By default, Android devices receive information updates constantly – emails, social media messages, notifications from apps, syncing with your Google account and so on. So even if you don’t interact with a device for an extended period of time, when you do eventually pick your smartphone or tablet up you’ll find that it’s bang up to date. However, there’s a point where this convenience isn’t worth the battery drain – no-one enjoys waking up in the morning to find their smartphone is now on 10% of battery because it spent the past 8 hours performing background work, while you were fast asleep.
Android 6.0 and higher attempts to strike a perfect balance between ensuring your smartphone or tablet is always relatively up to date (even if you haven’t interacted with it for a while) without burning through unnecessary amounts of battery.
This new feature is known as Doze mode, and in this article we’re going to look at how to update your apps, to make sure they place nicely with this new feature.
What is Doze Mode?
In the pre-Doze world, Android apps pretty much had free reign to perform whatever work they wanted in the background. While this was good for developers, who could create apps safe in the knowledge that said apps would be able to perform tasks whenever they needed (even if it meant waking an inactive smartphone or tablet) it wasn’t such good news for the end-user who found themselves constantly needing to recharge their device.
When a device is unplugged, stationary, and the screen turned off, Doze mode will eventually kick in and put the the device into a sleep state – hence the name Doze, as the device is essentially taking a power nap.
When a device is in Doze mode the system applies a range of battery-saving restrictions to all the apps on that device, as well as the device in general. For the duration of Doze mode, your app won’t be able to access the network, run sync adapters, fire standard alarms, run scheduled jobs, or acquire wakelocks. Think of Doze as an automatic flight mode – and we all know how much longer our battery lasts in flight mode!
As soon as a device no longer meets Doze’s list of criteria (for example the user moves the device or connects a charger) the system will exit Doze and all apps can resume normal activity.
If an app does try to perform tasks during Doze mode, the system will group all these tasks and batch execute them as soon as the device exits Doze, or during a scheduled maintenance window.
Imagine you put your Android smartphone or tablet down and don’t touch it at all for a few hours (it’s a stretch, I know). That device will eventually enter Doze mode, and from that point onwards it’s pretty much in a state of suspended animation. When you do finally pick the device up again, all of your apps are at least a few hours out of date – not exactly a great user experience!
To ensure that Doze’s battery savings don’t come at the cost of the user experience, Android exits Doze for regularly scheduled maintenance windows. A device will resume normal operations during these windows, giving your app a chance to run all its deferred activities. At the end of each maintenance window, the device will re-enter Doze. When a device first enters Doze, these maintenance windows occur pretty frequently, although they do occur less frequently the longer a device is in Doze mode.
And this was pretty much all your needed to know about Doze mode and its maintenance windows – until Android 7.0 came along and added the disclaimer that a device didn’t necessarily have to be stationary, in order to Doze.
Doze on the Go
When you think about it, an Android smartphone or tablet is rarely stationary. Your Android device probably spends a good chunk of its time in your pocket or bag, where it’s going to get jostled around so much that it’s unlikely to doze at all.
That’s why Android 7.0 introduced ‘Doze on the go,’ a new tier of Doze mode that applies a subset of the regular, ‘deep-Doze’ restrictions when the device is running on battery power and the screen is turned off, but Doze is still detecting movement. This lightweight version of Doze ensures that users can benefit from Doze’s battery saving features, even when they’re on the go (hence the name!)
If a device’s conditions change while it’s dozing, that device may move between these two versions of Doze. So, if a device in Doze-light mode remains stationary for an extended period of time, then that device may sink into deep-Doze. At the other end of the scale, if a device in deep-Doze mode detects movement, but the screen remains off and the device is still unplugged, then it’ll enter Doze-light mode, rather than exiting Doze completely.
The good news is that the recommended best practices are the same regardless of how deeply a device is dozing, so we can cover optimizing your app for both tiers of Doze, in one fell swoop.
Optimizing your Apps for Doze
By this point, you may be wondering how any app can provide a good user experience if it can’t perform essential background work whenever it needs to. While it’s true that Doze temporarily prevents applications from performing background activities, Doze is designed to have a minimal impact on your app’s performance.
Maintenance windows crop up pretty frequently when a device first dips into Doze mode, and only start to occur less frequently when the device has been dozing for a while (the assumption is that the user has either left their device somewhere, or they’ve left it unplugged overnight and are actually fast asleep).
If your app has to wait a little longer in order to perform deferred work, then this isn’t going to have a huge impact on the user experience – especially if the user is either nowhere near their device or it’s the middle of the night and they’re fast asleep.
However, there are some instances where you may need to make specific changes to your app, in order to provide a better Doze experience. In this section, I’ll look at two features that Doze is known to interfere with, and the workarounds you’ll need to use if your app includes these features. I’ll also share one final trick you can resort to, just in case Doze completely breaks your app and you need a get-out clause from Doze’s restrictions!
Receiving Messages in Doze Mode
If you’re developing a messaging app, or an app that has some form of messaging functionality, then chances are your users aren’t going to be too thrilled when your app doesn’t notify them about important messages straight away, just because their device happened to be dozing when these messages were sent.
To make sure your app never fails to notify the user about an incoming message, you can use either Google Cloud Messaging (GCM) or Firebase Cloud Messaging (FCM). Both of these services have the power to push messages to a dozing device, as long as you mark those messages as high-priority.
When your app is in Doze mode, standard AlarmManager alarms get deferred until the device enters its next maintenance window, or the device exits Doze completely.
GCM and FCM attempt to deliver high-priority messages immediately. If your app receives a high-priority message during Doze, the system will wake the device and grant your app temporary network services and partial wakelocks so it can notify the user (just resist the temptation to use these temporary privileges as an excuse to perform work that really could have waited until the next maintenance window).
While it’s easy to assume that everything your app does is important, waking a device from Doze mode will always have an impact on that device’s battery, so you should only use this technique for messages that are truly time-critical.
Unless you have a good reason for marking a message as high priority, you should assume that all your message have the default priority. Messages marked as “normal” won’t interrupt Doze mode, and will be delivered as soon as the device either enters a maintenance window or exits Doze completely.
Sounding the Alarm in Doze
Alarms are the other major feature you may need to adjust for Doze mode, so if you’re developing an alarm app, or an application that has some form of alarm functionality, then this section is for you!
When your app is in Doze mode, standard AlarmManager alarms get deferred until the device enters its next maintenance window, or the device exits Doze completely. This presents a problem, as it’s likely your users are going to ooh and aww over how little battery your app uses if they wind up getting into the office hours late because your app didn’t sound their morning alarm when it was supposed to.
To create alarms that are immune to Doze, you’ll need to use one of the following AlarmManager methods:
setExactAndAllowWhileIdle. Use this method to create an alarm that executes in Doze mode at exactly the time specified.
setAndAllowWhileIdle. Use this method if you need to be confident that an alarm will execute in Doze mode, but it’s not crucial that this alarm fires at exactly the time specified. This may sound strange (surely the whole purpose of an alarm is that it goes off at a particular time?) but there’s a few instances where you might want to use this method, rather than setExactAndAllowWhileIdle. For example maybe you’re building an app that alerts the user to bank holidays and other important events, or an app that presents the user with a ‘To Do’ list at the start of each day. In these scenarios, is it really crucial for the alarm to fire at exactly the time specified?
Note, setAndAllowWhileIdle and setExactAndAllowWhileIdle are only available in Lollipop and higher.
Bear in mind that if your app wakes a device then it’ll have an impact on that device’s battery, so you should only use these new methods if the benefits outweigh the potential battery hit of waking a dozing device.
If you suspect an alarm can wait until the device exits Doze mode or enters a maintenance window, then you should use the standard set() and setExact() instead.
Requesting access to the whitelist
Doze shouldn’t have a huge impact on most apps. Even if your app performs lots of background work then this work won’t be ignored, it’ll simply be deferred until the next maintenance window or until the device exits Doze (whichever comes first). And if you do need to make some explicit changes to your project in order to provide a better Doze experience, then most of the time this will be restricted to using GMC/FCM for time-sensitive messages, and using the new AlarmManager classes for important alarms.
However, occasionally Doze may break an app’s core functionality, for example if you’re developing a task automation app, then this app may hinge on being able to perform tasks when the user isn’t interacting with their device. Alternatively, you may be developing a messaging app that can’t use GCM or FCM for technical reasons.
If your app falls into either of these two very specific use cases, then you may need to request that the user adds your app to their ‘whitelist,’ at which point it’ll be exempt from Doze’s restrictions.
Users can build their own whitelist at any point, simply by opening their device’s ‘Settings’ app, followed by ‘Battery’ and ‘Battery Optimization,’ finding the app(s) they want to add to their whitelist, and then setting that app’s switch to ‘Off.’
However if Doze mode breaks your app, then you should take a more proactive approach and explicitly request that the user adds your app to their whitelist. You have two options:
Firing the ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS intent. This launches the device’s ‘Battery Optimization’ screen, ready for the user to (hopefully) add your app to their whitelist.
Adding the REQUEST_IGNORE_BATTERY_OPTIMIZATIONS permission to your project. This will trigger a system dialogue prompting the user to disable battery optimizations for your app, at which point your app will be exempt from Doze’s restrictions.
You can check whether your app has made it onto the user’s whitelist at any point, by calling the isIgnoringBatteryOptimizations method.
Testing your app in Doze mode
The final step is testing how your app behaves in Doze, including ensuring that your app makes the most out of the mode’s maintenance windows, and that your app recovers gracefully once the device exits Doze.
Rather than waiting for your device to slip into Doze mode naturally, you can cut to the chase and use adb commands to send a device into deep sleep in an instant.
The most effective way of testing your app’s Doze performance, is to use an Android Virtual Device (AVD) that’s running Android 6.0 or higher. You can then use the emulator tools to simulate different events that may occur while your app is subjected to Doze’s restrictions, for example if you’re developing a messaging app you should simulate your app receiving messages in Doze mode.
Make sure the app you want to test is installed on your AVD, then open a Terminal (Mac) or Command Prompt (Windows) and changing directory (‘cd’) so it’s pointing at your Android SDK’s ‘platform-tool’s folder, for example:
Make sure the app you want to test is running, then turn the AVD’s screen off and simulate the device entering Doze mode by running the following adb commands:
adb shell dumpsys battery unplug
This tells the AVD to assume it’s been unplugged from a power source.
adb shell dumpsys deviceidle step
This command takes the device through the various states it needs to sink through, before entering full-blown Doze. The Terminal will print the device’s state each step of the way, so keep re-entering this command until the Terminal/Command Prompt window returns the Idle state.
Once your app is in Doze mode, spend some time testing how your app handles Doze in general, being on the lookout for anything that isn’t working as you intended, or parts of your app that you could tweak in order to provide a better overall Doze experience.
In particular, make sure you simulate all the events that you suspect Doze might impact, for example if you want your SMS app to wake the device whenever it receives a new message, then simulate an incoming message and check that your app behaves as expected.
You should also check how your app handles the device leaving Doze mode; the easiest way is by turning the AVD’s screen on and observing your app’s behavior.
By default, adb’s deviceidle step command glosses over the light-Doze phase and sends the device directly into a deep Doze, but you’ll want to test that your app provides a good user experience in both Doze states.
To place an AVD into Doze-light mode, enter the following adb command:
$ adb shell dumpsys deviceidle step [light]
Do you have anymore tips for creating apps that play nicely with Android’s Doze mode? Share them in the comments below!