了解 Android 的进程和线程

前言:本文所写的是博主的我的看法,若有错误或者不恰当之处,欢迎私信博主,加以改正!原文连接demo连接java

当某个应用组件启动且未运行其余组件时, Android 系统会使用单个执行线程为应用启动新的 Linux 进程。默认状况下,同一应用的全部组件在相同的进程和线程(主线程)中运行。若是某个应用组件启动且应用已存在进程(存在其余组件),则该组件会在此进程内启动并使用相同的执行线程,可是你能够安排应用的其余组件在单独的进程中运行,并为任何进程建立额外的线程。android

进程

默认状况下,同一应用的全部组件均在相同的进程中运行,且大多数的应该都是如此。可是,若是须要控制某个组件所属的进程,则能够在清单文件中执行此操做。git

各种组件元素的清单文件条目 <activity><service><receiver><provider> 均支持 android:process 属性,该属性能够指定组件应该运行在哪一个进程。能够设置此属性,使每一个组件在各自的进程中运行,或者使一些组件共享一个进程,而其余组件则不共享。此外,还能够设置 android:process ,应用共享具备相同的 Linux 用户 ID 和相同的证书签署,使不一样应用的组件在相同的进程中运行。github

此外,<application> 元素还支持 android:process 属性,能够设置适用于全部组件的默认值。数据库

若是内存不足,而其余为用户提供更紧急服务的进程又须要内存时,Android 可能会决定在某一时刻关闭某一进程。在被终止进程中运行的应用组件也会随之销毁。当这些组件须要再次运行时,系统将为它们重启进程。编程

决定终止哪一个进程时,Android 系统将权衡它们对用户的相对重要程度。例如,相对于托管可见 Activity 的进程而言,它更有可能关闭托管屏幕上再也不可见的 Activity 进程。所以,是否终止某个进程取决于该进程所运行组件的状态。缓存

进程的生命周期

Android 系统将尽可能长时间的保持应用进程,但为了新建进程或者运行更重要的进程,最终须要移除旧的进程来回收内存。为了肯定保留或终止哪些进程,系统会根据正在运行的组件及这些组件的状态,将每一个进程按重要性层次结构进行划分,必要时,系统会首先清除重要性最低的进程,而后是重要性略逊的进程,以此类推,来回收系统资源。安全

重要性层次结构一共有5级。下面的列表按照重要程度列出了各种进程(第一个进程最重要,会是最后被终止的进程):网络

  1. 前台进程app

    用户当前操做所必需的进程。若是一个进程知足如下任意一个条件,即视为前台进程:

    • 托管于用户正在交互的 Activity (已调用 Activity 的 onResume() 方法)
    • 托管于某个 Service,后者绑定到用户正在交互的 Activity
    • 托管于正在前台运行的 Service (服务已调用 startForeground())
    • 托管于正在执行一个生命周期回调的 Service (onCreate()、onStart() 或 onDestroy())
    • 托管于正在执行 onReceive() 方法的 BroadcastReceiver

    一般在任意给定时间前台进程都为数很少。只有在内存不足以支持它们同时继续运行,不得已的状况下,系统才会终止它们。此时,,设备每每已达到内存分页状态,须要终止一些前台进程来确保用户界面正常响应。

  2. 可见进程

    没有任何前台组件、但会影响用户在屏幕上所见内容的进程。若是一个进程知足如下任一条件,即视为可见进程:

    • 托管不在前台、但仍对客户可见的 Activity (已调用其 onPause()方法)。例如,若是前台 Activity 启动一个对话框,容许在其后显示一个 Activity ,则有可能会发生这种状况。
    • 托管绑定到可见(或前台) Activity 的 Service
      可见进程被视为极其重要的进程,除非是为了维持全部前台进程同时运行而必须终止,不然系统不会终止这些进程。
  3. 服务进程

    正在运行已使用 startService() 方法启动的服务不属于上面的两个更高级别的进程。尽管服务进程与用户缩减内容没有直接关联,但他们一般执行一些用户关心的操做(例如,在后台播放音乐或从网络下载数据)。所以除非内存不足以维持全部前台进程和可见进程同时运行,不然系统会会让服务进程保持运行状态。

  4. 后台进程

    包含目前对用户不可见的 Activity 的进程(已调用 Activity 的 onStop() 方法)。这些进程对用户体验没有直接影响,系统随时能够终止它们,以回收内存供前台进程、可见进程或服务进程使用。一般会有不少后台进程在运行,他们都会保存在LRU(最近最少使用)列表中,确保包含用户最近查看的 Activity 的进程会是被最后终止的一个。若是某个 Activity 正确的实现了生命周期方法,而且保存了其当前状态,则终止进程不会对用户体验产生明显影响,由于当用户导航回该 Activity 时,Activity 会恢复其全部可见的状态。能够参考浅谈Android Activity的生命周期

  5. 空进程
    不含任何活动应用组件的进程。 保留这种进程的惟一目的是用做缓存,以缩短下次在其运行组件所需的启动时间。为使整体系统资源在进程缓存和底层内了缓存之间保持平衡,系统每每会终止这些进程。

根据进程中当前活动组件的重要程度, Android 会将进程评定为它可能达到的最高级别。例如,若是某些进程托管这服务和可见 Activity ,则会将进程评定为可见进程,而不是服务进程。

此外,一个进程的级别可能会因其余进程对他的依赖而有所提升,即服务于另外一个进程的进程,它的级别永远不会低于它所服务的进程。例如,若是进程 A 中的内容提供程序为 B 进程的客户端提供服务,或者进程 A 中的服务绑定到进程 B 中的组件,则进程 A 始终被视为至少与 B 一样重要。

因为运行服务的进程其级别高于托管在后台的 Activity 的进程,所以启动长时间运行操做的 Activity 最好为该操做启动服务,而不是简单的建立工做线程,当操做有可能比 Activity 更加持久时尤要如此。例如,正在将图片上传到网站的 Activity 应该启动服务来执行上传,这样一来,即便用户退出 Activity ,仍可在后台继续执行上传操做。使用服务能够保证,不管 Activity 发生什么状况,该操做至少具有“服务进程”优先级。同理,广播接收器也可使用服务,而不是简单的将耗时操做放入工做线程中。

线程

应用启动时,系统会为应用建立一个名为“主线程”的执行线程。此线程很是重要,它负责将事件分派给相应的用户界面小部件,其中包括绘图事件。此外,他也是应用于 Android UI 工具包组件(来自 android.widget 和 android.view 软件包的组件)进行交互的线程。所以,主线程也有时被称为 UI 线程。

系统不会为每一个组件建立单独的线程。运行在同一进程的全部组件均在 UI 线程中实例化,而且对每一个组件的系统调用均由该线程进行分派。所以响应系统回调的方法(例如,报告用户操做的 onKeyDown() 或者生命周期回调方法)始终在进程的 UI 线程中运行。

例如,当用户触摸屏幕上的按钮时,应用的 UI 线程将会将触摸时间分派给小部件,而小部件反过来又设置其按下状态,并将失效请求发布到事件队列中。UI 线程从队列中取消该请求并通知小部件重绘自身。

在应用执行繁重的任务响应用户交互时,除非正确实现应用,不然这种单线程模式可能会致使性能低下。若是 UI 线程须要处理全部任务,则执行耗时操做(如网络访问或数据库查询)将会阻塞整个 UI 。一旦线程被阻塞,将没法分派任何事件,包括绘图事件。从用户角度来看,应该显示为挂起。更糟糕的状况是若是 UI 线程被阻塞超过几秒钟(目前大约是5秒钟),用户就会看到一个“应用无响应”( ANR )对话框。若是引发用户不满,他们可能会决定退出并卸载应用。

此外,Android UI 工具包并不是线程安全工具包,因此不能经过工做线程操纵 UI,只能经过 UI 线程操纵用户界面。所以 Android 的单线程模式必须遵照两条规则:

  1. 不要阻塞 UI 线程
  2. 不要在 UI 线程以外访问 Android UI 工具包

工做线程

在单线程模式下,要保证应用 UI 的响应能力,关键是不能阻塞 UI 线程。若是执行的操做不能很快完成,则应该确保他们在单独的线程(后台或者工做线程)中运行。

例如,下面演示了一个点击监听器从单独的线程下载图片并将其显示在 ImageView 中:

@Override
   public void onClick(View v) {
       new Thread(new Runnable() {
           @Override
           public void run() {
               final Bitmap bitmap = loadImageFromNetwork(url);
               showImage.setImageBitmap(bitmap);
           }
       }).start();
   }复制代码

这段代码看起来彷佛运行良好,由于它建立了一个新的线程来处理网络操做。可是它违背了单线程模式的第二条规则:不要在 UI 线程以外访问 Android UI 工具包,此示例从工做线程(而不是 UI )线程修改了 ImageView 。这个可能致使出现不明确,不可预见的行为,可是要跟踪这个行为既困难又费时。

未解决此问题,Android 提供了几种途径从其余线程访问 UI 线程。如下列出几种有用的方法:

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

例如你能够经过使用 Activity.runOnUiThread(Runnable) 方法修复上面的代码:

@Override
   public void onClick(View v) {
       new Thread(new Runnable() {
           @Override
           public void run() {
               final Bitmap bitmap = loadImageFromNetwork(url);
               MainActivity.this.runOnUiThread(new Runnable() {
                   @Override
                   public void run() {
                       showImage.setImageBitmap(bitmap);
                   }
               });

           }
       }).start();
   }复制代码

上面的实现属于线程安全型:在单独的线程中完成网络操做,而在 UI 线程中操做 ImageView。

可是随着操做的复杂,这类代码变得难以维护,要经过工做线程实现更复杂的交互,能够考虑在工做线程中使用 Handler 处理来自 UI 线程的消息。固然更好的解决方案是扩展 AsyncTask 类,此类简化了与 UI 线程进行交互所须要执行的工做线程任务。

使用 AsyncTask

AsyncTask容许用户对用户界面执行异步操做。他会先阻塞工做线程中的操做,而后在 UI 线程中发布结果,而你无需亲自处理线程和处理程序。

要使用它,必须建立 AsyncTask 的子类,并实现 odInBrackground() 回调方法,该方法将在后台线程池中运行,须要更新 UI 则要实现 onPostExecute(),传递 odInBrackground() 返回的结果并在 UI 线程中运行,使之更安全地更新 UI,能够经过从 UI 线程中调用 execute() 来运行任务。

例如,下面使用 AsyncTask 来实现上述的示例:

Activity 里调用 execute() 方法

@Override
            public void onClick(View v) {
                new DownloadImageTask(showImage).execute(url);
            }复制代码

继承 AsyncTask 实现异步加载

public class DownloadImageTask extends AsyncTask<String, Void, Bitmap> {

    private ImageView mImageView;
    public DownloadImageTask() {
    }

    public DownloadImageTask(ImageView imageView) {
        mImageView = imageView;
    }
    @Override
    protected Bitmap doInBackground(String... params) {
        return DownImageUtil.getInstance().loadImageFromNetwork(params[0]);
    }

    @Override
    protected void onPostExecute(Bitmap bitmap) {
        if (mImageView != null) {
            mImageView.setImageBitmap(bitmap);
        }
    }
}复制代码

如今 UI 是安全的,代码也获得简化,任务分解成了两部分:一部分在工做线程内完成,另外一部分在 UI 线程内完成。

下面简单的介绍 AsyncTask 的工做方法:

  • 可使用泛型指定参数类型、进度值和任务最终值
  • 方法 doInBackground() 会在工做线程上自动执行
  • onPreExecute() 、onPostExecute() 和 onProgressUpdate() 均在 UI 线程中调用
  • doInBackground() 返回值将发送到 onPostExecute()
  • 能够随时在 doInBackground() 中调用 publishProgress() ,以在 UI 线程中执行 onProgressUpdate()
  • 能够随时取消任何线程中的任务

注意:使用工做线程时有可能遇到另外一个问题,即:运行时配置变动(例如,用户更改了屏幕方向),致使 Activity 意外重启,这可能会销毁工做线程。

线程安全方法

在某些状况下,你实现的方法可能会从多个线程调用,所以在编写这些方法时必须确保其知足线程安全的要求。

这一点主要适用于能够远程调用的方法,如绑定服务中的方法。若是对 IBinder 中所实现方法的调用源自运行 IBinder 的同一进程,则该方法在调用方的线程中执行。可是,若是调用源自其余进程,则该方法将从线程池中选择某个线程中执行(而不是在进程的 UI 线程中执行),线程池由系统与 IBinder 相同的进程中维护。例如,服务的 onBind() 方法从服务的 UI 线程中调用,在 onBind() 返回的对象中实现的方法仍会从线程池中的线程调用。因为一个服务能够有多个客户端,所以可能会有多个线程池同一时间使用同一个 IBinder 方法。因此 IBinder 方法实现必须为线程安全方法。

一样,内容提供程序也能够接受来自其余进程的数据请求。尽管 ContentResolver 和 ContentProvider 类隐藏了如何管理进程间通讯的细节,但响应这些请求的 ContentProvider 方法(query()、insert()、delete()、update() 和 getType() 方法)将从内容提供程序所在进程的线程池中调用,而不是从进程的 UI 线程调用。因为这些方法同时从任意数量的线程调用,所以它们必须实现为线程安全方法。

进程间通讯

Android 利用远程过程调用(RPC)提供了一种进程间通讯(IPC)机制,经过这种机制,由 Activity 或其余应用组件调用方法将(在其余进程中)远程执行,而全部结果将返回给调用方。这要求把方法调用及其数据分解至操做系统能够识别的程度,并将其从本地进程和地址空间传输至远程进程和地址空间,而后在远程进程中从新组装并执行该调用。而后返回值将沿相反方向传输回来。Android 提供了执行这些 IPC 事务所需的所有代码,所以只须要集中精力定义和实现 RPC 编程接口便可。

须要执行 IPC,必须使用 bindService() 将应用绑定到服务上,想了解详细的信息,能够参考
浅谈 Android Service

相关文章
相关标签/搜索