Android devs: You’ve handled activity recreation. What about process recreation?

Wesley Moy
7 min readMay 30, 2017

--

tl;dr: First handle activity recreation. Done? Good. Now handle process death. Simulate process death in your own apps: https://github.com/wesleym/process-spam.

At Google I/O 2017, Google announced Android Architecture Components. One of the highlights of the library is ViewModel, a way to store state across activity recreation. Whether you use ViewModel or another way to handle activity recreation, you should probably know whether you’ve handled process death.

Problem 1: activity recreation

In an Android app, activities get recreated all the time. In particular, an activity is recreated anytime a configuration change happens. A user rotating their device is a configuration change, but configuration changes are increasingly common on newer versions of Android. Android Nougat introduced split-screen mode, and putting an app into split-screen or resizing it also results in a configuration change. Even making an Android app work on Chromebooks involves configuration changes. And, by default, all of these configuration changes result in activities being destroyed and recreated.

While it’s possible to store state in a bundle, that’s not always enough. Some state is difficult to serialize, and sometimes real Java objects need to be kept alive. Traditionally, this problem was addressed in one of two ways:

This is where the newly announced ViewModel comes in. ViewModel is a place to store state or stateful objects with a much simpler API than that of fragments.

This is also where the shortcomings of all of these approaches emerges. They are all effective ways of dealing with activity recreation. They don’t handle process death.

Problem 2: process death

Once no activity of an app is in the foreground, the process backing the app is considered a cached process. (For simplicity, ignore services for the moment.) At this point, the process becomes a strong candidate for being killed.

Then, once an app’s process is killed, every object in memory disappears. Sure, this includes any fields in any activity classes. But it also includes fragments — even the ones set to retain their instance. And it also includes ViewModels and any other global stores of data.

The problem is the following scenario: if the user returns to your app through the Recents screen and process death has occurred, your activity is recreated but no fragment or global state formerly in memory is available anymore. If your activity depended on some state stored in a fragment or ViewModel, it better be able to carry on without it or gracefully recreate it, or else your app will exhibit bad behaviour including crashes.

Picture a contacts app that has a list of contacts in one activity and a detailed view of a contact in another activity. Tapping on the contact’s entry in the list activity shows the detail activity. A single network call when the app loads populates a shared data store. For an example of this, see https://github.com/wesleym/contacts-example.

(Why couldn’t you just serialize the model object into the bundle? Say the detail view lets you edit the contact and you want every other part of the app to show the updated contacted when it’s displayed. Or the contact object holds a list of change operations and whether they’ve been persisted to a server. Any number of reasons are possible — the point is that the object is in memory.)

Dealing with process death

There are a number of ways to handle process death. These range from quick fixes to deeper questions of app structure:

  1. The app can prevent process death in the first place. If your app is the middle of something where the app process really really shouldn’t die, you can use a foreground service. Even if the user leaves your app, Android will try to kill other cached processes before moving on to those of foreground services. The downside of doing this is that the user may not agree with the importance of your app and see the notification your app uses to signal being a foreground service as being a nuisance.
  2. The app can abandon whatever it was doing that needed the now-missing information. In the example with the contacts, the detail view can check for the presence of the contact model in memory and dismiss itself. The downside of doing this is that the user loses their place in the app.
  3. The app can treat missing data in memory as a cache miss and fall back to either persistent storage or the network to retrieve that data. The downside of doing this is that living objects with state or behaviour not reflected in persistent storage or from the network is lost. This is especially the case in flows where the user is progressively filling out information in steps and the partial results haven’t been stored anywhere.
  4. The app can store enough information about living objects in memory in persistent storage (files, SharedPreferences, SQLite) so that these objects can be recreated in case they’re missing from memory. The downside of this is the difficulty in keeping the in memory object and the persistent storage in sync. After all, the whole point of keeping an in-memory living object around is that serialization into a bundle or other persistent storage is hard.

In the end, this is a tradeoff that will depend on the specific problem the app is trying to solve. In many cases where an app just displays information from the source of truth over the network, case 3 can be very effective. This also prepares the activity for situations where the app is launched from that activity, such as from a deep link or a notification.

Visualizing process death

Knowing the problem and how to solve it is all good and well, but it’s also helpful to be able to see the problem in practice. While there’s an option in the Android developer settings on most phones to destroy activities whenever the user leaves them (“Don’t keep activities”), there isn’t something similar for killing processes. Furthermore, killing the process for an app by swiping it out of the recents view takes away the entry point into this problem entirely.

Using an older phone and cycling between apps is one way to do this, but the results tend to be inconsistent and time-consuming. Instead, it would be nice to force Android to kill an app’s process in a way similar to what happens when too much else is happening and Android itself kills the app’s cached process.

To do this, I’ve written up a simple app called Process Spam (source). This app launches a hundred activities in sequence, each in its own process. This crowds out other processes very quickly on even my Nexus 5X and on the emulator. Even the home screen often needs to relaunch after a few seconds of running Process Spam. By returning to your app after running this app for a few seconds, you simulate Android recreating your activity with a new process. Try it out and see if it works for you. The sample contacts app above should crash on the detail screen (with a NullPointerException) if you try using Process Spam.

Note: because this app launches activities as quickly as possible across many processes, your phone will have terrible battery life and be very laggy for as long as you run the app. On my Nexus 5X, I’ve never needed to run the app more than about twenty seconds before returning to another app to force a process recreation.

Other things I’ve tried

I’ve had mixed luck with a StackOverflow answer recommending an adb command to cause a similar effect in a quicker and cleaner way. Unfortunately, I haven’t had much luck with it on my physical test device running Android O Beta 1 or on my newer Android emulators running Nougat and the O Beta. Your mileage may vary. Try it out on my sample contacts app — if you get a crash on the detail view, it’s working!

Edit: Some great discussion has been happening in the thread in /r/androiddev. /u/Boza_s6 on Reddit recommends going into developer settings and turning background processes to 0, and Vasiliy in the comments seconds this. By leaving your app and returning, you’ll be restored to your previous state in the app but with the process destroyed. In my testing, pressing Home and then returning to the app wasn’t enough — I actually had to open a different app and then return to the first one. But once I did this, it worked great.

Related resources

Edit: After I’d published this article, /u/mathematicalfunk replied with a presentation they had created addressing exactly this issue with a catchy name: Android Zombies. The presentation covers many of the same points as this article with straightforward pros and cons for testing and mitigation. Take a look!

Think about process death when writing for Android

With both activity recreation and process recreation, the most important thing is to be aware that these situations exist and to understand what happens in each case. There are techniques and tradeoffs for dealing with each, and knowing what the tradeoff is lets you make the decision at code design time rather than trying to apply a fix to an established system.

--

--