Google API 翻译:Processes and Threads

水平有限,请多指正。
———————————————————————

启动应用程序组件时,如果该程序没有其他组件在运行,Android系统就会为该程序启动一个新的Linux进程,该进程只有一条执行线程。同一程序的所有组件都默认在这条线程(称为”主线程”)中运行。当启动一个应用程序组件,且该程序已经持有一个进程(因为该程序的其他组件已经存在),新的组件将在同一进程的同一执行线程中启动。不过,你可以将程序的各个组件放进不同的进程中,也可以为每个进程增加新线程。

Processes

默认情况下,同一程序的所有组件都在同一进程中运行,绝大多数应用程序都应该遵循这一点。不过,如果要把某个特定的组件放入另一个进程,你可以在manifest文件中声明。

manifest中的每个组件——<activity>, <service>, <receiver>, 以及<provider>——都支持android:process属性,该属性声明当前组件将运行于哪个进程。通过设置这个属性,你可以让每个组件运行在一个单独的进程中,或者控制某些组件共享同一个进程。甚至,通过设置android:process属性,可以让不同程序在同一进程中运行——只要这些程序使用相同的Linux user ID,并且使用具有相同证书的签名。

<application>也支持android:process属性,用于为所有组件设置一个默认值。

当其他前台进程需要内存,但内存又不足的时候,系统可能会杀死某些后台进程。运行于被杀进程中的程序组件将被销毁。

如果需要,系统会权衡每个进程对用户的重要性,以决定杀死哪个进程。例如,一个进程的activity均不可见,那么它被杀死的可能远大于activity可见的进程。因此,是否杀死一个进程,取决于运行在该进程中的组件的状态,具体如何取舍,下面将会讨论。

Process lifecycle

系统会尽可能维系程序进程,但免不了要移除旧的进程,为新的,或者更重要的进程取得内存。基于进程中运行的组件以及这些组件的状态,系统将每个进程放入一个“优先级结构”中,以此决定哪个将被杀死,哪个将被保留。当系统资源不足时,优先级最低的进程第一个被消除,接着是优先级倒数第二低的,以此类推。

上述规则中共有5个优先级。下表展示了优先级从高到低的进程类型:

  1. 前台进程(Foreground process)
    前台进程是用户当前工作所需要的进程。一个进程如果满足以下任意一点,则认为是前台进程:

    • 进程中有一个正在与用户交互的Activity(该Activity的onResume()方法被调用)。
    • 进程中有一个Service,该Service与正与用户交互的Activity绑定。
    • 进程中有一个Service在前台运行——该Service调用了startForeground().
    • 进程中有一个Service正在执行生命周期回调函数(onCreate(), onStart(), 或者onDestroy()).
    • 进程中有一个BroadcastReceiver正在执行onReceive()方法。

    通常,任意时刻都只有很少的前台进程,它们很难会被杀死——只有系统内存严重不足,这些前台进程才无法全部继续运行。这种情况下,设备需要杀死某些前台进程以响应用户界面。

  2. 可见进程(Visible process)

    可见进程并没有任何前台组件,但仍能影响用户在屏幕上看到的东西。一个进程如果满足以下任意一点,则认为是可见进程:

    • 进程中有一个不在前台的Activity,但仍对用户可见(其onPause()方法被调用),例如,当前台Activity启动了一个dialog,该activity仍然可见。
    • 进程中有一个Service,绑定在一个可见(或前台)Activity上。

    可见进程十分重要,只有在必要的时候才会被杀死,以保证前台进程正常运行。

  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. 尽管服务进程与用户所见没有直接联系,但通常这些进程都在做一些用户关心的事务(例如后台播放音乐或执行下载任务),因此,只有在没有足够内存维持前台和可见进程时,服务进程才有可能被杀死。

  4. 后台进程(Background process)

    后台进程持有的activity当前对用户不可见(activity的onStop()方法被调用)。这类进程对用户体验没有直接影响,系统可以在任何时候杀死他们,以保证前台、可见、服务进程有足够的内存资源。通常会有很多后台进程同时运行,这些后台进程都被列在一个LRU(最近使用)清单中,若某个进程的activity最近才被用户可见过,那么该进程将是清单中最晚被杀死的。若一个activity正确实现了其生命周期方法,并保存了自己的状态,那么,当这个activity所在进程被杀死,将不会对用户体验带来可见的影响,因为用户回到该activity时,activity会恢复其所有可视化状态。(关于保存及恢复状态,参见Activity文档)。

  5. 空进程(Empty process)

    空进程是指那些没有任何active程序组件的进程。这种进程存活的唯一目的就是为了缓存,当下一次有组件需要在其中运行,减少组件的启动时间。系统经常会杀死这类进程,以此来保持总体系统资源在进程缓存和底层内核缓存两者之间的均衡。

基于进程中所有active组件的重要性,Android系统将会给每个进程分配其所能达到的最高优先级。例如,一个进程持有一个service和一个可见activity,该进程就属于可见进程,而不是服务进程。

另外,如果其他进程依赖某个进程,该进程的优先级可能会提高——为进程A服务的进程不可能比进程A的优先级低。例如,进程A有一个content provider为进程B的某个客户端服务,或者进程A有一个service绑定于进程B的某个组件上,那么,进程A至少和进程B的优先级相同(而不会低于B)。

由于服务进程的优先级高于后台进程,当activity需要执行耗时操作,最好使用service,而不是简单地创建一条工作线程——尤其是耗时操作可能比activity本身存活时间更久的情况。例如,activity要上传一张图片到某个网站,应该启动service来完成上传工作,这样的话,当用户离开该activity时,上传仍能进行。无论activity发生什么,使用service能保证操作至少有服务进程的优先级。基于相同理由,broadcast也应该使用service,而不是简单地把耗时操作丢进一个线程。

