Android 线程与进程

当某个应用组件启动且该应用没有运行其他任何组件时,Android 系统会使用单个执行线程为应用启动新的 Linux 进程。默认情况下,同一应用的所有组件在相同的进程和线程(称为“主”线程)中运行。 如果某个应用组件启动且该应用已存在进程(因为存在该应用的其他组件),则该组件会在此进程内启动并使用相同的执行线程。 但是,您可以安排应用中的其他组件在单独的进程中运行,并为任何进程创建额外的线程。

进程

默认情况下,同一应用的所有组件均在相同的进程中运行,且大多数应用都不会改变这一点。 但是,如果您发现需要控制某个组件所属的进程,则可在清单文件中执行此操作。

各类组件元素的清单文件条目—<activity>、<service>、<receiver> 和 <provider>—均支持 android:process 属性,此属性可以指定该组件应在哪个进程运行。您可以设置此属性,使每个组件均在各自的进程中运行,或者使一些组件共享一个进程,而其他组件则不共享。 此外,您还可以设置 android:process,使不同应用的组件在相同的进程中运行,但前提是这些应用共享相同的 Linux 用户 ID 并使用相同的证书进行签署。

此外,<application> 元素还支持 android:process 属性,以设置适用于所有组件的默认值。

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

决定终止哪个进程时,Android 系统将权衡它们对用户的相对重要程度。例如,相对于托管可见 Activity 的进程而言,它更有可能关闭托管屏幕上不再可见的 Activity 的进程。 因此,是否终止某个进程的决定取决于该进程中所运行组件的状态。 下面,我们介绍决定终止进程所用的规则。

进程生命周期

Android 系统将尽量长时间地保持应用进程,但为了新建进程或运行更重要的进程,最终需要移除旧进程来回收内存。 为了确定保留或终止哪些进程,系统会根据进程中正在运行的组件以及这些组件的状态,将每个进程放入“重要性层次结构”中。 必要时,系统会首先消除重要性最低的进程,然后是重要性略逊的进程,依此类推,以回收系统资源。

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

  1. 前台进程
    用户当前操作所必需的进程。如果一个进程满足以下任一条件,即视为前台进程:
    1> 托管用户正在交互的 Activity(已调用 Activity 的 onResume() 方法)
    2> 托管某个 Service,后者绑定到用户正在交互的 Activity
    3> 托管正在“前台”运行的 Service(服务已调用 startForeground())
    4> 托管正执行一个生命周期回调的 Service(onCreate()、onStart() 或 onDestroy())
    5> 托管正执行其 onReceive() 方法的 BroadcastReceiver
    通常,在任意给定时间前台进程都为数不多。只有在内存不足以支持它们同时继续运行这一万不得已的情况下,系统才会终止它们。 此时,设备往往已达到内存分页状态,因此需要终止一些前台进程来确保用户界面正常响应。

  2. 可见进程
    没有任何前台组件、但仍会影响用户在屏幕上所见内容的进程。 如果一个进程满足以下任一条件,即视为可见进程:
    1> 托管不在前台、但仍对用户可见的 Activity(已调用其 onPause() 方法)。例如,如果前台 Activity 启动了一个对话框,允许在其后显示上一 Activity,则有可能会发生这种情况。
    2> 托管绑定到可见(或前台)Activity 的 Service。
    可见进程被视为是极其重要的进程,除非为了维持所有前台进程同时运行而必须终止,否则系统不会终止这些进程。

  3. 服务进程
    正在运行已使用 startService() 方法启动的服务且不属于上述两个更高类别进程的进程。尽管服务进程与用户所见内容没有直接关联,但是它们通常在执行一些用户关心的操作(例如,在后台播放音乐或从网络下载数据)。因此,除非内存不足以维持所有前台进程和可见进程同时运行,否则系统会让服务进程保持运行状态。

  4. 后台进程
    包含目前对用户不可见的 Activity 的进程(已调用 Activity 的 onStop() 方法)。这些进程对用户体验没有直接影响,系统可能随时终止它们,以回收内存供前台进程、可见进程或服务进程使用。 通常会有很多后台进程在运行,因此它们会保存在 LRU (最近最少使用)列表中,以确保包含用户最近查看的 Activity 的进程最后一个被终止。如果某个 Activity 正确实现了生命周期方法,并保存了其当前状态,则终止其进程不会对用户体验产生明显影响,因为当用户导航回该 Activity 时,Activity 会恢复其所有可见状态。

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

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

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

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

1 UI 线程

应用启动时,系统会为应用创建一个名为“主线程”的执行线程,通常用来处理UI操作的,所以也叫着UI 线程,UI 线程默认会创建其对应的Loopper,Loopper提供了Android事件驱动的能力。

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

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

