Summary of Android processes and threads

Summary of Android processes and threads

This article is translated from Android official documentation

When an Android application component is started, if no other components of the application are running at the same time, the system will start a new Linux process for the application as a single thread. By default, all components of the same application run in the same process and thread (usually called the "main" thread of the application). If an application component is started and the process of the application already exists (because other components of the application have been started before), the component will be started in this process and executed in the main thread of the application. However, you can also have components in your application run in different processes, and you can add additional threads to any process.

This article discusses how processes and threads work in Android programs.

process

By default, all components of the same application run in the same process, which is the case for most applications. However, if you find that you need a component in your application to run in a specific process, you can set it in the manifest file.

The manifest file provides an android:process attribute for each component element—<activity>, <service>, <receiver>, and <provider>. By setting this attribute, you can specify a specific process for a component to run in. You can set each component to run in its own process, or you can set some components to share a process while others do not. You can also set components of different applications to run in the same process—this allows those applications to share the same Linux user ID and be authenticated by the same certificates.

The <application> element also supports the android:process attribute. Setting this attribute will make all components in the application inherit this attribute by default.

Android may shut down a process when there is less remaining system memory and other processes that directly serve users need to apply for memory. At this time, the components in this process will also be killed one by one. When new tasks arrive for these components, their corresponding processes will be started again.

When deciding which processes to kill, the Android system weighs the importance of those processes to the user. For example, the system is more likely to kill processes that host activities that are no longer visible than processes that host visible activities. The decision to terminate a process depends on the state of the components running in the process. Below we discuss some of the rules used when killing processes.

Process Lifecycle

As a multi-tasking system, Android can certainly keep an application process as long as possible. However, as new or more important processes require more memory, the system has to gradually terminate old processes to gain memory. In order to declare which processes to keep and which to kill, the system generates an "importance hierarchy" for each process based on the components in these processes and the status of these components. The processes in the highest importance hierarchy will be cleared first, followed by the more important ones, and so on, according to the system's needs.

There are 5 levels in this importance hierarchy. The following list shows the different types of processes in order of importance (the first process is the most important and will be killed first):

  1. Foreground process A process that is interacting with the user. If a process is in one of the following states, then we can call this process a foreground process:
    • The process contains an Activity that interacts with the user (the onResume() method of this Activity is called).
    • The process contains a Service that is bound to an activity that interacts with the user.
    • The process contains a Service running "in the foreground" state - this service has called the startForeground() method.
    • The process contains a Service that is running its lifecycle callback functions (onCreate(), onStart(), oronDestroy()).
    • The process contains a BroadcastReceiver that is running its onReceive() method.

    Generally speaking, at any time, there are only a few foreground processes in the system. Only when the system memory is so tight that it cannot continue to run, the system will kill these processes to relieve memory pressure. At such times, the system must kill some foreground processes to ensure that user interactions are responsive.

  2. A process does not have any foreground components, but it can still affect the display on the screen. If a process is in one of the following states, then we can call this process a visible process:
    • The process contains an Activity that is not in the foreground state, but it is still visible to the user (its onPause() method has been called). This situation may occur, for example, if a foreground activity starts a dialog, which makes the previous activity visible behind the dialog.
    • The process contains a Service that is bound to a visible (or foreground) activity.

    A visible process is quite important in the system. Killing a visible process is considered only when all foreground processes are running normally.

  3. Service process A process that contains a Service that has been started with the startService() method, and has not yet entered the above two higher-level categories. Although service processes are not directly associated with any user, they are often used to do things that users care about (such as playing music or downloading network data in the background), so the system will only kill the service process in order to ensure that all foreground and visible processes are running normally.
  4. Background process A process that contains an activity that is no longer visible (onStop() has been called on this activity). Such processes do not directly affect the user experience, and the system can kill them at any time in favor of foreground, visible, or service processes. Generally speaking, there are many background processes running in the system, so keeping them in an LRU (least recently used) list ensures that the process of the activity that the user has recently seen will be killed first. If an activity implements its lifecycle callback functions correctly and saves its current state, then killing the process containing the activity will not affect the user's visual experience, because when the user navigates back to the activity, all its visible state will be restored. See Activities for more documentation on how to save and restore state.
  5. Empty process A process that does not contain any active application components. The only reason for the existence of such a process is caching. In order to improve the startup time of a component, it is necessary to let the component run in such a process. In order to balance the system resources of the process cache and the related kernel cache, the system needs to kill these processes.