Thread

进入程序时,系统将为程序创建一条执行线程,称为”主线程”。这条线程十分重要,它负责把事件分发到正确的UI控件上,包括绘制事件。另外,程序与UI控件的交互也是在这条线程中完成的。因此,主线程也被称为UI线程。

系统不会为每个组件都创建一条线程,同一进程中运行的所有组件都在UI线程中初始化,并且对每个组件的系统调用都由UI线程分发。因此,所有响应系统调用的方法(例如onKeyDown())都在进程的UI线程中执行.

举例来说,用户触碰了屏幕中的一个按钮,程序的UI线程把这一触碰事件分发到对应控件,控件将先设置其pressed状态,并post一个刷新请求到事件队列中。UI线程从队列中取出这个请求,并通知控件重绘自身。

当程序需要执行繁重的工作来响应用户交互,除非程序实现很完美,否则这种单线程模式将不堪重负。尤其是,当UI线程承担了所有工作,包括网络访问和数据库查询等耗时操作,整个UI线程将被阻塞。这时,没有任何事件能够被分发下去,包括绘制事件。在用户看来,程序就像是卡死了。更糟的是,如果UI线程阻塞的时间够长(当前版本大约是5秒),用户将看到一个”程序无响应”(ANR)提示框。接着,用户可能不开心了,于是退出你的程序并把它卸载了。

另外,android的UI工具集并非线程安全。因此,你不能把UI操作放进工作线程里去——所有界面操作必须在UI线程中完成。你需要遵循以下两点:

  1. 不要阻塞UI线程;
  2. 不要在非UI线程操作界面。

Worker threads

如上所述,保证UI线程不被阻塞至关重要。如果有耗时操作,应该放入一条单独的线程中执行(“后台”或”工作”线程)。

例如,下面程序来自一个监听器,点击时在新线程中下载一张图片,并在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();
}

乍一看似乎没有问题,因为它创建了一条新线程来处理网络操作。但是,它违反了单线程模式的第二条规则:不要在非UI线程操作界面——该例在工作线程操作了ImageView。这可能引发未定义或不可预知的问题,并且很难定位跟踪。

为了解决这类问题,Android提供了几种方法,在其它线程访问UI线程。例如:

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

你可以用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线程中完成。

不过,随要完成的操作复杂度增加,这类代码也变得越来越复杂且难于维护。如果要在工作线程中处理更复杂的操作,你应该考虑使用Handler来处理UI线程递送的消息。但,最优的方案也许是继承AsyncTask。如果一个工作线程需要与UI线程交互,使用AsyncTask可以简化操作。

Using AsyncTask

AsyncTask让你能在UI中执行异步工作。它在工作线程中执行阻塞操作,然后在UI线程中发布结果,使你不必亲自处理thread和handler了。

要使用AsyncTask,必须继承AsyncTask并实现doInBackground()方法,该方法将在后台线程池中运行。要更新UI,则需要实现onPostExecute(),该方法将doInBackground()的结果传到UI线程,并在UI线程中运行,以保证安全地更新UI。实现这些方法后,就可以在UI线程中调用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,应该去参考AsyncTask的文档,这里仅简要概括它是如何工作的:

  • 你可以用泛型指明参数类型,进度类型,以及任务最终结果的类型。
  • doInBackground()方法自动在工作线程中执行。
  • onPreExcute(),onPostExcute(),onProgressUpdate()均在主线程运行。
  • doInBackground()的返回值将传入onProgressUpdate()中。
  • 任何时候,都可以在doInBackground()中调用publishProgress(),让UI线程执行onProgressUpdate()方法。
  • 可以在任何时间,任何线程中取消异步任务。

警告:当使用工作线程时,可能遇到另一个问题:由于runtime configuration change(比如用户改变了屏幕方向)引起的activity异常重启,可能使工作线程被销毁。

Thread-safe methods

某些情况下,方法实现可能被不同的线程调用,这种情况下必须做到线程安全。

对于可以被远程调用的方法(做到线程安全)更是如此——例如bound service中的方法。如果IBinder的方法实现和IBinder本身运行在同一进程中,调用该方法,方法会在调用者线程中执行。然而,如果调用者在另一个进程中,方法则会在IBinder所在进程中,由系统维护的线程池中选一个线程去执行(这个线程不会是UI线程)。举个例子,由于service的onBind()方法将被service所在进程的UI线程调用,onBind()将方法返回一个IBinder对象,这个对象中的方法将由线程池调用。由于service可以有多个客户端,所以可能有多个线程池同时执行同一个IBinder方法。因此,IBinder方法必须实现为线程安全的。

与之类似,ContentProvider可以从不同的进程中接受数据请求。尽管ContentResolver和ContentProvider隐藏了管理进程间通信的细节,但ContentProvider中响应这些请求的方法——query(),insert(),delete(),update(),以及getType()——都被ContentProvider所在进程中,线程池的非UI线程调用。这些方法可能被多个线程同时调用,所以必须实现线程安全。

Interprocess Communication

Android提供了一种远程程序调用的IPC机制,方法被Activity或其他程序组件调用,(在另一个进程中)远程执行,然后把结果返回调用者。这需要将方法和方法数据分解为操作系统可以理解的层级,把它们从本地进程和地址空间传到远程进程和地址空间,然后在那里重组重组方法并执行。最后将返回值反向传过来。Android系统已经提供了执行IPC事务的全部代码,你所要做的只是定义和实现远程调用的接口。

要执行IPC操作,程序必须通过bindService()绑定服务。更多信息请参见Service文档。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值