如果 UI 线程处理繁重的耗时操作,则执行耗时很长的操作(例如,网络访问或数据库查询)将会阻塞整个 UI。 一旦线程被阻塞,将无法分派任何事件,包括绘图事件。 从用户的角度来看,应用显示为挂起。 更糟糕的是,如果 UI 线程被阻塞超过几秒钟时间(目前大约是 5 秒钟),用户就会看到一个让人厌烦的“应用无响应”(ANR) 对话框。

此外,Android UI 工具包并非线程安全工具包。因此,您不得通过工作线程操纵 UI,而只能通过 UI 线程操纵用户界面。 因此,Android 的单线程模式必须遵守两条规则:
1> 不要阻塞 UI 线程
2> 不要在 UI 线程之外访问 Android UI 工具包

2 工作线程

根据上述单线程模式,要保证 UI 的响应能力,关键是不能阻塞 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 线程之外访问 Android UI 工具包 — 此示例从工作线程(而不是 UI 线程)修改了 ImageView。 这可能导致出现不明确、不可预见的行为。

为解决此问题,Android 提供了几种途径来从其他线程访问 UI 线程。 以下列出了几种有用的方法:
1> Activity.runOnUiThread(Runnable)
2> View.post(Runnable)
3> 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();
}

现在,上述实现属于线程安全型:在单独的线程中完成网络操作,而在 UI 线程中操纵 ImageView。但是随着操作日趋复杂,这类代码也会变得复杂且难以维护,可以考虑使用Rxjava来解决线程切换的嵌套问题。

线程的同步

在java语言中,引入了 对象互斥锁 的概念来实现不同线程对共享数据操作的实现。对象互斥锁 阻止多个线程同时访问同一个条件变量(就是synchronized后面跟的变量,通常是this,所以可以直接将其放到方法返回值前),java可以为每一个实例配一个 对象互斥锁,且只有对象才可以被放到synchronized后面的括号中。

在java语言中,有两种方法实现 对象互斥锁:
1> 用关键字volatile来声明一个共享数据。
2> 用关键字synchronized来声明一个操作共享数据的方法、一段代码。

第一种方法很少被使用,第二种方法更多被使用,用关键字synchronized来声明一段代码比声明一个方法更加高效。

1. synchronized的用法

在android开发中synchronized主要有两种用法。第一是在方法声明时使用;
第二是在对某一代码块时使用。具体的实现代码如下:

1.1 方法声明时使用
放在范围操作符(public等)之后,返回类型声明(void等)之前.这时,线程获得的是方法中this实例的对象锁,即一次只能有一个线程进入该方法,其他线程要想在此时调用该方法,由于获得不到对像锁只能排队等候,当前线程执行完该方法后就会释放对象锁,别的线程才能进入。

例如:
public synchronized void synMethod() {
//方法体
}

1.2 对某一代码块使用
synchronized后跟括号,括号里是变量(只能是对象),这样,一次只有一个线程进入该代码块.此时,线程获得的是成员锁.例如:
public void synMethod(Integer a1){
synchronized(a1) {
//一次只能有一个线程进入
}
}

线程池

线程池的使用在java中占有极其重要的地位,在jdk1.4及其之前的jdk版本中,关于线程池的使用是极其简陋的。在jdk1.5之后这一情况有了很大的改观。Jdk1.5之后加入了java.util.concurrent包,这个包中主要介绍java中线程以及线程池的使用。为我们在开发中处理线程的问题提供了非常大的帮助。

1. 线程池的作用

线程池作用就是限制执行线程的数量。根据系统的环境情况,可以自动或手动设置线程数量,达到运行的最佳效果;少了浪费了系统资源,多了造成系统拥挤效率不高。用线程池控制线程数量,空闲线程排队等候。一个任务执行完毕,再从队列的中取最前面的任务开始执行。若队列中没有等待进程,线程池的这一任务处于等待。当一个新任务需要运行时,如果线程池中有等待的工作线程,就可以开始运行了;否则进入等待队列。

2. 为什么要用线程池

1> 减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务。
2> 可以根据系统的承受能力,调整线程池中工作线线程的数目,防止因为消耗过多的内存,而把服务器累趴下(每个线程需要大约1MB内存,线程开的越多,消耗的内存也就越大,最后死机)。
Java里面线程池的顶级接口是Executor,但是严格意义上讲Executor并不是一个线程池,而只是一个执行线程的工具。真正的线程池接口是ExecutorService。

3. 比较重要的几个类

类名 说明
ExecutorService 真正的线程池接口
ScheduledExecutorService 和Timer/TimerTask类似,解决那些需要任务重复执行的问题
ThreadPoolExecutor ExecutorService的默认实现
ScheduledThreadPoolExecutor 继承ThreadPoolExecutor类和对ScheduledExecutorService接口的实现,周期性任务调度的类实现

