Android process death recovery is not often discussed, and information about it is often convoluted and hard to find. This talk will cover process death and common resolution strategies we can take to ensure our users have a seamless experience when it inevitably happens.
I’m Emmett Wilson. I work for Asynchrony Labs in St. Louis, Missouri. I’m @MathematicalFnk on Twitter.
What Is an Android Zombie?
An Android zombie is a process that has been killed by Android, and is reanimated because of a problem state. This often happens when your process is backgrounded, or your application crashes and is recreated.
The name Zombie is appropriate because whenever you tap on the icon to your app, Android brings the app back to the original screen you were on, regardless of how many Activities deep you are.
What Android Zombies Are Not
Android process death and recreation is not a configuration change. A configuration change can be a screen rotation; plugging in or disconnecting a hardware keyboard; walking into or out of a Bluetooth keyboard connection; walking into or out of an Android Auto connection; or changing the font size for accessibility reasons.
A process death and re-creation are not what happens when you check the “Don’t Keep Activities” setting.
It’s not swiping the app away from “Recents”. Doing this kills the application, and when we reopen it, we do not expect to come back into the most recent state we were in.
How Android Zombies Are Created
There are two ways in which Android Zombies Are Created:
Get more development news like this
- You background your app, and then the user goes and does something else. Android may decide to close your application based on time elapsed, but, when you reopen it, the application is subject to a recreation.
- When an application crashes in a deep stack. The specific Activity can be recreated upon return.
Static Singletons and Dagger
What happens when the app is not following a robust pattern? Suppose Android attempts to restore the state, but there is a null pointer, or there is invalid data. The app may continue to crash as a result.
Global memory data is one danger that can lead to Android zombies. Static singletons and injectable memory models from Dagger, for example, can cause issues.
Dagger allows you to inject repositories and presenters, and this is helpful for surviving configuration and rotation changes, but when the application is killed, those are no longer present.
Retained fragments can also cause issues with Zombies. People often suggest retaining fragments as a way to survive configuration change, but be mindful that retained fragments are torn down when the process is gone.
How to Test
How do we test our apps to make sure these types of crashes do not happen?
Compete for Resources
The first test is to background our app and run another resource intensive app such as Pokémon Go, or Netflix. This is a realistic test to see how your app will react when it’s recreated.
Samsung Font Style Trick
While testing an app we were working on, we were getting configuration crashes. We discovered that changing a font caused this crash. We were perplexed because this change is not a configuration change event. Then we noticed both the Settings application and our app flashing once, indicating a recreation of the Activities.
The benefits for testing an app this way is that there is a deterministic way to cause a process death, but this could only be done on Samsung devices.
Limit Background Processes
A better way to test is to use an option that is built into Developer Options - “Limit Background Processes”. Set this option to “No Background Processes” and leave. Because our application was in the background, the process was terminated.
This way of testing is deterministic, and it will shut down every time, so we can reliably test how our app responds to process death. This also works on all devices, including on emulators. A drawback to this method is accidentally leaving this option on, which can cause some unexpected issues, such as deep linking or push notifications not working.
To work around these issues, I wrote a quick utility that targets my application and terminates it. I put this up on GitHub as AppTerminator. It’s also on Google Play, and I encourage you to download it or fork it on GitHub.
Seamless Re-entry After Process Death
We know what Android process death is, how it creates zombie applications, what the dangers are, and how to test our app. Let us cover how our users can get a seamless re-entry experience when this happens.
Recover Seamlessly Via Disk Persistence
The first way to do this is just to recover by persisting onto disk all of our models and data that we need to populate the UI.
There are a couple of ways we could do this.
saveInstanceState, and Bundles. The drawback with this is that you have to write your own parcelable code. There is also limits as to how much data you can store.
Use Intents and Bundles, but only save the keys to download the data from a server. This gets around the potential issue of the data in question being too large, triggering an exception.
Using SQLite. This is likely the most robust option because you get a full database where you can rely on network caching, SD card storage, and you get upgrade pathways to persist data across installs. The drawback with SQLite is the amount of boilerplate involved when setting it up. Luckily, there is a new library called Room and things like Realm which reduces the amount of boilerplate code that has to be written.
Escape to a Safe Place
The second strategy I recommend is a fresh application start, then navigate the user back to a safe place.
The final strategy that I suggest is a hybrid approach of recovering from disk and navigating to a safe space. We bring the user to a safe place and attempt to load enough to bring the user back to where they were before the crash. If any of the data is corrupted, or there is not enough data, the user would still get a normal experience. This often results in less work because you do not require a full database to accomplish this approach.
The fourth strategy is, anything else that makes sense for your app and your user base! I’ve successfully used the first three options in different apps, but perhaps you have a different use case, different scenarios, and different things that make sense for your app.
By all means, go ahead and use those. It’s all about presenting our users with the best experience, not necessarily fitting to some paradigm that worked for somebody else.
About the content
This talk was delivered live in July 2017 at 360 AnDev. The video was recorded, produced, and transcribed by Realm, and is published here with the permission of the conference organizers.