Translator’s note: when translating, I tried to make the most of the terminology that Google itself offers in the Russian version of the Android documentation, thus “service” became “service”, “content provider” became “content provider”, and so on. But the “activity” could not become an “operation” - I did not overpower myself. Excuse me.
Let's face it: mobile devices don't have infinite memory, infinite battery power, or something else infinite. For us, this means the following:
we must view the death of the process as a natural part of the life cycle of our applications . It is important to make sure that the release of memory associated with the destruction of the process does not lead to negative consequences for our user. To accomplish this task, most of the process architecture in Android was created in such a way as to ensure a rigid hierarchy, in accordance with which processes live and die.
Android process hierarchy
If you refer to the
documentation , you will find that the most important processes are called
foreground processes , followed by
visible processes ,
utility processes ,
background processes , and finally
empty processes . By the way, notice that although we’ll talk about the components of Android applications (Services,
Activity
, and so on),
Android always destroys processes, not components . Of course, this does not contradict garbage collection (as a result of which the memory occupied by objects that no one else refers to is returned to the system), but this is a topic for a separate post.

')
Foreground processes
You probably think that what the user interacts with at any given moment is the component of the system that cannot be destroyed at all (at least as long as the user continues his work), and you are absolutely right. But one thing: “what the user interacts with at any given point in time” is a slightly vague definition. One of the components that fall into this category is the
Foreground Activity
— the one in which
onResume () has already been called, and
onPause () is not yet.
While some
Activity
rely in their work only on themselves, others shift a part of it to bound services.
Any process containing a service Activity
foreground Activity
receives exactly the same priority from the system as the process containing the foreground Activity
itself. And rightly so: if the
Activity
believes that for its work it is necessary to keep a constant connection with the service, then keep this service alive and unharmed in the interests of both the
Activity
itself and Android. The same principle applies to content providers.
But who said that
Activity
is the only component whose disappearance will immediately make the user indignant? I would surely be angry if my music suddenly stopped playing, or the tips from my navigation system would disappear into a fog. Fortunately, Android allows services to notify the system that they have high priorities by calling the
startForeground () method. Calling this method is the best way to provide
background music playback , and, as for other tasks, before calling
startForeground()
, you need to ask yourself: “
startForeground()
user immediately notice that my service has stopped working?” the plan should be used only in critical cases, when the interruption becomes immediately apparent.
Note: Front-end services require that you post a notification that tells the user that the service is working. If it seems to you that in your particular case the notification is not needed, then you probably don’t need the foreground service (this is normal, there are other ways to ensure work in the background, which will be described later).
There are several other cases in which the priority of a process is temporarily raised to that of a foreground process. These include the service performing the following lifecycle methods:
onCreate () ,
onStartCommand (), and
onDestroy () . The execution of a broadcast receiver by the
onReceive () method also applies to them. This priority increase is necessary in order to make these lifecycle methods atomic and allow each component to execute them without being destroyed by the system.
Visible processes
So, stop, I already told about the foreground
Activity
? He told, but Android in its inscrutable wisdom allows you to face situations where your
Activity
is visible, but not in the foreground. This can happen when the
Activity
starts another
Activity
, the topic of which is inherited from Dialog. Or when the activity being
Activity
is translucent. Or when you call the system dialog with a request from the user of
certain permissions (which, in fact, is an
Activity
!).
Activity
is visible from the
onStart () call to the onStop () call. Between these two calls, you can do everything you expect from a visible
Activity
: refreshing the screen, and so on.
By the way, the processes in which the associated services and content providers of the visible
Activity
also given the same priority as the process with the visible
Activity
(just as it does in the
Activity
), and for the same reasons: to ensure that these dependent processes will not be destroyed by the system.
Pay attention to the following point: the
fact that your process is visible does not guarantee that it will not be destroyed by the system . If the foreground processes require a lot of memory for their needs, there is a chance that Android will still go to extreme measures and swat the visible process. For the user, this will look as follows: a visible
Activity
located under the foreground
Activity
will be replaced with a black screen. Of course, if you
recreate a killed Activity
correctly , your process and your
Activity
will be recreated without losing state as soon as the foreground
Activity
is destroyed.
Note: one of the reasons that the result of startActivityForResult () is processed in onActivityResult () , and the result of requestPermissions () is processed in onRequestPermissionsResult () , and not in callback functions, the very possibility of destroying the visible process is if your entire the process will be destroyed, and all the callback functions in it will be destroyed too. Therefore, if you see libraries using the callback approach, be aware: they may not work the way you want, if there is a shortage of memory in the system.
Utility processes
If your process does not fall into any of the above categories, but it has a running service, then your process receives the priority of the service process. Such cases are typical of many applications that perform background work (for example, loading data), but the results of this work are not as important as in the case of front-end services.
And these processes are not as useless as they may seem. For a large range of tasks, such a process is the
best way to perform background tasks, since it will be destroyed only if so much is happening in visible and foreground processes that you need to take memory from somewhere urgently.
Pay particular attention to the constant returned by you from
onStartCommand () , since it determines the behavior of the system in case your process is destroyed when there is a shortage of memory:
- START_STICKY implies that the system will revive your service when it is possible, but it will not re-send the last
Intent
received by the service (for example, you can restore the state yourself, or you have a self-written life cycle that determines what to do at the start and stop the service). - START_REDELIVER_INTENT is intended for services that want to be restarted with the same
Intent
'that they received in onStartCommand () - START_NOT_STICKY is useful for services that do not have to be restarted if they are quietly dissolved in the fog. This behavior can be useful for services that perform periodic tasks that can be temporarily omitted.
Background processes
Suppose that you had a
Activity
, and the user clicked on the Home button, which led to the
onStop () call. If, apart from this
Activity
, you no longer had anything that would allow you to continue to maintain the high priority of your process, then
your process goes into the category of background processes . By the way, they occupy most of the device’s memory, in case the user decides to return to one of the previously opened applications.
Android does not destroy processes right and left, since launching them from scratch is quite a resource-consuming operation. Therefore, they can remain in memory for some time before being destroyed if a new high-priority process appears on the horizon. Destruction occurs in the order of crowding out long-unused: first, those processes that have not been used the most are destroyed. Just as in the case of the destruction of visible processes /
Activity
, you need to be able to
correctly recreate the Activity
without losing data.
Empty processes
As in any hierarchy, there is always the lowest level. In empty processes, there is not a single active component, and Android can destroy them at any time, but usually they are still kept in memory for some time (this is again a word about the efficient use of memory, which is not to clean everything contract).
What you should pay attention to
Although we talked about process priorities in terms of what components are running in your processes, remember that
hierarchy exists at the process level, not the component level . Only one component (for example, the foreground service) will translate your entire process into the category of foreground processes. Although most applications live well in one process, you need to remember that if the life cycle of your application can be very different from launch to launch, or the application has a very difficult task as well as a long time, but lightweight, and the first does not depend on the second , it makes sense to distinguish two processes. Let the heavy task be performed in the first process, and the lightweight one in the second, and then, in the event of a lack of memory, the heavy process can be suspended, and the lightweight one will continue its work.
It is also important to remember that the
priority of your process is determined by what happens at the component level . This means that if you run a very important task, which should be performed for a long time, in a separate thread, but from under the
Activity
, then you will have an unpleasant surprise when your process suddenly becomes a background. Use the tools available to you (for example, services, or foreground services) to ensure that the system is up to date with what you are doing.
Do not forget about others and remember your user.
Create your application in such a way that it constantly works at the level of priority that it needs - no more and no less. Remember also that you, as a developer, have a device that is
much more powerful than the weakest end-user devices, and because of this you can never see with your own eyes the destruction of the visible process (and the service one even more so), but this does not mean that never happens.
Although I usually recommend buying as weak a device as possible for testing,
you can test the behavior of your application when it is destroyed, even on your flagship . To destroy your application (along with all its processes), do the following:
adb shell am force-stop com.example.packagename
If you have multiple processes, you can first find the PID of the process you need by looking at the second column (that is, the first number) of the result of the following command:
adb shell ps | grep com.example.packagename
And then swat this process:
adb shell kill PID
This will be the first step to ensure that the application will work correctly on most devices, regardless of what memory limitations will arise.