要配置一个线程池是比较复杂的,尤其是对于线程池的原理不是很清楚的情况下,很有可能配置的线程池不是较优的,因此在Executors类里面提供了一些工厂方法用来生成一些常用的线程池:

/**
 * 创建一个使用单个线程操作一个无界任务队列的线程池。(但是,如果这个唯一的工作线程
 *由于执行期间的异常而终止,此时如果需要执行后续的任务,那么会有一个新的线程来替代它。)
 *任务保证被顺序地执行,并且在任何给定的时间不多于一个任务被正在执行 。 
 *与其他等效的{@code newFixedThreadPool(1)}不同,返回的线程池不能被重新配置以使用额外的线程。
 *
 * @return 新创建的单线程线程池
 */
public static ExecutorService newSingleThreadExecutor() {
    return new FinalizableDelegatedExecutorService
        (new ThreadPoolExecutor(1, 1,
                                0L, TimeUnit.MILLISECONDS,
                                new LinkedBlockingQueue<Runnable>()));
}
/**
 * 创建一个使用单个线程操作一个无界任务队列的线程池,并在需要时使用提供的
 *ThreadFactory创建一个新线程。 与其他等效的{@code newFixedThreadPool(1,threadFactory)}不同,
 *返回的线程池不能被重新配置以使用额外的线程。
 *
 * @param threadFactory 在创建新线程时使用
 *
 * @return 新创建的单线程线程池
 * @throws NullPointerException if threadFactory is null
 */
public static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory) {
    return new FinalizableDelegatedExecutorService
        (new ThreadPoolExecutor(1, 1,
                                0L, TimeUnit.MILLISECONDS,
                                new LinkedBlockingQueue<Runnable>(),
                                threadFactory));
}
/**
 * 创建一个使用固定数量线程去操作一个共享无界任务队列的线程池。 在任何时候,至多nThreads个线程
 *正在处理任务。 如果在所有线程都处于活动状态时提交新任务,那么新任务将在任务队列中等待,直到线程可用。 
 *如果任何线程由于执行期间的异常而终止,此时如果需要执行后续的任务,那么会有一个新的线程来替代它。 
 *池中的线程将永远存在,直到显式调用{@link ExecutorService#shutdown shutdown}。
 *
 * @param nThreads 池中的线程数
 * @return 新创建的线程池
 * @throws IllegalArgumentException if {@code nThreads <= 0}
 */
public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(nThreads, nThreads,
                                  0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>());
}

/**
 *  创建一个使用固定数量线程去操作一个共享无界任务队列的线程池。
 *使用提供的ThreadFactory在需要时创建新线程。 在任何时候,至多nThreads个线程正在处理任务。
 *如果在所有线程都处于活动状态时提交新任务,那么新任务将在任务队列中等待,直到线程可用。 
 *如果任何线程由于执行期间的异常而终止,此时如果需要执行后续的任务,那么会有一个新的线程来替代它。 
 *池中的线程将永远存在,直到显式调用{@link ExecutorService#shutdown shutdown}。
 *
 * @param nThreads 池中的线程数
 * @param threadFactory 在创建新线程时使用
 * @return 新创建的线程池
 * @throws NullPointerException if threadFactory is null
 * @throws IllegalArgumentException if {@code nThreads <= 0}
 */
public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) {
    return new ThreadPoolExecutor(nThreads, nThreads,
                                  0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>(),
                                  threadFactory);
}
/**
 * 创建一个根据需要创建新线程的线程池,但会在以前构造的线程可用时重用它。 这样的线程池通常
 *会提高需要执行许多短暂异步任务的程序的性能。 调用{@code execute}将重用以前构造的线程(如果可用)。 
 *如果没有现有线程可用,将创建一个新线程并将其添加到池中。 超过60秒未被使用的线程将被终止,
 *并从缓存中删除。 因此,这样的线程池长时间的空闲也不会消耗任何资源。 
 *注意,可以使用{@link ThreadPoolExecutor}构造函数创建具有相似属性但不同细节
 *(例如,超时参数)的线程池。
 *
 * @return 新创建的线程池
 */
public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                  60L, TimeUnit.SECONDS,
                                  new SynchronousQueue<Runnable>());
}

/**
 * 创建一个根据需要创建新线程的线程池,但会在以前构造的线程可用时重用它,
 *并在需要时使用提供的ThreadFactory创建新线程。
 * @param threadFactory 在创建新线程时使用
 * @return 新创建的线程池
 * @throws NullPointerException if threadFactory is null
 */