Android rates the components in a process based on their importance as much as possible. For example, if a process contains a service and a visible activity, then the process will be rated as a visible process, not a service process.

Additionally, a process's rating may be boosted by other processes that depend on it—a process that serves other processes will never have a lower rating than the process it is serving. For example, if a content provider in process A is serving a client in process B, or if a service in process A is bound to a component in process B, process A will be considered by the system to have at least a higher rating than process B.

Because a process with a service running in it is rated higher than a process with background activities, when an activity starts a long-running operation, it is best to start a service to do the operation rather than simply creating a worker thread—especially when the long-running operation might bring down the activity. For example, an activity that uploads a picture to a website should start a worker thread to perform the upload. That way, the upload can continue in the background even if the user navigates away from the activity. Using a service ensures that the operation is at least at the "service process" priority, regardless of what happens to the activity. This is why broadcast receivers should use services rather than simply putting long-running operations in threads.

Threads

When an application starts, the system creates a thread for it, called the "main thread". This thread is important because it handles dispatching events to relevant user interface widgets, including drawing events. Your application also interacts with components from the Android UI toolkit (including components from the android.widget and android.view packages) in this thread. Therefore, this main thread is sometimes called the UI thread.

The system does not create a separate thread for each component. All components in the same process are instantiated in the UI thread, and the system calls each component through this thread. Therefore, methods that respond to system calls (such as the onKeyDown() method used to capture user actions or a lifecycle callback function) are all run in the UI thread of the process.

For example, when the user clicks a button on the screen, your application's UI thread passes the click event to the widget, which then sets its pressed state and sends an invalidation request to the event queue. The UI thread dequeues the request and then processes it (notifying the widget to redraw itself).

When your application's interaction with the user requires a high response speed, this single-threaded model may produce bad results (unless you implement your application well). In particular, when everything in the application happens in the UI thread, long operations such as accessing network data and database queries will block the entire UI thread. When the entire thread is blocked, no events can be delivered, including drawing events. This makes the application appear to the user to be suspended. Even worse, if the UI thread is blocked for more than a few seconds (currently 5 seconds), the system will pop up the infamous "application not responding" (ANR) dialog box. At this time, the user may choose to exit your application or even uninstall it.

In addition, Android's UI thread is not thread-safe. So you can't operate your UI in a worker thread—you must operate your UI on the UI thread. There are two simple rules about Android's single-threaded model:

  1. Don't block the UI thread
  2. Do not access the Android UI toolkit from non-UI threads

Worker Threads

Because of the single-threaded model described above, it is important to ensure that your app's UI is responsive and that the UI thread is not blocked. If you cannot allow operations in your app to be executed in a short period of time, then you should make sure to put these operations in a separate thread ("background" or "worker" thread).

For example, the following code downloads an image in an additional thread and displays it in an ImageView:

  1. new Thread( new Runnable(){
  2. public   void run(){
  3. Bitmap b = loadImageFromNetwork( "http://example.com/image.png" );
  4. mImageView.setImageBitmap(b);
  5. }
  6. }).start();}

At first this code looks good because it creates a new thread to handle the network operation. However, it violates the second rule of the single thread model: do not access the Android UI toolkit from a non-UI thread—this example modifies the ImageView in a worker thread. This can lead to unexpected results and is difficult to debug.

To fix this problem, Android provides several methods to access the Android UI toolkit from non-UI threads. See the following list for details:

  • Activity.runOnUiThread(Runnable)
  • View.post(Runnable)
  • View.postDelayed(Runnable, long)

