Fighting with Doze, App Standby and Audio Streaming
About a year ago, Google released Marshmallow, the version 6 of the Android operating system. Two of the most important under-the-hood features introduced in M were Doze and App Standby, with the purpose of extending device battery life.
We spent a lot of time developing our app according to ALL best practices on media playback so we tested our app. According to documentation, apps that implements a foreground service are exempted by App Standby, but there were nothing similar reported about Doze. Because Doze should only kick-in if the device is stationary since some time, with the screen turned off and unplugged, we thought this was not a common use case for an audio listening app.
A few months after the release, as soon Marshmallow started rolling out on a significative number of devices, we started receiving some “weird” complaints through our customer support that our app, like this one:
I have the Samsung Galaxy s6 and just updated to android 6.0.1.
I use Spreaker to listen to my favorite podcasts nearly every single day and have not had a problem with it until now.
Since the android update three days ago Spreaker randomly simply stops playing. I have no other notifications, alarms, etc…going off. No other apps are even running.
The rate at which we were receiving this kind of reports was not worrying, like a couple per months, but since we were totally confident about having done things the right way, and according to some users this was also happening with the phone in their pocket (not stationary) this was super weird to understand.
So I started again to experiments with Doze and App Standby, reading the documentation multiple times, doing manual tests with all the devices we own that could run Marshmallow and I found nothing unexpected. Obviously forcing the device to enter Doze was causing exactly the issue they reported, and the same applied for App Standby but:
- our app should be exempted by App Standby because of the foreground service
- those phones were not stationary, so Doze should not kick-in
This went on for a few months, until a few weeks ago, when I decided to do another round of testing about this stuffs, to see if I were able to figure out something new. After hours of testing and digging trough internet trying to shed some light on what was happening, I stumbled on this Google+ post and I started reading the comments, until I found this one by Dianne Hackborn:
Apps that have been running foreground services (with the associated notification) are not restricted by doze. This covers a large number of these kinds of use cases.
WTF?? I didn’t knew about this, it’s not in the documentation, and all the manual tests we made proven exactly the opposite! When our app was playing back audio, I turned off the screen, run the commands to force Doze:
$ adb shell dumpsys battery unplug
$ adb shell dumpsys deviceidle step
and the app immediately started reporting network issues, meaning Doze kicked-in, exactly like I was remembering since the last tests. Oh man, this was driving me nuts, let’s continue reading trough the comments on that post.
Let me explain it with a real example:
- the app is streaming audio from the network, you turn off the screen and force Doze = network access interrupted, the app stop streaming
- the app is streaming audio from the network, you send the activity in background by pressing the home button, turn off the screen and force Doze = network access still active, the app continue streaming
Luckily the dumpsys tool can provide some additional informations. Let’s see it action. When the phone is active:
$ adb shell dumpsys power | grep spreaker_playerPARTIAL_WAKE_LOCK 'spreaker_player' (uid=10309, pid=31020, ws=null) (elapsedTime=4524)
When the phone is forced into Doze but our Activity was in the foreground before turning the screen off:
$ adb shell dumpsys power | grep spreaker_playerPARTIAL_WAKE_LOCK 'spreaker_player' DISABLED (uid=10309, pid=31020, ws=null) (elapsedTime=20218)
When the phone is forced into Doze but our Activity was in the background before turning the screen off:
$ adb shell dumpsys power | grep spreaker_playerPARTIAL_WAKE_LOCK 'spreaker_player' (uid=10309, pid=31020, ws=null) (elapsedTime=64626)
Finally, this last experiment provided some useful informations. Here is a wrap-up of what I discovered:
- If an app implements a foreground service is exempted by both App Standby and Doze
- Because of a bug present in the 6.x release, if your app Activity was in the foreground when the phone screen was turned off, this invalidates the exemptions above, and your app is affected.
- This bug has been fixed in Nougat. It’s not clear if the fix will be backported to the 6.x branch, but at this point with Nougat already out I think it won’t.
There is no 100% safe workaround. The only possible one is to create a foreground service that runs in a different process with the only purpose of keeping an active wake lock. It’s not safe, and to me it’s very limited because then your app process will not have a foreground service running, its priority will be very low, it will likely be killed by the system, and because the audio playback is run by the other process it will stop no matter what.
Actually you can resolve it if you also move all the playback code in the second process, but then the only way you can communicate with it from the app (like for example to render the audio player UI) is trough the MediaSession. If your player UI is very basic this can be a viable approach, but in our case in order to render the UI correctly we need a lot more informations that the ones the MediaSession is able to carry around, so this is not at option.
Finding those informations took me months, so I thought it was nice to wrap them in a post, to save some time and help someone fighting with a similar issue.