public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                  60L, TimeUnit.SECONDS,
                                  new SynchronousQueue<Runnable>(),
                                  threadFactory);
}
/**
 * 创建可在给定延迟后被调度运行或者定期执行的线程池。
 * @param corePoolSize 在池中保留的线程数,即使它们是空闲的
 * @return 新创建的调度线程池
 * @throws IllegalArgumentException if {@code corePoolSize < 0}
 */
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
    return new ScheduledThreadPoolExecutor(corePoolSize);
}

/**
 * 创建可在给定延迟后被调度运行或者定期执行的线程池。
 * @param corePoolSize 在池中保留的线程数,即使它们是空闲的
 * @param threadFactory 在创建新线程时使用
 * @return 新创建的调度线程池
 * @throws IllegalArgumentException if {@code corePoolSize < 0}
 * @throws NullPointerException if threadFactory is null
 */
public static ScheduledExecutorService newScheduledThreadPool(
        int corePoolSize, ThreadFactory threadFactory) {
    return new ScheduledThreadPoolExecutor(corePoolSize, threadFactory);
}
/**
 * 创建单线程的线程池,可以在给定延迟后被调度运行,或定期执行。 
 *(但是,如果该单个线程由于在执行期间的异常而终止,此时如果需要执行后续的任务,
 *那么会有一个新的线程来替代它。)任务保证被顺序地执行,并且在任何给定的时间不多于一个任务被正在执行。 
 *与其他等效的{@code newScheduledThreadPool(1)}不同,返回的线程池不能被重新配置以使用额外的线程。
 * @return 新创建的调度线程池
 */
public static ScheduledExecutorService newSingleThreadScheduledExecutor() {
    return new DelegatedScheduledExecutorService
        (new ScheduledThreadPoolExecutor(1));
}

/**
 * 创建单线程线程池,可以在给定延迟后被调度运行,或定期执行。 
 *(但是,如果该单个线程由于在执行期间的异常而终止,此时如果需要执行后续的任务,
 *那么会有一个新的线程来替代它。)任务保证被顺序地执行,并且在任何给定的时间不多于一个任务被正在执行。 
 *与其他等效的{@code newScheduledThreadPool(1,threadFactory)}不同,
 *返回的线程池不能被重新配置为使用额外的线程。
 * @param threadFactory 在创建新线程时使用
 * @return 新创建的调度线程池
 * @throws NullPointerException if threadFactory is null
 */
public static ScheduledExecutorService newSingleThreadScheduledExecutor(ThreadFactory threadFactory) {
    return new DelegatedScheduledExecutorService
        (new ScheduledThreadPoolExecutor(1, threadFactory));
}

上面的注释已经详细的说明了Executors类中常用的工厂方法,这里就不在说明了。
上面的工厂方法直接或者间接的创建ThreadPoolExecutor类的实例或者ScheduledThreadPoolExecutor类的实例,那下面我们就来看一下ThreadPoolExecutor和ScheduledThreadPoolExecutor类的构造方法:

/**
 *使用给定的初始参数、默认thread factory和RejectedExecutionHandler创建一个
 *ThreadPoolExecutor实例。 使用Executors的工厂方法而不是这个通用构造函数可能更方便。
 *
 * @param corePoolSize 要保留在池中的核心线程数,即使它们是空闲的,除非设置{@code allowCoreThreadTimeOut}
 * @param maximumPoolSize 池中允许的最大线程数
 * @param keepAliveTime 当线程数大于核心线程数时,
 *        超出核心线程数的空闲线程在终止前等待新任务的最大时间。
 * @param unit keepAliveTime参数的时间单位
 * @param workQueue 在执行任务之前用于保存任务的队列。 此队列只保留execute方法提交的Runnable任务。
 * @throws IllegalArgumentException 如果以下之一成立:<br>
 *         {@code corePoolSize < 0}<br>
 *         {@code keepAliveTime < 0}<br>
 *         {@code maximumPoolSize <= 0}<br>
 *         {@code maximumPoolSize < corePoolSize}
 * @throws NullPointerException if {@code workQueue} is null
 */
public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue) {
    this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
         Executors.defaultThreadFactory(), defaultHandler);
}

/**
 * 使用给定的初始参数和默认RejectedExecutionHandler创建ThreadPoolExecutor实例。
 *
 * @param threadFactory 在创建新线程时使用
 *  其他参数同上,此处不在赘叙。
 * @throws IllegalArgumentException if one of the following holds:<br>
 *         {@code corePoolSize < 0}<br>
 *         {@code keepAliveTime < 0}<br>
 *         {@code maximumPoolSize <= 0}<br>
 *         {@code maximumPoolSize < corePoolSize}
 * @throws NullPointerException if {@code workQueue}
 *         or {@code threadFactory} is null
 */
public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory) {
    this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
         threadFactory, defaultHandler);
}

