Android 进程和线程 | Processes and Threads

原文:来自Android Developers

Processes and Threads 

When an application component starts and the application does not have any other components running, the Android system starts a new Linux process for the application with a single thread of execution. By default, all components of the same application run in the same process and thread (called the "main" thread). If an application component starts and there already exists a process for that application (because another component from the application exists), then the component is started within that process and uses the same thread of execution. However, you can arrange for different components in your application to run in separate processes, and you can create additional threads for any process.

This document discusses how processes and threads work in an Android application.

 

Processes 


By default, all components of the same application run in the same process and most applications should not change this. However, if you find that you need to control which process a certain component belongs to, you can do so in the manifest file.

The manifest entry for each type of component element—<activity> <service> <receiver> , and <provider> —supports an android:process attribute that can specify a process in which that component should run. You can set this attribute so that each component runs in its own process or so that some components share a process while others do not. You can also set android:process so that components of different applications run in the same process—provided that the applications share the same Linux user ID and are signed with the same certificates.

The <application> element also supports an android:process attribute, to set a default value that applies to all components.

Android might decide to shut down a process at some point, when memory is low and required by other processes that are more immediately serving the user. Application components running in the process that‘s killed are consequently destroyed. A process is started again for those components when there‘s again work for them to do.

When deciding which processes to kill, the Android system weighs their relative importance to the user. For example, it more readily shuts down a process hosting activities that are no longer visible on screen, compared to a process hosting visible activities. The decision whether to terminate a process, therefore, depends on the state of the components running in that process. The rules used to decide which processes to terminate is discussed below.

 

 

 

  1. Foreground process

    A process that is required for what the user is currently doing. A process is considered to be in the foreground if any of the following conditions are true:

    Generally, only a few foreground processes exist at any given time. They are killed only as a last resort—if memory is so low that they cannot all continue to run. Generally, at that point, the device has reached a memory paging state, so killing some foreground processes is required to keep the user interface responsive.

  2. Visible process

    A process that doesn‘t have any foreground components, but still can affect what the user sees on screen. A process is considered to be visible if either of the following conditions are true:

    • It hosts an Activity that is not in the foreground, but is still visible to the user (its onPause() method has been called). This might occur, for example, if the foreground activity started a dialog, which allows the previous activity to be seen behind it.
    • It hosts a Service that‘s bound to a visible (or foreground) activity.

    A visible process is considered extremely important and will not be killed unless doing so is required to keep all foreground processes running.

  3. Service process

    A process that is running a service that has been started with the startService() method and does not fall into either of the two higher categories. Although service processes are not directly tied to anything the user sees, they are generally doing things that the user cares about (such as playing music in the background or downloading data on the network), so the system keeps them running unless there‘s not enough memory to retain them along with all foreground and visible processes.

  4. Background process

    A process holding an activity that‘s not currently visible to the user (the activity‘s onStop() method has been called). These processes have no direct impact on the user experience, and the system can kill them at any time to reclaim memory for a foreground, visible, or service process. Usually there are many background processes running, so they are kept in an LRU (least recently used) list to ensure that the process with the activity that was most recently seen by the user is the last to be killed. If an activity implements its lifecycle methods correctly, and saves its current state, killing its process will not have a visible effect on the user experience, because when the user navigates back to the activity, the activity restores all of its visible state. See the Activities document for information about saving and restoring state.

  5. Empty process

    A process that doesn‘t hold any active application components. The only reason to keep this kind of process alive is for caching purposes, to improve startup time the next time a component needs to run in it. The system often kills these processes in order to balance overall system resources between process caches and the underlying kernel caches.

Android ranks a process at the highest level it can, based upon the importance of the components currently active in the process. For example, if a process hosts a service and a visible activity, the process is ranked as a visible process, not a service process.

In addition, a process‘s ranking might be increased because other processes are dependent on it—a process that is serving another process can never be ranked lower 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 is always considered at least as important as process B.

