Random Musings on the M Developer Preview: the Ugly (Part One)
This post continues the series that I started on Monday (“the Good”) and Tuesday (“the Moderately Disconcerting”, or “the Bad” for short), reviewing what is in the M Developer Preview.
At this point, I should re-emphasize that I am covering the state of the initial M Developer Preview. Further previews are supposed to be released, and none of this is shipping production software yet. There may be changes to M along the way that affect some of what I am covering in these posts.
With that as prologue, today I am going to look at the first of the “ugly” bits: background processing.
Look, I like long-lived batteries as much as the next person. However, I also like software that works as advertised. The problem with what the M Developer Preview is doing with respect to background processing is that, in the service of extending battery life, it makes software unreliable with respect to documented functionality.
This isn’t new. Long-time followers of this blog may recall that I sounded the warning of the first occurrence of aggressive power-saving behavior, in the form of SONY’s STAMINA mode, over two years ago. Other manufacturers have made their own attempts at the same sort of thing, and so to the extent that Android M may standardize some of this behavior, I suppose that’s good.
However, what Android M does to our apps is:
More aggressive than what I have seen manufacturers do
Is generally hidden from the user
Is largely uncontrollable by the user
Is (almost) completely uncontrollable by developers
So, what’s changing?
First, we have “Doze mode”. If the device is not charging and not moving,
after about an hour, the device enters “Doze mode”. While dozing, pretty
much all background processing is disabled. No
SyncManager. If you somehow get control by other means, no
WakeLock and no Internet (with one noteworthy exception that I’ll
cover later). In short, your code won’t run.
The device will briefly exit “Doze mode” for about five minutes after an hour passes, then return to “Doze mode”. You get similar windows after another two hours, then after another four hours, then after another six hours, then I ran out of time for testing.
However, even during these brief non-dozing windows, Internet access may or may not be available. From what I have seen, my guess is that Android “releases the hounds” and runs pending background work immediately after exiting “Doze mode”, but before Internet access is necessarily available (or possibly I am seeing a switchover to WiFi). Regardless, the state of your world during these brief windows is in flux.
Second, we have “app standby”. If the device is not charging, and if your app has not been in the foreground for some time, but due to sticky services or something your process still exists, your app will move into a “standby” state. This appears to amount to a local “Doze mode”, affecting only your app, where your alarms and network and whatnot are unavailable. Your app may get the ability to run and access the Internet around once per day (whether it is also allowed time in the prison’s exercise area is undocumented).
The user is not made aware of any of this. The user is not told that certain apps were put in standby, or the device had been in “Doze mode” and so background work may have been blocked. In theory, the users do not need to know this, any more than they knew exactly what was going on when the screen turned off on their devices before. However, for quite a few apps, these changes in behavior will break functionality. Users may, in time, grok that if they keep their device on a charger, their apps will behave better, just as users would grok that on older versions of Android, if they keep their device on a charger, their battery would be charged. However, whereas the user is told about battery levels, they are not told about app degradations, and they are likely to assume that the app developer is at fault.
Now, once “Doze mode” and “app standby” exit, such as the user plugging the device into a charger, things return to normal. At least, sort of. Initially, there’s going to be a lot of contention, as all sorts of background work tries to get caught up. It may take a bit before Internet access stabilizes as well. This gets worse when the causes normal behavior is because the user presses the POWER button, as now there is a window in which the apps are scrambling to do what they were supposed to have been doing but do not have the results yet, and so the user will be exposed to stale information.
This wouldn’t be so bad if there were options for the user, or possibly for developers, to manage this.
The Android M Settings app has a place where users can indicate that they want to “ignore optimizations” for certain apps. However:
This is not easy to find, buried in an overflow menu item off the Apps screen
It doesn’t affect “Doze mode”, at least on the current M Developer Preview, and devices will be in “Doze mode” a fair bit
It is possible that “ignore optimizations” affects “app standby”. That
is what’s documented, and I have not tested that scenario yet, in part
because I have no good way of distinguishing “app standby” from
“Doze mode”, and for this sort of analysis I want real-world results,
not the results from issuing
adb commands to simulate real-world
On the developer side, in theory, the new
setExactAndAllowWhileIdle()) methods would allow
you to get control even during “Doze mode”. At least, that’s what
the documentation says. However:
My guess is that you may still not get Internet access or have wakelocks, and so these methods would mostly be for time-critical local-only processing, such as raising a calendar reminder
They don’t work, insofar as they do not affect “Doze mode” as documented
If they do work, we’re going to have lots of “special snowflake” developers who choose to use them just to get past Google’s nudges in the direction of better power management, even though they may not need the functionality
(it would certainly help if the OS and tools could make inexact
alarms more visible, so that
adb shell dumpsys alarm would tell us when the alarm actually will go off)
Google seems to have a “golden ticket” approach to getting around this problem: use GCM. A future edition of GCM will offer “high-priority messages”, which will be delivered to your app even in “Doze mode” or “app standby” states, and that you will briefly have network access and some time to do some work in response to those messages.
Even without “high-priority messages”, there is little doubt that push messages are more efficient, on the whole, than are polling-based techniques. I have no problem with the notion of pushing push.
However, GCM is proprietary, powered by Google Play Services, and route through Google’s servers.
This introduces problems:
While Google likes to think that the only devices that matter are ones that are part of the Google Play ecosystem, other manufacturers and channels (e.g., Amazon) would beg to differ. At best, those manufacturers would need to offer their own push solution and tie it into their forks of Android to provide similar effects with respect to “Doze mode” and “app standby”. And then developers have to fragment their apps further to support pushing through multiple systems, just to arrange to get their apps to run in the background.
There is no SLA for GCM, and so we have no reliability guarantees.
There are many apps for which passing communications through Google’s servers would be inappropriate or illegal for privacy and security reasons. While GCM appears to be encrypted over the air, GCM payloads are cleartext to Google and to whoever Google elects to share the data with.
Devices are sometimes in airplane mode, so using GCM as a generic “send a message to tickle the device” approach will not be reliable.
Beyond this, intentionally altering an open source operating system to steer people towards proprietary APIs reeks of Mafia-style behavior (“Gee, that’s a nice app you have there. Pity if something were to happen to it”). If push messaging on Android were an open, pluggable system, with multiple competing implementations, I would have no problem with Android giving benefits towards apps that use push. But third-party push engines (e.g., XMPP, WebSockets-based long polling) are going to be subject to “Doze mode” and “app standby” and cannot help apps get the data they need when the user asked for that data. Here, GCM’s exalted status, and Android’s dependence upon it, represents damage to be routed around.
Now, lots of apps will not really be harmed by these changes. And some apps that are “harmed” were written poorly in the first place and these changes will help wrestle them into better behavior. However, this is a very heavy club that Google is wielding, and many apps and their developers are going to get beaten by that club, even though their apps are fairly good citizens. This is even more the case if the apparent bugs in M turn out to be documentation bugs, not functionality bugs, and so calendars can no longer remind users on time in a reliable fashion.
Assuming nothing changes, developers are going to need to take yet more steps for dealing with unreliable background work. For example, make sure that you are keeping your user informed, by one means or another, if they come back into your UI and you are waiting for blocked background work to complete and so do not have the results that the user is expecting. Ideally, you would be doing this sort of thing already, to help cope with other things that mess up background work (e.g., “Force Stop”). However, background work has typically been reliable enough that developers could be forgiven for assuming that it worked in general. That’s no longer going to be the case.
Tomorrow, I will conclude this series of posts reviewing the M Developer Preview with the remaining “ugly” area: “auto backups for apps”.
Want an expert opinion on your Android app architecture decisions? Perhaps Mark Murphy can help!