/**
 * 使用给定的初始参数和默认thread factory创建一个ThreadPoolExecutor实例。
 *
 * @param handler 当执行被阻塞时被使用,因为已达到线程限制和队列容量
 *  其他参数同上,此处不在赘叙。
 * @throws IllegalArgumentException if one of the following holds:<br>
 *         {@code corePoolSize < 0}<br>
 *         {@code keepAliveTime < 0}<br>
 *         {@code maximumPoolSize <= 0}<br>
 *         {@code maximumPoolSize < corePoolSize}
 * @throws NullPointerException if {@code workQueue}
 *         or {@code handler} is null
 */
public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          RejectedExecutionHandler handler) {
    this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
         Executors.defaultThreadFactory(), handler);
}

/**
 * 使用给定的初始参数创建一个ThreadPoolExecutor实例。
 *
 *  参数同上,此处不在赘叙。
 * @throws IllegalArgumentException if one of the following holds:<br>
 *         {@code corePoolSize < 0}<br>
 *         {@code keepAliveTime < 0}<br>
 *         {@code maximumPoolSize <= 0}<br>
 *         {@code maximumPoolSize < corePoolSize}
 * @throws NullPointerException if {@code workQueue}
 *         or {@code threadFactory} or {@code handler} is null
 */
public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory,
                          RejectedExecutionHandler handler) {
    if (corePoolSize < 0 ||
        maximumPoolSize <= 0 ||
        maximumPoolSize < corePoolSize ||
        keepAliveTime < 0)
        throw new IllegalArgumentException();
    if (workQueue == null || threadFactory == null || handler == null)
        throw new NullPointerException();
    this.corePoolSize = corePoolSize;
    this.maximumPoolSize = maximumPoolSize;
    this.workQueue = workQueue;
    this.keepAliveTime = unit.toNanos(keepAliveTime);
    this.threadFactory = threadFactory;
    this.handler = handler;
}

/**
 * 使用给定的核心线程数创建ScheduledThreadPoolExecutor实例。
 *
 * @param corePoolSize corePoolSize 要保留在池中的核心线程数,即使它们是空闲的,除非设置{@code allowCoreThreadTimeOut}
 * @throws IllegalArgumentException if {@code corePoolSize < 0}
 */
public ScheduledThreadPoolExecutor(int corePoolSize) {
    super(corePoolSize, Integer.MAX_VALUE,
          DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS,
          new DelayedWorkQueue());
}

/**
 * 使用给定的初始参数创建ScheduledThreadPoolExecutor实例。
 *
 * @param threadFactory 在创建新线程时使用
 *  其他参数同上,此处不在赘叙。
 * @throws IllegalArgumentException if {@code corePoolSize < 0}
 * @throws NullPointerException if {@code threadFactory} is null
 */
public ScheduledThreadPoolExecutor(int corePoolSize,
                                   ThreadFactory threadFactory) {
    super(corePoolSize, Integer.MAX_VALUE,
          DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS,
          new DelayedWorkQueue(), threadFactory);
}

/**
 * 使用给定的初始参数创建ScheduledThreadPoolExecutor实例。
 *
 * @param handler 当执行被阻塞时被使用,因为已达到线程限制和队列容量
 *  其他参数同上,此处不在赘叙。
 * @throws IllegalArgumentException if {@code corePoolSize < 0}
 * @throws NullPointerException if {@code handler} is null
 */
public ScheduledThreadPoolExecutor(int corePoolSize,
                                   RejectedExecutionHandler handler) {
    super(corePoolSize, Integer.MAX_VALUE,
          DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS,
          new DelayedWorkQueue(), handler);
}

/**
 * 使用给定的初始参数创建ScheduledThreadPoolExecutor实例。
 *
 *  参数同上,此处不在赘叙。
 * @throws IllegalArgumentException if {@code corePoolSize < 0}
 * @throws NullPointerException if {@code threadFactory} or
 *         {@code handler} is null
 */
public ScheduledThreadPoolExecutor(int corePoolSize,
                                   ThreadFactory threadFactory,
                                   RejectedExecutionHandler handler) {
    super(corePoolSize, Integer.MAX_VALUE,
          DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS,
          new DelayedWorkQueue(), threadFactory, handler);
}

在JDK帮助文档中,有如此一段话:
“强烈建议程序员使用较为方便的Executors工厂方法Executors.newCachedThreadPool()(无界线程池,可以进行自动线程回收)、Executors.newFixedThreadPool(int)(固定大小线程池)、Executors.newSingleThreadExecutor()(单个后台线程),
它们为大多数使用场景预定义了设置。”

HandlerThread 和 AsyncTask

在开发的过程中,下面两种情况相信大家都遇到过。
1> 不定期的执行一些不需要与应用程序界面交互的任务,通常是通过创建一个具有消息循环的工作线程来实现的,当我们需要执行任务时,就往该工作线程的消息队列中发送一个消息,然后就可以在工作线程中执行对应的任务。