Because a process running a service is ranked higher than a process with background activities, an activity that initiates a long-running operation might do well to start a service for that operation, rather than simply create a worker thread—particularly if the operation will likely outlast the activity. For example, an activity that‘s uploading a picture to a web site should start a service to perform the upload so that the upload can continue in the background even if the user leaves the activity. Using a service guarantees that the operation will have at least "service process" priority, regardless of what happens to the activity. This is the same reason that broadcast receivers should employ services rather than simply put time-consuming operations in a thread.

 

 

Threads 


When an application is launched, the system creates a thread of execution for the application, called "main." This thread is very important because it is in charge of dispatching events to the appropriate user interface widgets, including drawing events. It is also the thread in which your application interacts with components from the Android UI toolkit (components from the android.widget and android.view packages). As such, the main thread is also sometimes called the UI thread.

The system does not create a separate thread for each instance of a component. All components that run in the same process are instantiated in the UI thread, and system calls to each component are dispatched from that thread. Consequently, methods that respond to system callbacks (such as onKeyDown() to report user actions or a lifecycle callback method) always run in the UI thread of the process.

For instance, when the user touches a button on the screen, your app‘s UI thread dispatches the touch event to the widget, which in turn sets its pressed state and posts an invalidate request to the event queue. The UI thread dequeues the request and notifies the widget that it should redraw itself.

When your app performs intensive work in response to user interaction, this single thread model can yield poor performance unless you implement your application properly. Specifically, if everything is happening in the UI thread, performing long operations such as network access or database queries will block the whole UI. When the thread is blocked, no events can be dispatched, including drawing events. From the user‘s perspective, the application appears to hang. Even worse, if the UI thread is blocked for more than a few seconds (about 5 seconds currently) the user is presented with the infamous "application not responding " (ANR) dialog. The user might then decide to quit your application and uninstall it if they are unhappy.

Additionally, the Andoid UI toolkit is not thread-safe. So, you must not manipulate your UI from a worker thread—you must do all manipulation to your user interface from the UI thread. Thus, there are simply two rules to Android‘s single thread model:

  1. Do not block the UI thread
  2. Do not access the Android UI toolkit from outside the UI thread
 

Worker threads 

 

Because of the single thread model described above, it‘s vital to the responsiveness of your application‘s UI that you do not block the UI thread. If you have operations to perform that are not instantaneous, you should make sure to do them in separate threads ("background" or "worker" threads).

For example, below is some code for a click listener that downloads an image from a separate thread and displays it in an ImageView :

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

  

At first, this seems to work fine, because it creates a new thread to handle the network operation. However, it violates the second rule of the single-threaded model: do not access the Android UI toolkit from outside the UI thread—this sample modifies the ImageView from the worker thread instead of the UI thread. This can result in undefined and unexpected behavior, which can be difficult and time-consuming to track down.

 

To fix this problem, Android offers several ways to access the UI thread from other threads. Here is a list of methods that can help:

For example, you can fix the above code by using the View.post(Runnable) method:

public void onClick(View v) {
    new Thread(new Runnable() {
        public void run() {
            final Bitmap bitmap = loadImageFromNetwork("http://example.com/image.png");
            mImageView.post(new Runnable() {
                public void run() {
                    mImageView.setImageBitmap(bitmap);
                }
            });
        }
    }).start();
}

 

Now this implementation is thread-safe: the network operation is done from a separate thread while the ImageView is manipulated from the UI thread.

However, as the complexity of the operation grows, this kind of code can get complicated and difficult to maintain. To handle more complex interactions with a worker thread, you might consider using a Handler in your worker thread, to process messages delivered from the UI thread. Perhaps the best solution, though, is to extend 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 work on your user interface. It performs the blocking operations in a worker thread and then publishes the results on the UI thread, without requiring you to handle threads and/or handlers yourself.

To use it, you must subclass AsyncTask and implement the doInBackground() callback method, which runs in a pool of background threads. To update your UI, you should implement onPostExecute(), which delivers the result from doInBackground() and runs in the UI thread, so you can safely update your UI. You can then run the task by calling execute() from the UI thread.

 