Then, you can use the View.post(Runnable) method to modify the previous code:

  1. public   void onClick(View v){
  2. new Thread( new Runnable(){
  3. public   void run(){
  4. final Bitmap bitmap = loadImageFromNetwork( "http://example.com/image.png" );
  5. mImageView.post( new Runnable(){
  6. public   void run(){
  7. mImageView.setImageBitmap(bitmap);
  8. }
  9. });
  10. }
  11. }).start();}

Now this solution is thread-safe: after the network operation is completed in a separate thread, the UI thread will operate on the ImageView.

However, as the complexity of the operation grows, the code will become more and more complex and difficult to maintain. In order to handle more complex interactions with worker threads, you can consider using Handler in the worker thread to handle messages in the UI thread. Perhaps the best solution is to inherit the AsyncTask class, which simplifies the execution of worker thread tasks that need to interact with the UI.

Using AsyncTask

AsyncTask allows you to perform asynchronous operations on the UI. It performs some blocking operations in a worker thread and then passes the results to the UI main thread, without requiring you to handle threads or handlers during this process.

To use it, you must inherit AsyncTask and implement the doInBackground() callback method, which runs in a background thread pool. If you need to update the UI, you should implement onPostExecute(), which takes the result from doInBackground() and runs it in the UI thread, so you can update your UI safely. You can run this task by calling the execute() method on the UI thread.

For example, you could implement the previous example using an AsyncTask:

  1. public   void onClick(View v){
  2. new DownloadImageTask().execute( "http://example.com/image.png" );
  3. }
  4. private   class DownloadImageTask extends AsyncTask<String,Void,Bitmap>{
  5. /** The system calls this to perform work in a worker thread and
  6. * delivers it the parameters given to AsyncTask.execute() */  
  7. protected Bitmap doInBackground(String... urls){
  8. return loadImageFromNetwork(urls[ 0 ]);
  9. }
  10.     
  11. /** The system calls this to perform work in the UI thread and delivers
  12. * the result from doInBackground() */  
  13. protected   void onPostExecute(Bitmap result){
  14. mImageView.setImageBitmap(result);
  15. }}

Now the UI is safe and the code is simpler because AsyncTask separates what needs to be done in the worker thread from what needs to be done in the UI thread.

You should read the reference documentation of AsyncTask to get a better understanding of its use. Here is a quick overview of how AsyncTask works:

  • You can set the parameter type, progress value, task end value, and the type of the task to use.
  • doInBackground() method is automatically executed in the worker thread
  • onPreExecute() , onPostExecute() , and onProgressUpdate() methods are all called on the UI thread
  • The return value of doInBackground() will be sent to onPostExecute()方法
  • You can call publishProgress() in doInBackground() ) at any time to execute onProgressUpdate() in the UI thread.
  • You can cancel this task from any thread

Note: Another problem you may encounter when using worker threads is that your activity may be restarted unexpectedly due to runtime configuration changes (such as the user changing the screen orientation), which may kill your worker thread. To solve this problem you can refer to the Shelves project.

Thread-safe methods

In some cases, the method you implement may be called by multiple threads, so you must write it to be thread-safe.

[[124306]]

Article address: Android processes and threads

<<:  DAMO holds a seminar on self-controllable database software products and big data applications

>>:  A design study guide for programmers

Recommend

Where can I buy fresh dog meat?

Where should I buy dog ​​meat online? Where can I...

Do you know the 6 basic principles of operational thinking?

What is operational thinking ? Operational thinki...

Will people who rely on coffee to "extend their lives" be healthier?

Coffee is a daily drink in modern people's li...

Solid info! A comprehensive interpretation of Amazon’s advertising model!

Through the analysis and summary of this article,...

Man Jiang Hong, what is red?

A River Full of Red: Writing Thoughts (Song Dynas...

Guide to building a TOB operation customer acquisition system!

“The two core elements of customer acquisition ar...

Analysis of WeChat Reading Products

This series of articles may reach nearly 50,000 w...

How do K12 online education companies build their own distribution systems?

The current situation of online education compani...