SAKA'S BLOG

进程和线程

进程和线程

当一个程序组件启动时,
并且这个程序没有其他组件在运行,
安卓系统会为这个程序启动一个Linux进程,
这个进程只有一个线程在运行。
默认情况下,同一个程序的所有组件都运行在一个被称为“主线程”的线程。
当一个程序的组件在其他组件已经运行的时候启动,
这两个组件会运行在同一个进程中的同一个线程。
然而,你可以让程序不同的组件运行在不同的进程中,
你也可以为进程创建额外的线程。

这篇文档讨论线程和进程如何在安卓应用中工作。

进程(Process)

默认情况下,同一个程序的所有组件都运行在同一个进程中,
最好不要改变这种情况。
当你需要控制一个其他进程中的组件,
你可以在manifest中进行。

Manifest中每个元素(activity,service,broadcastreceiver,contentprovider)支持android:process属性,
这个属性可以明确在哪个进程中运行组件。
你可以设置这个属性来使得每个组件都运行在自己的进程中,
或者只有一些组件共享进程而其他的组件不会干扰。
你也可以通过设置这个属性使得不同的应用中的组件运行在同一个进程中,
不同的应用程序共享相同的userId和签名证书。

application标签也支持android:process属性,
设置这个属性后,为该程序中所有的组件设置了一个默认相同的进程。

安卓系统在以下情况会关闭进程:内存低,用户需要使用其他进程。
此时应用中运行的组件会立即被销毁。
当用户再次需要运行此程序时,一个进程会被启动。

安卓系统会对应用的重要性的权重进行对比,来决定那个进程会被杀死。
例如,系统会杀死那些持有activity但是不会显示在屏幕上的activity,
而不会杀死持有显示在屏幕上的activity。
因此,终止一个进程的条件取决于那个进程中组件的状态。

线程(Threads)

当一个程序被启动时,系统会为它创建一个线程,一般叫做主线程。
这个线程是一个非常重要的线程,
因为它控制了用户与控件的交互和绘制等事件分发。
因此也称主线程为UI线程。

系统不会为每个组件单独创建一个线程,
运行在同一个进程中的所有的组件都运行在同一个UI线程中,
并且系统会在这个线程中为调用其他组件分发事件。
通常,系统的回调方法(例如onKeyDown()报告给用户的回调方法的生命周期)运行在进程中的UI线程。

例如:当用户触摸屏幕上的按钮时,app的UI线程会将触摸事件分发给控件,
这个事件会将按压状态不断传递给事件队列。
UI线程不断去除队列中的请求来通知控件需要重绘;

当应用程序执行大量的工作来响应用户交互时,
这种单线程模型可能会降低性能,
除非你正确的实现了你的功能。
如果在UI线程中执行网络访问或者数据库查询这种长时间的操作,
会阻塞整个UI线程。
当线程被阻塞时,UI线程不会再分发事件,也不会重绘控件。
当程序阻塞UI线程5s时,系统会ANR。
安卓的UI控件不是线程安全的,所以你不能从工作线程更改UI。
安卓单线程模型使用的两条规则:

  1. 不要锁死UI线程
  2. 不要在UI线程以外的线程更新UI。

工作线程(Worker threads)

由于安卓系统的单线程模型,保证应用程序的UI响应至关重要。
如果你要执行的操作不是立即能完成的,
那应该确保不是即时完成的工作在后台或者工作线程。

为了修复这个问题,安卓系统提供了以下方法来在非UI线程中更新UI:

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    public void onClick(View v) {
    new Thread(new Runnable() {
    public void run() {
    // a potentially time consuming task
    final Bitmap bitmap =
    processBitMap("image.png");
    mImageView.post(new Runnable() {
    public void run() {
    mImageView.setImageBitmap(bitmap);
    }
    });
    }
    }).start();
    }

这种实现方式是线程安全的:
后台操作是另外的非UI线程,
而ImageView始终从UI线程更新处理。

然而,由于操作的复杂度不断增长,这种方式的代码越来越复杂并且难以维护。
为了应对复杂的交互,你可以在工作线程使用Handler来发送Message到UI线程。
还有一个更好的方式,就是使用AsyncTask,异步任务简化了工作线程和UI线程的交互。

使用AsyncTask

AsyncTask允许你的交互与后台工作异步。
在工作线程进行操作,然后在主线程发布运行结果。

要使用AsyncTask必须继承它,并实现doInBackground()方法,
这个方法在后台的线程池中运行。
想要更新UI,必须实现onPostExecute()方法,
这个方法将doinbackground的结果传递到主线程,
从而安全的更新UI。
通过调用execute()方法来运行任务。

线程安全的方法(Thread-safe methods)

在某些情况下,需要从多个线程调用实现方法,一次必须将其编写称为线程安全的。
这主要适用于可以远程调用的方法,例如绑定服务中的方法。
当在IBinder中调用的方法的源于IBinder运行的相同进程中的方法时,
该方法在调用者的线程中执行。
然而,当方法调用源于另一个进程时,
该方法在从系统维护在与IBinder相同的进程(不在进程的UI线程中执行)的线程池中选择的线程中执行。
例如,虽然服务的进程的UI线程将调用一个服务的onBind()方法,
但是在onBind()返回的对象中实现的方法(例如,实现RPC方法的子类)将从池中返回,
因为服务可以有多个客户端,
所以多个池线程可以同时使用相同的IBinder方法。
因此,IBinder方法必须实现为线程安全。

类似地,ContentProvider可以接收源自其他进程的数据请求。
虽然ContentResolver和ContentProvider类隐藏了进程间通信如何管理的细节,
但是ContentProvider方法会响应这些请求
(query(),insert(),delete(),update()和getType())
被来自ContentProvider进程中的线程池调用,
而不是进程的UI线程。
因为这些方法可能会同时从任意数量的线程调用,
所以它们也必须被实现为线程安全的。

进程间通信

安卓系统提供了使用remote procedure calls (RPCs)的进程间通信(IPC)机制,
一个方法被activity或者其他应用破嗯的组件调用,但是在另一个进程中执行,
最后返回给调用这结果。
这需要将该方法嗲用及其数据分解为操作系统可以理解的级别,
将它从本地进程和地址空间发送到运城进程和地址空间,
然后组合并重启本次调用。
返回值以反方向传输。
安卓系统提供这些IPC事物的所有接口。

要执行IPC,应用程序必须使用bindService()绑定到service。