For example, you can implement the previous example using AsyncTask this way:

public void onClick(View v) {
    new DownloadImageTask().execute("http://example.com/image.png");
}

private class DownloadImageTask extends AsyncTask<String, Void, Bitmap> {
    /** The system calls this to perform work in a worker thread and
      * delivers it the parameters given to AsyncTask.execute() */
    protected Bitmap doInBackground(String... urls) {
        return loadImageFromNetwork(urls[0]);
    }

/** The system calls this to perform work in the UI thread and delivers
      * the result from doInBackground() */
    protected void onPostExecute(Bitmap result) {
        mImageView.setImageBitmap(result);
    }
}

 

Now the UI is safe and the code is simpler, because it separates the work into the part that should be done on a worker thread and the part that should be done on the UI thread.

You should read the AsyncTask reference for a full understanding on how to use this class, but here is a quick overview of how it works:

Caution: Another problem you might encounter when using a worker thread is unexpected restarts in your activity due to a runtime configuration change (such as when the user changes the screen orientation), which may destroy your worker thread. To see how you can persist your task during one of these restarts and how to properly cancel the task when the activity is destroyed, see the source code for the Shelves sample application.

 

 

Thread-safe methods 

In some situations, the methods you implement might be called from more than one thread, and therefore must be written to be thread-safe.

This is primarily true for methods that can be called remotely—such as methods in a bound service . When a call on a method implemented in an IBinder originates in the same process in which the IBinder is running, the method is executed in the caller‘s thread. However, when the call originates in another process, the method is executed in a thread chosen from a pool of threads that the system maintains in the same process as the IBinder (it‘s not executed in the UI thread of the process). For example, whereas a service‘s onBind() method would be called from the UI thread of the service‘s process, methods implemented in the object that onBind() returns (for example, a subclass that implements RPC methods) would be called from threads in the pool. Because a service can have more than one client, more than one pool thread can engage the same IBinder method at the same time. IBinder methods must, therefore, be implemented to be thread-safe.

Similarly, a content provider can receive data requests that originate in other processes. Although the ContentResolver and ContentProvider classes hide the details of how the interprocess communication is managed, ContentProvider methods that respond to those requests—the methods query()insert()delete()update(), and getType()—are called from a pool of threads in the content provider‘s process, not the UI thread for the process. Because these methods might be called from any number of threads at the same time, they too must be implemented to be thread-safe.

 

 

Interprocess Communication 


Android offers a mechanism for interprocess communication (IPC) using remote procedure calls (RPCs), in which a method is called by an activity or other application component, but executed remotely (in another process), with any result returned back to the caller. This entails decomposing a method call and its data to a level the operating system can understand, transmitting it from the local process and address space to the remote process and address space, then reassembling and reenacting the call there. Return values are then transmitted in the opposite direction. Android provides all the code to perform these IPC transactions, so you can focus on defining and implementing the RPC programming interface.

To perform IPC, your application must bind to a service, using bindService(). For more information, see the Services developer guide.

 

译文:

 

进程和线程

 

当一个软件初次启动时,Android 系统将开启一个新的单线程的Linux进程 。默认的,一个软件的所有组件和将运行在同个进程的同个线程上,即主线程。如果一个软件组件启动时,已经存在一个该软件的进程(因为可能另一个软件的组件已经运行),这个时候,这个组件将在同个进程及同个线程上启动。然而,你可以将不同的组件安排到不同的进程上运行,也可以为每个进程创建多个线程。

 

进程


一般来说,同个软件的所有组件都运行在同个进程上,大部分软件都该遵循这个规则。但是,如果你觉得你需要控制哪个组件运行在哪个进程,你可以在manifest 文件中进行配置。

