`

Processes and Threads

阅读更多

当一个应用程序组件启动,并且应用程序没有任何其它组件在运行,Android系统为应用程序启动一个新的Linux process单线程。默认情况下,同一个应用程序中的所有组件运行在相同的process和thread中(叫做“main”thread)。如果一个应用程序组件启动并且这个应用程序已经存在一个process(因为这个应用程序中有其他的组件存在着),于是这个组件在相同的process中被启动并使用相同的thread。然而,你可以安排你的应用程序中不同的组件运行在不同的processes中,并且你可以为你的任何process创建额外的threads。

 

这个文档讨论的是一个Android应用程序中的processes和threads如何工作。

 

Processes

 

 

默认的,同一个应用程序中的所有组件运行在同一个process中并且大部分应用程序不应该改变它。然而,如果你发现你需要控制某个组件属于哪个process,你可以在一个manifest文件中这样做。

 

每种类型的组件标签在manifest中的入口--<activity>,<service>,<receiver>,和<provider>--支持一个android:process属性能够用来指定一个组件运行在一个指定的process上。你也可以设定android:process属性,这样不同应用程序的组件可以运行在同一个process中--假如应用程序有相同的Linux用户ID并且有相同的认证签名。

 

<application>标签也支持一个android:process属性,来设置一个默认值,应用在所有的组件上。

 

Android可能在某一时刻关闭process,当内存低和其他的processes要求更快地位用户服务。运行在process的应用程序组件,当process被杀死的时候,这些组件因此被销毁。当那些组件又有工作要做的时候,为了这些组件,一个process被再次启动。

 

当决定要杀死哪个process,Android系统权衡它们对用户的相对的重要性。例如,相对于一个拥有用户可见的activity的process,一个拥有用户在屏幕上不可见的activity更容易被系统关闭。决定是否关闭一个process,因此依赖于运行在process中的组件的状态。用来决定哪个processed被终结的规则在下面被讨论。

 

 

Process lifecycle

Android系统尝试尽可能长时间地维护一个应用程序process,但是最终需要移除旧的processes来为新的或者更重要的processes回收内存。为了决定哪个processes被保留,哪个被杀死,系统根据运行在process中的组件和这些组件的状态将每个process放进一个“importance hierarchy”。最低重要性的processes首先被淘汰,再者就第二低重要性的,以此类推,来回收系统资源。

 

 

在“importance hierarchy”中有5个级别。下面的列表按照重要性依次展现了processes的不同类型(第一个process是最重要的并且最后被杀死):

 

1.Foreground process

 

一个process是否需要要看用户当前在做什么操作。如果下面的任何条件为true,那么这个process被认为是在前台foreground的。

 

 

 

 

 

  • 它拥有一个 Service ,这个 Service 被绑定到用户正在交互的这个activity上。

 

 

 

 

 

 

 

 

 

 

 

通常来说,只有很少的前台foreground processes存在于任何给定的时间。他们被杀死只能作为最后的手段--如果内存太低以至于他们根本不能继续运行。通常来说,就在那时,设备已经到达了一个内存临界点,因此杀死一些前台foreground processes被用来保证用户接口能响应。

 

2.Visible process

 

一个process没有任何的前台foreground组件,但是仍然可以影响用户在屏幕上看到的东西。一个process被认为是可见的,如果下面的情况任何一个为true的话:

 

 

  • 它有一个不在前台foreground的Activity,但是对用户仍然可见(它的onPause()方法已经被调用了)。这可能会发生,例如,如果前台foreground activity启动了一个dialog,允许之前的activity被看见。

 

 

 

  • 它有一个Service,这个Service被绑定到一个可见的(或者前台foreground)activity。

 

 

一个可见的process非常重要,不会被杀死除非为了保持所有的前台foreground process的运行。

 

 

3.Service process

 

一个process,运行着一个由方法启动的service,并且没有陷入两个更高的categories中的任意一个。尽管服务进程没有直接联系着用户看到的任何东西,他们通常做着用户关心的事情(例如在后台播放音乐或从网络上下载数据),因此系统让它们保持运行除非没有足够的内存来维持所有的前台foreground和可见visible processes。

 

4.Background process

 

一个process拥有一个当前用户不可见的activity(这个activity的onStop()方法已经被调用了)。这些processes没有直接影响到用户体验,系统能够在任何时间杀死它们来为一个foreground,visible或者service process回收内存。通常有许多的后台background processes在运行,因此它们被保持在LRU(least recently used)列表中来确保最近被用户所看到的activity所在的process最后被杀死。如果一个activity正确地实现了它的生命周期方法,并保存了它的当前状态,杀死它的进程对用户体验来说不会有一个可见的影响,因为当用户导航回到这个activity,这个activity恢复所有它的可见状态。参考Activities文档获取更多关于保存和恢复状态的信息。

 

5.Empty process

 

一个process没有任何激活的应用程序组件。保持这种类型的process活着的唯一的原因是为了缓存的目的,来提高一个组件下一次启动它的时间。系统经常杀死这些processes为了在process缓存和基本的内核缓存之间平衡资源。

 

Android排列一个process是根据这个process的最高的级别来的,也就是这个process中当前被激活的组件的重要性。例如,如果一个process拥有一个service和一个visible activity,这个process是按照visible process来排名的,而不是一个service process。

 

另外,一个process的排名可能会增加因为其它的应用程序依赖于它--一个为其它process服务的process永远不会比它所服务的process排名低。例如,如果一个process A中的content provider为process B中的一个client服务,或者如果process A中的一个service被绑定到process B中的一个组件,process A总是被认为重要性最少不会低于process B。

 

因为一个运行一个service的process排名比一个拥有background activities的process高,一个activity开始一个长时间运行的操作可能要比启动一个service操作要好,而不是简单的创建一个工作线程--特别地如果这个操作可能比这个activity长久。例如,一个正在上传一张图片到一个web站点的activity应该启动一个service来执行上传操作,这样即时如果用户离开activity,上传能够在后台继续。使用一个service保证了操作最少有“service process”优先权,无论activity发生了什么。这也是相同的原因,broadcast receivers应该雇佣services而不是简单地在一个thread中加入耗时的操作。

 

Threads

当一个应用程序被启动,系统为应用程序创建一个执行线程,叫做“main”。这个thread非常重要因为它负责分发事件给用户接口widget,包括绘制事件。他也是负责你的应用程序与Android UI工具包中的组件(组件来自android.widgetandroid.view包)交互的thread。这样,main thread也叫做UI thread。

 

系统没有为一个组件的每个实例创建一个不同的线程thread。所有的组件运行在在UI thread中实例化的同一个process中,系统调用从这个thread中分发出的每个组件。因此,响应系统回调的方法(例如onKeyDown()方法响应用户动作或者一个生命周期回调方法)总是运行在process的UI thread中。

 

例如,当用户触摸屏幕上的一个按钮,你的应用程序的UI thread分发这个触摸事件给widget,依次设置它的按下状态和提交一个更新请求到事件队列中。UI thread处理这个请求并通知要重新绘制的widget。

 

当你的应用程序彻底的工作来响应用户交互,这个单线程模型会展现出不好的表现除非你正确地实现了你的应用程序。特别地,如果任何事情都发生在UI thread中,执行一个长时间的操作,例如网络访问或者数据库查询将会中断整个UI。当thread被中断,没有事件能够被分发,包括绘制事件。在用户看来,应用程序假死了。更糟的是,如果UI thread被中断了几秒(一般是5秒)用户就会看到一个不好的“application not responding”(ANR)dialog。用户可能决定退出你的应用程序或者卸载你的应用程序如果他们不开心的话。

 

额外的,Android UI 工具包不是线程安全的。因此,你千万不能通过一个工作线程去操作你的UI--你必须在UI thread中去操作用户接口。这样,Android的单线程模型有两个简单的规则:

 

1.不要中断UI thread

2.不要从UI thread外部去访问Android UI工具包。

 

 

Worker threads

 

由于上面讨论的单线程模型,它对响应你的应用程序的UI,不卡住UI thread十分重要。如果你要执行的操作不是立刻要执行的,你应该让它们在不同的线程中进行(“background”或者“worker”threads)。

 

例如,下面是一个点击监听从一个不同的thread下载一张图片并在一个ImageView中显示它的一些代码:

 

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

首先,这看上去似乎工作正常,因为它创建了一个新的thread来处理网络操作。然而,它违反了当线程模型的第二条规则:不要从UI thread外部去访问Android UI工具包--这个例子从工作线程中去修改一个ImageView,而不是UI thread。这会导致未定义和未捕获的行为,跟踪它会很难并很耗时。

 

为了修复这个问题,Android提供了一系列的方法来从其它线程来访问UI thread。下面是一些能够帮助的方法:

 

 

 

 

 

 

 

 

 

 

例如,你可以使用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 thread中进行操作的。

 

然而,随着操作的复杂性上升,这种类型的代码对维护来说会变得复杂和困难。为了用工作线程处理更加复杂的交互,你可以考虑在你的工作线程中使用一个Handler来处理从你的UI thread递交来的消息。可能最好的解决方式,尽管,是继承自AsyncTask类,简化了工作线程与UI的交互。

 

 

Using AsyncTask

AsyncTask允许你在你的用户接口上执行一个异步的工作。它在一个工作线程中执行中断操作并且将结果公布给UI thread,而不需要你自己来处理threads和/或handlers。

 

 

使用它,你必须继承AsyncTask并实现doInBackground()回调方法,它是运行在一个后台线程池中。为了更新你的UI,你需要实现onPostExecute()方法,它处理doInBackground()方法递交过来的结果并运行在UI thread中,因此你可以安全地更新你的UI。你可以在你的UI thread中调用execute()方法来运行这个task。

 

例如,你可以以这种方式使用AsyncTask实现之前的例子:

 

 

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文档,完全了解下如何使用这个类,但是这里有一个它如何工作的快速概览:

 

 

  • 你可以使用泛型指定参数的类型,进度值,和task的final值。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

  • 你可以在任何thread中随时取消task。

 

注意:当使用工作线程你可能会遇到另一个问题,你的activity因为一个runtime configuration change(例如当用户改变了屏幕的方向)被不按预期地重新启动了,这可能会销毁你的工作线程。参看Shelves示例应用程序的源码,获知如何在这种重启情况下保持你的task和当activity被销毁的时候如何适当地取消task。

 

 

 

Thread-safe methods

在某些情况下,你实现的方法可能被超过一个的线程调用,因此必须被写成线程安全的。

 

 

方法可以被远程调用,这是正确的--例如在一个bound service中的方法。当一个方法在一个IBinder中实现,调用这个方法的调用者和这个IBinder运行在同一个process中,那么这个方法在调用者的thread中被执行。然而,当调用者在另一个process,这个方法在一个线程池中选中的一个线程中被执行,系统以IBinder,在同一个process中维护着这个线程池。(这里翻译有点问题: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)(它不是在process的UI thread中被执行)例如,然而一个service的onBind()方法可能会在这个service的process的UI thread中被调用,onBind()方法返回的对象所实现的方法(例如,是个实现RPC方法的子类)可能会被池中的线程所调用。因为一个service能够有超过一个的client,多于一个的池线程可以同时雇用同一个IBinder中的方法。IBinder中的方法因此必须被以线程安全的方式被实现。

 

同样地,一个content provider能够接收到来自其它processes的数据请求。尽管ContentResolverContentProvider类隐藏了进程间交流interprocess communication是如何被管理的详细。ContentProvider中的方法响应了这些请求--方法query()insert()delete()update()getType()--在这个content provider的process中的线程池被调用,而不是process中的UI thread。因为这些方法可能同时被许多线程调用,他们也必须以线程安全的方式被实现。

 

 

Interprocess Communication

 

 

Android使用remote procedure calls(RPCs)来实现interprocess communication(IPC),也就是一个方法被一个activity或者其它的应用程序组件所调用,但是在远程被执行(在其他的process),并返回给调用者。这需要将一个方法调用和它的数据分解为操作系统能够明白的级别,将它从本地process和地址空间发送到远程process和地址空间,然后在那边再对调用重装和再制定。返回值被传递到相反的方向。Android提供所有的代码来执行这些IPC传输,因此你可以聚焦在定义和实现RPC编程接口。

 

你的应用程序必须使用bindService()绑定一个service来实现IPC。更多信息参考Services开发指导。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics