【艺术探索笔记】第 11 章 Android 的线程和线程池

第 11 章 Android 的线程和线程池

线程分为主线程和子线程,主线程主要处理和界面相关的事情,子线程用来执行耗时操作。

在 Android 中,如果主线程中执行耗时操作就会导致程序无法响应,因此耗时操作必须放在子线程中去执行。

除了传统的 Thread 外,AsyncTask、IntentService、HandlerThread 也是特殊的线程

AsyncTask 底层用到了线程池,IntentService 和 HandlerThread 底层则直接使用了线程

AsyncTask 封装了线程池和 Handler,为了方便开发者在子线程中更新 ui

HandlerThread 是一种具有消息循环的线程,它内部可以使用 Handler

IntentService 是一个服务,系统对其进行封装使其更方便地执行后台任务,内部采用 HandlerThread 来执行任务,执行完毕后 IntentService 会自动退出。(服务,不容易被系统杀死)

操作系统中,线程是操作系统调度的最小单元,同时又是一种受限的系统资源,不能无限制地产生,它的创建和销毁都有相应的开销。

系统中有大量线程时,系统通过时间片轮转调度每个线程,因此线程不可能做到绝对并行,除非线程数小于等于 cpu 的核心数量。

用线程池来管理线程是一个高校的做法,它缓存一定数量的线程,避免频繁创建销毁带来的开销。

Android 线程池来源于 Java,主要通过 Excutor 来派生特定类型的线程池。


11.1 主线程和子线程

主线程是指进程所拥有的线程,在 Java 中默认情况下一个进程只有一个线程,这个线程就是主线程。处理界面交互的逻辑,用户随时都会和界面交互,所以主线程必须有较高的响应速度,否则就会产生一种界面卡顿的感觉。

为了保持较高的响应速度,这就要求主线程不能执行耗时任务,这个时候子线程就派上用场了。子线程也叫工作线程,除了主线程以外的线程都是子线程。

Android 沿用了 Java 的线程模型,主线程也叫 UI 线程。

主线程作用是运行四大组件以及处理他们和用户的交互

子线程的作用是处理耗时任务:网络请求、I/O操作等

Android 3.0 开始,网络访问必须在子线程中进行,否则会抛 NetworkOnMainThreadException 异常。


11.2 Android 中的线程形态

11.2.1 AsyncTask

轻量级异步任务类,可以在线程池中执行异步任务,执行结果传递给主线程并在主线程更新 ui。

实现上来说,它封装了 Thread 和 Handler,通过它可以很方便的执行后台任务以及在主线程中访问 ui,但它不适合进行特别耗时的任务。

AsyncTask 是一个抽象的泛型类,提供了 Params、Progress、Result 这三个泛型参数。Params 表示参数的类型,Progress 表示后台任务的执行进度类型,Result 表示后台任务的返回结果类型。如果不需要具体参数,可以传 Void 代替。

AsybcTask 提供了 4 个核心方法:

  1. onPreExecute(),在主线程中执行,异步任务执行之前,此方法会被调用,一般用于做一些准备工作
  2. doInBackground(Params...params),在线程池中执行,用于执行异步任务,params 参数表示异步任务的输入参数。在此方法中可以通过 publishProgress 方法更新任务进度,publishProgress 方法会调用 onProgressUpdate 方法。另外此方法需要返回计算结果给 onPostExecute 方法
  3. onProgressUpdate(Progress...values),在主线程中执行,当后台任务的执行进度发生改变时此方法会被调用。
  4. onPostExecute(Result result),在主线程中执行,在异步任务结束之后会被调用,result 是后台任务的返回值,即 doInBackground 的返回值。

上边的方法, onPreExecute 限先执行,接着是 doInBackground,最后才是 onPostExecute。AsyncTask 还提供了 onCanceled() 方法,同样在主线程执行,当异步任务被取消时,onCanceled() 方法被调用,这时 onPostExecute 就不会被调用了。

AsyncTask 的使用限制:

  1. AsyncTask 类必须在主线程中加载,意味着第一次访问 AsyncTask 必须在主线程。从 android 4.1 开始这个操作已经被系统自动完成。在 ActivityThread 的 main 方法中,调用了 AsyncTask 的 init 方法。(android 5.0 源码是这样的,而 api-27 构造方法里会拿到需要的 Handler,已经没有了 init 方法)
  2. AsyncTask 的对象必须在主线程中创建。
  3. execute 方法必须在 ui 线程调用
  4. 不要在程序中直接使用 onPreExecute()、onPostExecute()、doInBackground() 和 onProgressUpdate() 方法。
  5. 一个 AsyncTask 对象只能执行一次,即只能调用一次 execute 方法,否则会报运行时异常。
  6. Android 1.6 之前,AsyncTask 是串行执行任务,Android 1.6 的时候 AsyncTask 开始采用线程池里处理并行任务;Android 3.0 开始,为了避免 AsyncTask 所带来的并发错误,AsyncTask 又采用一个线程来串行执行任务。Android 3.0 以后的版本可以通过 executeOnExecutor 方法并行执行任务

11.2.2 AsyncTask 的工作原理

  • 看看构造方法做了啥 api-27

    image

  • AsyncTask#execute(Params... params) api-27

    image

    sDefaultExecutorSerialExecutor 这个线程池的实例,看一下它的源码。

  • SerialExecutor 线程池 api-27

    image

    任务执行的时候,会调用 FutureTask 的 run 方法,看一下源码

  • FutureTask.run() api-27

    image

  • WorkerRunnable.call() api-27

    image

    接下来接着看 InternalHandler 是怎么处理消息的

  • InternalHandler api-27

    image

    InternalHandler 是在 AsyncTask 的构造方法中创建的,因为构造方法必须在主线程调用,所以这个 Handler 所用的 Looper 也是主线程的 Looper,所以它可以完成切换到主线程的任务

    还看到 InternalHandler 处理了更新任务进度的消息,更新任务进度是用户在 doInBackground(Params... params) 方法里手动调用 publishProgress(Progress... values) 方法,publishProgress 通过发消息给 InternalHandler 完成线程切换去在主线程更新进度 ui。

以上,AsyncTask 的整个工作流程就分析完毕了。

AsyncTask 在 Android 3.0 以上是默认串行执行的的,想要达到并行的效果的话,可以直接调用 executeOnExecutor(Executor exec, Params... params) 传入执行任务的线程池 AsyncTask.THREAD_POOL_EXECUTOR

11.2.3 HandlerThread

继承自 Thread,是一种可以使用 Handler 的 Thread,它的实现是在 run 方法里通过 Looper.prepare() 来创建消息队列,并通过 Looper.loop() 开启消息循环,这样在实际使用中就允许在 HandlerThread 中创建 Handler 了。

  • HandlerThread.run() api-27

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

    内部创建了消息队列,外界需要通过 Handler 的消息方式来通知 HandlerThread 执行一个具体的任务。

在 Android 中的一个具体使用场景是 IntentService。

HandlerThread 的 run 方法是一个无限循环(因为里边创建了 Looper),因此当明确不再使用 HandlerThread 的时候,可以通过它的 quit 或 quitSafely 方法来终止线程的执行。

11.2.4 IntentService

是一种特殊的 Service,继承于 Service 并且是一个抽象类,所以必须创建它的子类才能使用它。

IntentService 可以执行后台耗时任务,任务执行完后它会自动停止。它是一个服务,优先级比较高不容易被系统杀死

  • IntentService#onCreate() api-27

    image

    每次启动 IntentService,它的 onStartCommand 方法就会调用一次,看一下源码

  • IntentService#onStartCommand() api-27

    image

    多次启动 IntentService 就会多次调用 onStartCommand 方法,把任务依次放入 HandlerThread 的消息队列中。

    把启动 Service 的 Intent 交给了 ServiceHandler 去处理,看一下它的源码

  • ServiceHandler api-27

    image

    如果目前只存在一个后台任务,那 onHandleIntent 执行完后,stopSelf 就会立即停止服务;如果有多个后台任务,当 onHandleIntent 执行完最后一个任务时,stopSelf 才会直接停止服务。

IntentService 是顺序执行后台任务的,当有多个后台任务同时存在时,会按照外界发起的顺序排队执行。


11.3 Android 中的线程池

线程池的优点:

  1. 重用线程池里的线程,避免因为线程的创建和销毁所带来的性能开销
  2. 有效控制线程池最大并发数,避免大量线程之间互相抢占资源导致阻塞
  3. 对线程进行简单的管理,提供定时执行和指定间隔循环执行等功能

Android 中线程池概念来自 Java 中的 Executor,Executor 是一个接口,真正的线程池的实现为 ThreadPoolExecutor。

ThreadPoolExecutor 提供一系列参数来配置线程池。

11.3.1 ThreadPoolExecutor

  • 它的常用的一个构造方法 api-27

    
    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             threadFactory, defaultHandler);
    }
    
    • corePoolSize

      线程池的核心线程数。默认情况核心线程会在线程池中一直存活,即使他们处于闲置状态。如果将 ThreadPoolExecutor 的 allowCoreThreadTimeOut 属性设置为 true,那闲置的核心线程在等待新任务到来时会有超时策略,时间间隔由 keepAliveTime 所指定,等待时间超过 keepAliveTime 所指定时间后,核心线程会被终止。

    • maximumPoolSize

      线程池所能容纳的最大线程数,当活动线程达到这个数值后,后续的新任务将会被阻塞。

    • keepAliveTime

      非核心线程闲置时的超时时长,超过这个时长,非核心线程就会被回收。当 ThreadPoolExecutor 的 allowCoreThreadTimeOut 属性设置为 true 时,keepAliveTime 同样会作用于核心线程。

    • unit

      用于指定 keepAliveTime 参数的时间单位,这是一个枚举,常用的有 TimeUnit.MILLISECONDS(毫秒)、TimeUnit.SECONDS(秒)以及 TimeUnit.MINUTES(分钟)等。

    • workQueue

      线程池中的任务队列,通过线程池的 execute 方法提交的 Runnable 对象会存储在这个参数中。

    • threadFactory

      线程工厂,为线程池提供创建新线程的功能。ThreadFactory 是一个接口,它只有一个方法:Thread newThread(Runnable r)

    除了以上参数外,还有一个不常用的参数 RejectedExecutionHandler handler。当线程池无法执行新任务时,这可能是任务队列已满或者是无法成功执行任务,这个时候 ThreadPoolExecutor 会调用 handler 的 rejectedWxecution 方法来通知调用者,默认情况下 rejectedExecution 方法会直接抛出异常 RejectedExecutionException。ThreadPoolExecutor 为 RejectedExecutionHandler 提供了几个可选值:CallerRunsPolicy、AbortPolicy、DiscardPolicy 和 DiscardOldestPolicy,其中 AbortPolicy 是默认值,它会直接抛出异常。

ThreadPoolExecutor 执行任务时大致遵循如下规则:

  1. 如果线程池中的线程数量未达到核心线程的数量,会直接启动一个核心线程来执行任务。
  2. 若已经达到或超过核心线程数量,人物会被插入任务队列
  3. 如果上一步中无法插入任务队列,说明任务队列已满,这时如果线程数未达到线程池规定的最大值,那么会立刻启动一个非核心线程来执行任务。
  4. 如果上一步中线程数量已经达到线程池规定的最大值,就拒绝执行任务,调用 RejectedExecutionHandler 的 rejectedExecution 方法来通知调用者。

    • AsyncTask 中线程池的配置情况 api-27

    image

11.3.2 线程池的分类

  1. FixedThreadPool

    通过 Executors 的 newFixedThreadPool 方法来创建

    
    public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>(),
                                      threadFactory);
    }
    

    看创建方法可知:核心线程数量和最大线程数相等,只有核心线程,核心线程空闲状态也不会被回收;任务队列没有大小限制,所有线程都处于活动状态时,新任务会等待。

    它可以更加快速的响应外界的请求。

  2. CachedThreadPool

    通过 Executors 的 newCachedThreadPool 方法来创建。

    
    public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>(),
                                      threadFactory);
    }
    

    它只有非核心线程、线程数量是 int 的最大值、限制超时时间 60 秒、任务队列大小不限制(线程数量最大值,可能用不到任务队列)

    任何任务都将立即被执行。它比较适合执行大量的耗时较少的任务,闲置状态时,所有线程超时都会被停止,几乎不占用系统资源

  3. ScheduledThreadPool

    通过 Executors 的 newScheduledThreadPool 方法来创建

    
    public static ScheduledExecutorService newScheduledThreadPool(
            int corePoolSize, ThreadFactory threadFactory) {
        return new ScheduledThreadPoolExecutor(corePoolSize, threadFactory);
    }
    
    public ScheduledThreadPoolExecutor(int corePoolSize,
                                       ThreadFactory threadFactory) {
        super(corePoolSize, Integer.MAX_VALUE,
              DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS,
              new DelayedWorkQueue(), threadFactory);
    }
    
    

    核心线程数固定、最大线程数无限制、空闲的非核心线程立即回收。

    用于执行定时任务和具有固定周期的重复任务。

  4. SingleThreadExecutor

    通过 Executors 的 newSingleThreadExecutor 方法来创建

    
    public static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory) {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>(),
                                    threadFactory));
    }
    

    只有一个核心线程,来任务等待,空闲不会超时回收。

    意义在于统一所有的外界任务到一个线程中,在这些任务之间不需要处理线程同步的问题。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值