📜 ⬆️ ⬇️

Who should live and who should die: priorities of processes in Android

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.

image
')

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:



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.

Source: https://habr.com/ru/post/276381/


All Articles