Effects of Android Application Termination

Dave Smith
Dave Smith
Effects of Android Application Termination

Digging into ActivityManagerService reveals a bit about how applications are killed. Between process management of the system at large and user behavior, there are a handful of methods through which your Android application process may terminate. The additional effects on your application code (based on the method) may have consequences for you as a developer.

We are going to look at the effects of the following termination methods:

  • Memory pressure (i.e lowmemorykiller) axing a process
  • User swiping a task away from the recents UI
  • User invoking Force Stop from device settings

Foreground vs. Background

Before going any further, we have to make a quick digression into how Android views foreground and background status. Generally speaking, the one application that is currently visible and interactive to the user is referred to as the foreground application. All other applications on the system are considered to be in a background state. The low memory killer module may not view all background states as equal, but for the purposes of this discussion they can be considered equivalent with a specific augmentation...

Service objects can be marked to run as a “foreground service” whose only active UI consists of a persisted notification in the system window shade. This special case gives that application process an elevated priority level (PERCEPTIBLE_APP_ADJ specifically), even when there is no activity present for the user to interact with. While not exactly equivalent to a fully visible application, for our purposes here we’ll say this puts the overall process in the foreground state.

Technically speaking, what I’m referring to is the "scheduling group" the process is placed in, which is distinct from the values used to determine the killing order of processes for memory pressure. The system services use this distinction to govern certain behaviors related to task killing, so this is important to keep in mind.

Comparison

Each of these methods causes different behaviors to occur within the application that you may need to account for as a developer. I have marked the behaviors that best differentiate the different approaches in the following table:

Action Low Memory Killer Recents Swipe Force Stop
Process is terminated Y Y1 Y
Outstanding PendingIntents will trigger Y Y N
Incoming Broadcast can start application Y Y N
Activity stack preserved Y N N

Low Memory Killer

This condition occurs regularly in an Android system and without the user’s knowledge. When the system comes under enough memory pressure that it must kill a process to reclaim resources, it tries to do so with as little user impact as possible. So, while the application process (and all code running inside) is terminated, external events (such as an external broadcast or a pending alarm that fires) will start the process again. The application even remains in the recents UI for the user to launch again. Additionally, any activity stacks that existed when the process was killed are retained, and they will be re-created when the user attempts to bring the application into the foreground again. Ideally, the fact that the application process was ever terminated in the first place is masked from the user.

Task Swipe

When the user takes explicit action to clear an application from the recents UI, the resulting behavior is a bit different. In this case, the system takes this cue from the user to mean he or she doesn’t want the application state preserved. Activity stacks associated with the process will be cleared out, and the next launch will create a brand new task. As before, external actions from other applications and services are allowed to wake up and restart the process again.

Additionally, the process will only be terminated if it is considered to be in a background state at the time. This is generally considered true, because even a foreground activity stack will be backgrounded temporarily while the recents UI is visible and active (technically, the foreground application at that point is SystemUI). However, the case of an active foreground service will prevent this termination from occurring. In a later section, we’ll describe how this can lead to some unexpected behavior.

This does mean, however, that running background services does not protect a process from this level of termination. To help ease the transition, services are afforded a callback via onTaskRemoved() when this occurs if there is something critical that needs to be addressed before the service is forcefully removed. Note that this callback is specific to this use case, and you should not expect to receive this when low memory killer is involved.

Force Stop

The final case involves the user venturing into the device settings and requesting the application be stopped. Many assume that the previous case and this one behave in the same way, but there are some acute differences in behavior to be aware of. The first is that the precondition of background state for process termination disappears; the process will always be killed when the user asks for a force stop.

Beyond this, the entire process record is marked within the system services as being in a "stopped state"; this means that no external triggers will be allowed to wake up the application process. Until the user explicitly engages with the application again, it has been isolated. This is the same state an application is placed in upon initial install before the user opens it for the first time.

An Interesting Edge Case

Inevitably, as Android tries to become smarter about managing processes, the rise in complexity will lead to producing undefined states. One such case was illuminated in a comment on this bug report filed for a related reason. The behavior described here indicates that when an application is running a foreground service and the task is swiped away, the process stays running but becomes susceptible to death on any subsequent external trigger.

The reason for this behavior lies in how ActivityManagerService currently tries to manage these events. Upon receiving a request to clear an application task, the service will attempt to kill the process if it is considered to be in the background. If it is in the foreground at the time, it will mark the process to be killed later. Presumably, this is to allow the process to die silently after the service work is complete, or it moves out of the foreground state.

The marking value is checked anytime the foreground/background state of the process gets re-evaluated inside of ActivityManagerService.computeOomAdjLocked(), which happens continuously as events occur in the system for all processes. The key to this little case seems to be in this block of code from ActivityManagerService, which evaluates different event conditions in an if-else fashion. You can see from the code that the act of receiving a broadcast is evaluated before checking for a running foreground service. If the application is in the process of receiving a standard broadcast (i.e. one without the FLAG_RECEIVER_FOREGROUND flag set), the process will be set to a background state, and the code will not continue on to verify that there is a foreground service running to override that condition.

To summarize, the events that lead to the unexpected behavior end up being:

  1. Application starts a foreground service.
  2. User moves the application into the background (it is still in the foreground group because of the service).
  3. User swipes the task away, which marks the process to be killed when its state changes.
  4. A broadcast event is received, and ActivityManagerService updates the application process state to handle the broadcast.
  5. In doing so, ActivityManagerService moves the process to the background group because the broadcast doesn’t have the foreground flag.
  6. ActivityManagerService skips over checking for a foreground service in this case because of the if-else structure.
  7. In the same block, ActivityManagerService checks if the process is marked for death AND in the background group...both are now true.

This use case may not be highly common, but it has been common enough to spawn several bug reports on the issue (and a host of traffic on SO). From my perspective, those bug reports are righteous. So, be sure to test your applications under different exit conditions, and make use of the callbacks the framework provides to the extent that they are useful.


  1. This termination is not guaranteed. ↩︎