Manifest中 各种类型组件的标签 如 <activity> <service> <receiver> , 和 <provider>  都支持 android:process 属性,你可以通过它指定某个组件运行在哪个进程。你可以设置这个属性,让各个组件运行在自己的进程上,或者让几个组件共享一个进程。你也可以通过设置android:process 属性,让不同软件的组件运行在同个进程上—— 假设这些软件共享了同个Linux User ID 并 使用了同样的签名证书。

<application> 标签也支持 android:process 属性,对他进行设置将对所有组件都生效。

Android 在某些场景可能会关闭某个进程,比如当内存余量较低且其他进程亟需内存来完成跟用户的交互时。此时,运行在这个进程的组件将被销毁,当这些组件再次有任务需要进行时,进程再次启动。

当确定哪个进程将被关闭时,Android 系统根据它们相对用户的重要性来权衡。比如,拥有不可见的Activity 的进程,比拥有可见的Activity 进程更容易被关闭。因此,是否终止一个进程取决于运行其上的组件状态。其规则在下文将会详述。

Process lifecycle 

The Android system tries to maintain an application process for as long as possible, but eventually needs to remove old processes to reclaim memory for new or more important processes. To determine which processes to keep and which to kill, the system places each process into an "importance hierarchy" based on the components running in the process and the state of those components. Processes with the lowest importance are eliminated first, then those with the next lowest importance, and so on, as necessary to recover system resources.

There are five levels in the importance hierarchy. The following list presents the different types of processes in order of importance (the first process is most important and is killed last):

Android 系统将尽量长时间地保存一个软件的进程,但是事实上是为了新的或者更重要的进程,系统必须终止旧的进程以回收内存。为了确定进程的终止或保存,系统根据进程中的组件及其状态将每个进程放置在重要性层次中。

一共有5个界别的重要性层次。下面是根据重要性的级别次序列出的不同类型的进程(第一个级别是最重要的也是最晚被终止的)

1. 前台进程