2> 执行一些需要与应用程序界面交互的任务,比如在网上下载文件并且在下载的同时还要在应用程序的界面上显示下载进度,通常会创建一个工作线程来负责下载任务(避免ANR异常),在下载的过程中工作线程向主线程的消息队列中发送包含下载进度的消息,主线程处理消息时就可以显示当前的下载进度。

上面两种情况解决方法的描述有些抽象,有兴趣的同学可以参考 Android 消息机制源码分析一文最后面的实例讲解。对于上面两种情况,Google提供了完善的解决方案,前者通过HandlerThread类来解决,后者通过AsyncTask类来解决。

1. HandlerThread详解

HandlerThread类继承自Thread类,HandlerThread类用于创建具有消息循环的线程。 然后通过消息循环创建Handler类实例。 注意start方法必须被调用。
首先先来看一下HandlerThread如何使用:

HandlerThread handlerThread = new HandlerThread("handlerThread");
handlerThread.start();
Handler handler = new Handler(handlerThread.getLooper()) {
    @Override
    public void handleMessage(Message msg) {
        super.handleMessage(msg);
    }
};

首先我们来看一下HandlerThread的构造方法:

public HandlerThread(String name) {
    super(name);
    mPriority = Process.THREAD_PRIORITY_DEFAULT;
}

HandlerThread的构造方法很简单,创建一个指定名称的线程并且设置mPriority的值为Process.THREAD_PRIORITY_DEFAULT。
接着调用HandlerThread类的start方法,由于HandlerThread类继承自Thread类,所以HandlerThread类的run方法会被调用,源码如下:

@Override
public void run() {
    mTid = Process.myTid();
    Looper.prepare();
    synchronized (this) {
        mLooper = Looper.myLooper();
        notifyAll();
    }
    Process.setThreadPriority(mPriority);
    onLooperPrepared();
    Looper.loop();
    mTid = -1;
}

HandlerThread的run方法的源码很简单,就是用来为HandlerThread对应的线程创建消息循环。
接着就是创建一个向HandlerThread发送消息的Handler实例。

2. AsyncTask详解

AsyncTask被设计为一个关于Thread和Handler的帮助类,而不是一个通用的Thread框架。 AsyncTasks应该用于短操作(最多几秒钟)。如果您需要保持Thread长时间运行,强烈建议您使用由 java.util.concurrent包中提供的各种API,例如Executor,ThreadPoolExecutor和FutureTask。
一个asynchronous task被定义成一个在后台Thread上运行(对应于doInBackground方法)并且在UI线程上发布运行结果(对应于onProgressUpdate 和 onPostExecute方法)的运算。
首先看一下AsyncTask如何使用:

private class DownloadFilesTask extends AsyncTask (URL, Integer, Long) {
      protected Long doInBackground(URL... urls) {
          int count = urls.length;
          long totalSize = 0;
          for (int i = 0; i < count; i++) {
              totalSize += Downloader.downloadFile(urls[i]);
              publishProgress((int) ((i / (float) count) * 100));
              // Escape early if cancel() is called
              if (isCancelled()) break;
          }
          return totalSize;
      }
 
      protected void onProgressUpdate(Integer... progress) {
          setProgressPercent(progress[0]);
      }
 
      protected void onPostExecute(Long result) {
          showDialog("Downloaded " + result + " bytes");
      }
}

new DownloadFilesTask().execute(url1, url2, url3);

上面的代码实现了一个下载文件的AsyncTask。

下面详细讲解一下AsyncTask中用到的3个类型和4个步骤:
1> Params : 执行任务时发送到任务的参数的类型。
2> Progress : 在后台任务执行期间发布的进度的类型。
3> Result :任务执行结果的类型。
并非上面的类型都会被AsyncTask用到。 如果没有用到可以使用类型Void。

当执行AsyncTask时,AsyncTask会经过如下4个步骤:
1> onPreExecute() :在执行任务之前在UI线程上被调用。 此步骤通常用于setup 任务,例如在用户界面中显示进度条。
2> doInBackground:在onPreExecute方法完成执行后立即在后台线程上被调用。 此步骤用于执行可能需要很长时间的后台计算。 任务的参数(Params类型)会被传递到此方法。 计算的结果(Result类型)必须在此方法的最后一步被返回。 此方法还可以使用publishProgress方法发布一个或多个进度值(Progress类型)。 这些进度值将在UI线程上的onProgressUpdate方法中被发布。
3> onProgressUpdate:在调用publishProgress后会在UI线程上被调用。 执行的时间是不定期的。 此方法用于在后台计算仍在执行的过程中在用户界面中显示任何形式的进度。 例如,它用于显示动态进度条或在文本字段中显示进度。
4> onPostExecute:在后台计算完成(doInBackground方法返回)后在UI线程上被调用。 后台计算的结果作为参数传递到该方法。

注意,还有一个cancel方法也是很常用的,比如执行压缩文件的过程是很漫长的,如果用户没有耐心了,想取消压缩任务,那么该方法就派上用场了,源码如下:

public final boolean cancel(boolean mayInterruptIfRunning) {
    mCancelled.set(true);
    return mFuture.cancel(mayInterruptIfRunning);
}

该方法尝试取消被执行的任务。 如果任务已完成,已取消或者由于某些其他原因无法被取消,则此尝试将失败(即该方法返回false)。 如果此尝试成功(即该方法返回true),并且调用cancel方法时此任务尚未开始,则此任务将不会被执行; 如果任务已经启动,则mayInterruptIfRunning参数确定正在执行此任务的线程是否应在尝试取消任务时被中断(true 正在执行此任务的线程应该被中断; 否则,正在进行的任务被允许完成)。

调用此方法将导致回调方法onCancelled(Object)在回调方法doInBackground(Object [])返回后在UI线程上被调用。 调用此方法可确保不会调用回调方法onPostExecute(Object)和onProgressUpdate(Progress... values)。 调用此方法后,您应该在回调方法doInBackground(Object [])中定期检查isCancelled()返回的值,以尽快结束任务。

内存可观察性

AsyncTask保证所有回调调用都以这样一种方式同步,使得以下操作在没有显式同步的情况下是安全的。
1> 在构造函数或onPreExecute方法中设置成员字段,并在doInBackground方法中引用它们。
2> 在doInBackground方法中设置成员字段,并在onProgressUpdate和onPostExecute方法中引用它们。

执行顺序

当AsyncTask第一次被引入时,AsyncTasks在单个后台线程上连续执行。 从android.os.Build.VERSION_CODES#DONUT开始,这被更改为允许多个任务并行执行的线程池。 从android.os.Build.VERSION_CODES#HONEYCOMB开始,任务在单个线程上执行,以避免并行执行引起的常见应用程序错误。
如果你真的想并行执行,你可以使用THREAD_POOL_EXECUTOR作为参数调用executeOnExecutor(java.util.concurrent.Executor,Object [])方法。

源码分析

我们来看一下AsyncTask的execute方法及其相关的代码:

private static class SerialExecutor implements Executor {
    final ArrayDeque<Runnable> mTasks = new ArrayDeque<Runnable>();
    Runnable mActive;

    public synchronized void execute(final Runnable r) {
        mTasks.offer(new Runnable() {
            public void run() {
                try {
                    r.run();
                } finally {
                    scheduleNext();
                }
            }
        });
        if (mActive == null) {
            scheduleNext();
        }
    }

    protected synchronized void scheduleNext() {
        if ((mActive = mTasks.poll()) != null) {
            THREAD_POOL_EXECUTOR.execute(mActive);
        }
    }
}

/**
 * An {@link Executor} that executes tasks one at a time in serial
 * order.  This serialization is global to a particular process.
 */
public static final Executor SERIAL_EXECUTOR = new SerialExecutor();
private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;

@MainThread
public final AsyncTask<Params, Progress, Result> execute(Params... params) {
    return executeOnExecutor(sDefaultExecutor, params);
}

@MainThread
public final AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec,
        Params... params) {
    if (mStatus != Status.PENDING) {
        switch (mStatus) {
            case RUNNING:
                throw new IllegalStateException("Cannot execute task:"
                        + " the task is already running.");
            //这个异常相信大家非常熟悉,也就是说一个AsyncTask的实例只能被执行一次
            case FINISHED: 
                throw new IllegalStateException("Cannot execute task:"
                        + " the task has already been executed "
                        + "(a task can be executed only once)");
        }
    }

    mStatus = Status.RUNNING;

    onPreExecute();

    mWorker.mParams = params;
    exec.execute(mFuture);

    return this;
}

@MainThread
public static void execute(Runnable runnable) {
    sDefaultExecutor.execute(runnable);
}

AsyncTask共有两个重载的execute方法,上面的代码很简单,AsyncTask的两个execute方法最终都会调用线程池SERIAL_EXECUTOR的void execute(Runnable command)方法,由源码中的注释可以得知在线程池SERIAL_EXECUTOR上执行的任务都是串行执行的,从而证明了上面的一句话:从android.os.Build.VERSION_CODES#HONEYCOMB开始,任务在单个线程上执行

上面还提到过线程池THREAD_POOL_EXECUTOR,对应的源码如下:

private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
// We want at least 2 threads and at most 4 threads in the core pool,
// preferring to have 1 less than the CPU count to avoid saturating
// the CPU with background work
private static final int CORE_POOL_SIZE = Math.max(2, Math.min(CPU_COUNT - 1, 4));
private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;
private static final int KEEP_ALIVE_SECONDS = 30;