用户当前正在交互的进程。一个进程如果满足了以下条件的其中一个将被视为前台进程。

  •  它拥有一个用户正在交互的Activity(Activity OnResume() 方法被调用了)
  •  它拥有一个Service ,这个Service 绑定到一个用户正在交互的Activity
  •  它拥有一个Service ,这个Service 运行在“前台”(服务调用了startForeground().
  •  它拥有一个Service ,这个Service 正在执行它的生命周期的某个回调(onCreate()onStart(), or onDestroy()
  •  他拥有一个BroadcastReceiver 正在执行 onReceive() 方法.
一般来说,在任何时间,只有少数几个前台进程。他们在最后时刻才会被终止——当内存低到这些前台进程不能同时运行时。这种时候,一般来说,系统到了需要“内存分页”(使用磁盘虚拟内存)的状态(ram 耗尽),因此,及时终止一些前台进程,对于确保用户界面的反应非常关键。
 
2. 可见进程
虽然没有前台交互的组件,但是仍然可以影响用户屏幕上的所见的进程。满足以下其中一个条件的进程将被视为可见进程:
  •  它拥有一个不在前台交互的Activity,但是用户仍然看得到。( onPause()  被调用)。比如打开了一个对话框,之前的Activity仍然可视。
  •  它拥有一个Service,这个Service 绑定到了一个可视(或者前台的)的Activity 。
一个可见进程将被视为很重要的,一般不会被终止,除非是为了确保所有前台进程继续运行。
 
3. 服务进程
 

拥有以下特征的进程:拥有一个Service ,并通过StartService()启动,但它并不能满足以上两个更高重要性级别的条件。虽然服务进程,并不直接与用户的所见相关联,但它一般都在执行用户需要的工作(比如后台播放音乐,或者下载数据),因此系统会尽量保留它,除非内存不足以保留更高两种级别的进程。

4. 后台进程

拥有一个用户当前不可见的Activity (onStop() 方法被调用)的进程。这些进程对用户体验没有直接影响,当系统需要为更高级别的进程回收内存时,随时会将其终止。一般会有许多后台进程正在运行,因此他们被保存在LRU(最少-最近使用)队列中,以确保最常交互的Activity 关联的后台进程是最后被终止的。如果一个activity 正确地实现了它的生命周期回调方法,并进行状态保存,关闭这些进程并不会给用户体验带来可察觉的影响,因为用户回到activity时,所有可视状态都将被恢复。

5. 空进程

没有拥有任何活跃的组件的进程。保存这种进程的唯一原因是为了缓存,以此提升下次某个组件运行其上的启动速度。系统常常为了平衡在进程缓存和内核缓存之间的系统资源,将其终止。

 

Android 将基于当前进程内活跃组件的重要性,将一个进程的重要性定位在尽可能更高的级别。比如,一个拥有一个Service 和 一个可视 Activity的进程,将被视为可见进程,而非服务进程。

另外,一个进程的级别可能因为有其他进程对其的依赖而提升。比如进程A 为进程B 提供 服务(B 依赖于A),那么A 永远不可能比B 的级别低。打个比方:A 的contentProvider 为B 的客户端提供服务,或者A 的Service 绑定到B 的一个组件,A 的级别将最少跟B 同级。

因为一个运行一个service 的进程将比 拥有一个后台activity(不可见的)的进程级别高,因此一个 需要执行耗时操作的Activity 最好 开启一个service 来进行此操作,而不是简单地开启一个工作线程——特别是此操作的时长超过Activity的生命周期时。比如,一个activity正在上传一张图片到web 端,就应该开启一个service 来执行操作,这样上传就到进入后台执行,即使用户离开了activity。使用一个service 确保这个操作至少是“服务进程”的优先级,不管activity 发生了什么。同样的,broadcast receiver 应该将耗时操作放到一个service而非一个线程中。

 

线程


当一个软件启动时,系统为软件创建了一个线程,称为主线程。 这个线程负责将事件分发到合适的UI 控件上,包括绘制事件。这也是软件与Android UI 组件(Android UI 工具包)交互的线程。因此,主线程又叫UI 线程。
系统并不会为每个组件都创建一个单独的线程。所有组件都在UI线程中实例化。系统对各个组件的调用也是从该线程中分发出去的。因此,响应系统事件的回调方法(如onKeydown)一般都是在UI线程运行的。
 
当你的app 进行密集的工作来响应用户交互,单线程模式会导致糟糕的性能,除非你的代码很优质。特别是,当所有事情都发生咋UI 线程,进行长时间操作,比如网络连接,数据库查询将阻塞住整个UI。当UI 线程被阻塞,没有事件可被分发,包括绘制事件。从用户的角度看,软件似乎挂了,甚至可能导致ANR 操作。用户可能会决定退出你的程序并卸载它。
 
另外,Android UI 工具包是线程不安全的。所以,你不能在工作线程 操纵UI,必须在UI线程上进行操作。
 
因此,对于单线程模式,有两个基本原则
  1. 不要阻塞UI 线程
  2. 不要在UI 线程之外 操纵 Android UI 。
 

Worker threads 

如果你有耗时的操作,请确保在后台线程/工作线程中进行。
 

以下的代码是在在工作线程下载一张图片,然后设置到 ImageView :

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

  

 

乍一看来,这么做好像挺好,因为它创建了一个新线程来完成网络操作。但是,它违反了第二个原则:不要在其他线程操纵Android UI。这个例子在工作线程修改了ImageView 的内容,这可能导致一个不可预知且难以发现问题的结果。

为了解决这个问题,Android 提供了一些方法来从其他线程操纵UI 线程。

 

比如用 View.post(Runnable) :

public void onClick(View v) {
    new Thread(new Runnable() {
        public void run() {
            final Bitmap bitmap = loadImageFromNetwork("http://example.com/image.png");
            mImageView.post(new Runnable() {
                public void run() {
                    mImageView.setImageBitmap(bitmap);
                }
            });
        }
    }).start();
}

 

 

现在,这个实现是线程安全的:网络操作在工作线程中进行,ImageView 的内容在UI 被修改。

但是,随着操作的复杂性增加,这种代码将变得复杂且难以跟踪。为了除了UI 线程与工作线程的复杂交互情况,你应该考虑在工作线程中使用Handler,利用它来处理UI 线程发来的Message。也许最好的解决办法是,继承AsyncTask  类。AsyncTask  简化了需要与UI 线程交互的 工作线程操作。

 

使用AsyncTask 

AsyncTask  允许你在UI 上 执行一个异步的工作。它将一个阻塞式的操作放在一个工作线程中进行,并将结果返回给UI 线程,避免自己去处理线程或者Handler。

使用它的方法是:继承AsyncTask  并 实现 doInBackground() 回调方法,它是在一个后台线程池中进行的。为了更新你的UI,你应该实现onPostExecute(),它将 doInBackground() 的结果传送UI 线程,它本身也是运行在UI 线程的,以此,你可以安全地更新UI。你可以通过在UI 线程中调用execute() 来运行这个task。

比如上面的场景这么实现:
public void onClick(View v) {
    new DownloadImageTask().execute("http://example.com/image.png");
}

private class DownloadImageTask extends AsyncTask<String, Void, Bitmap> {
    /** The system calls this to perform work in a worker thread and
      * delivers it the parameters given to AsyncTask.execute() */
    protected Bitmap doInBackground(String... urls) {
        return loadImageFromNetwork(urls[0]);
    }

/** The system calls this to perform work in the UI thread and delivers
      * the result from doInBackground() */
    protected void onPostExecute(Bitmap result) {
        mImageView.setImageBitmap(result);
    }
}

 

 
 

现在UI 更新是安全的,代码也简洁了,因为你将应该在工作线程与在UI 线程进行的工作分离开了。

你应该认真阅读以下AsyncTask 文档,进一步了解如何使用这个类,以下是简单的概览如何使用它:

 

线程安全方法

在一些情景中,你的方法可能被超过一个的线程调用,因此他应该被写成线程安全的。

特别是那些可被远程调用的方法更应该如此——比如一个 bound service  中的方法。当IBinder 的一个被实现方法的调用来自 IBinder 运行的进程时,方法是在调用者所在线程执行的。但是,当这个调用发生在另一个进程,这个方法是在系统从它在IBinder 进程中维护的一个线程池中的一个非UI 线程中被执行的。比如,尽管,一个Service 的onBind 方法将从service进程的UI 线程中执行,onBind() 返回的对象(比如一个实现了RPC 方法的IBinder子类)的被实现的方法将在线程池线程中被调用。因为一个service 可以有多个client,多过线程池线程可以同时调用同一个IBinder 方法, IBinder  方法必须实现成线程安全的。

同样的,一个content provider 可以接受从其他进程引发的数据请求。尽管 ContentResolver ContentProvider 隐藏了跨进程通信的管理的细节,ContentProvider   响应数据请求的方法-—— query()insert()delete()update(), 和getType() 是从content provider 所在进程的线程池中被调用的,而非UI 线程。因为这些方法可能被多个线程同时调用,因此他们也应该被实现成线程安全的。

 

跨进程通信


 

Android 提供了跨进程通信(IPC)的机制:使用RPCs。在这种机制里,一个方法是被一个Activity或者其他系统组件调用的,但是,它是在远程被执行的(其他进程),然后将结果返回给调用者。这需要将一个方法调用和它的请求数据分解成到一个操作系统可以识别的层次,接着将它从本地进程和地址空间传送到其他进程和地址空间,然后将它重新组装成一个方法调用。返回值又再一次沿反方向传输。Android 提供了实现IPC 传输的整套框架,你可以专注于定义和实现你的PRC 接口。

为了进行IPC,你的程序必须绑定到一个Service,使用 bindService().更多信息,请查阅 Services 开发文档,

 

Android 进程和线程 | Processes and Threads,,5-wow.com

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。