private static final ThreadFactory sThreadFactory = new ThreadFactory() {
    private final AtomicInteger mCount = new AtomicInteger(1);

    public Thread newThread(Runnable r) {
        return new Thread(r, "AsyncTask #" + mCount.getAndIncrement());
    }
};

private static final BlockingQueue<Runnable> sPoolWorkQueue =
        new LinkedBlockingQueue<Runnable>(128);

/**
 * 可用于并行执行任务的{@link Executor}。
 */
public static final Executor THREAD_POOL_EXECUTOR;

static {
    ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
            CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE_SECONDS, TimeUnit.SECONDS,
            sPoolWorkQueue, sThreadFactory);
    threadPoolExecutor.allowCoreThreadTimeOut(true);
    THREAD_POOL_EXECUTOR = threadPoolExecutor;
}

上面的代码很简单,就是创建了一个可以并行执行的线程池,那么如果想要并行执行任务的话,就可以使用AsyncTask类的executeOnExecutor方法并且将THREAD_POOL_EXECUTOR作为第一个参数。

举例说明

经过上面对AsyncTask的分析可知:
AsyncTask默认是串行执行任务的,但是也提供了executeOnExecutor来并行的执行任务,下面我会写一个样例来实现这两个功能,代码如下:

AsyncTaskExecutor.java:

public interface AsyncTaskExecutor {
    <T> AsyncTask<T, ?, ?> submit(Object identifier, AsyncTask<T, ?, ?> task, T... params);
}

AsyncTaskExecutors.java:

/**
 * Created by chenyang on 2017/1/5.
 * Factory methods for creating AsyncTaskExecutors.
 */
public class AsyncTaskExecutors {

    /**
     * Creates an AsyncTaskExecutor that submits tasks to run with
     * {@link AsyncTask#SERIAL_EXECUTOR}.
     */
    public static AsyncTaskExecutor createAsyncTaskExecutor() {
        synchronized (AsyncTaskExecutors.class) {
            return new SimpleAsyncTaskExecutor(AsyncTask.SERIAL_EXECUTOR);
        }
    }

    /**
     * Creates an AsyncTaskExecutor that submits tasks to run with
     * {@link AsyncTask#THREAD_POOL_EXECUTOR}.
     */
    public static AsyncTaskExecutor createThreadPoolExecutor() {
        synchronized (AsyncTaskExecutors.class) {
            return new SimpleAsyncTaskExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
        }
    }

    public static void checkCalledFromUiThread() {
        if (Thread.currentThread() != Looper.getMainLooper().getThread()) {
           throw new RuntimeException("submit method must be called from ui thread, was: " + Thread.currentThread());
        }
    }

    private static class SimpleAsyncTaskExecutor implements AsyncTaskExecutor {
        private final Executor mExecutor;

        public SimpleAsyncTaskExecutor(Executor executor) {
            mExecutor = executor;
        }

        @Override
        public <T> AsyncTask<T, ?, ?> submit(Object identifer, AsyncTask<T, ?, ?> task,
                                             T... params) {
            checkCalledFromUiThread();
            return task.executeOnExecutor(mExecutor, params);
        }
    }
}

FileTaskHelper.java:

/**
 * Created by chenyang on 2017/1/5.
 */
public class FileTaskHelper {

    private static AsyncTaskExecutor sAsyncTaskExecutor = null;

    private static void initTaskExecutor() {
        sAsyncTaskExecutor = AsyncTaskExecutors.createThreadPoolExecutor();
    }

    /** The enumeration of {@link AsyncTask} objects used in this class. */
    public enum Tasks {
        COPY_FILE(0);

        private int value;
        Tasks(int value) {
            this.value = value;
        }

        public int getValue() {
            return value;
        }

        public static Tasks valueOf(int value) {
            Tasks ret = COPY_FILE;

            for (Tasks orientationType : Tasks.values()) {
                if (orientationType.getValue() == value) {
                    ret = orientationType;
                    break;
                }
            }

            return ret;
        }
    }

    public static void copyFile (AsyncTask<String, Object, Object> task, String... params) {
        if (null == sAsyncTaskExecutor) {
            initTaskExecutor();
        }
        sAsyncTaskExecutor.submit(Tasks.COPY_FILE, task, params);
    }
}

由上面的代码可知,FileTaskHelper的copyFile方法就是将copy文件的任务提交到线程池THREAD_POOL_EXECUTOR上执行,是串行还是并行是与FileTaskHelper类的实现有关。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,847评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,208评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,587评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,942评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,332评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,587评论 1 218
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,853评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,568评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,273评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,542评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,033评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,373评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,031评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,073评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,830评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,628评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,537评论 2 269

推荐阅读更多精彩内容