前言:不变的还是那颗勇往直前的心。 心得来源于:《Android开发艺术探索》
Android 的线程和线程池
在操作系统中,线程是操作系统调度的最小单元,同时线程又是一种受限的系统资源,即线程不可能无限制的产生,并且线程的创建和销毁都有一定的开销。
当系统中存在大量的线程时,系统会通过时间片轮转的方式调度每个线程,因此线程不可能做到绝对的并发,除非线程数小于等于CUP的核心数,一般来说这是不可能的。如果在一个进程中频繁的创建和销毁线程,这显然不是高效的做法。正确的做法是采用线程池,一个线程池中会缓存一定数量的线程,通过线程池就可以避免因为频繁创建和销毁线程所带来的系统开销。Android中的线程池来源于Java,主要是通过Executor来派生特定类型的线程池,不同种类的线程池又具有各自的特性。
主线程和子线程
主线程是指进程所拥有的线程,在Java中默认情况下一个进程只有一个线程,这个线程就是主线程。
子线程也叫工作线程,除了主线程以为的线程都是子线程。
Android 中的线程形态
除了传统的 Thread 以外,还包括 AsyncTask、HandlerThread 以及 IntentService,这三者底层实现也是线程。
Android 中的AsyncTask和IntentService的对比
接下来我们针对具体的AsyncTask和Intent Service进行具体的分析
AsyncTask
AsyncTask 是一种轻量级的异步任务类,它可以在线程池中执行后台任务,然后把执行的进度和最终的结果传递给主线程并在主线程中更新UI。
从实现上来说,AsyncTask封装了 Thread 和 Handler,通过 AsyncTask 可以更加方便的执行后台任务以及在主线程中访问UI,但是不适合执行特别耗时的后台任务,对于特别耗时的任务来说,还是使用线程池比较好。
AsyncTask 是一个抽象的泛型类,它提供了 Params、Progerss 和 Result 这三个泛型参数,其中 Params 表示参数的类型,Progress 表示后台任务执行进度的类型,而 Result 则表示后台任务的返回结果的类型,如果不需要传递具体的参数,那么这三个泛型参数可以使用 Void 来代替:
- 1
- 1
AsyncTask 提供了4个核心方法,它们的含义如下:
- onPreExecute()
这个方法会在后台任务开始执行之间调用,用于进行一些界面上的初始化操作,比如显示一个进度条对话框等。 - doInBackground(Params…)
这个方法中的所有代码都会在子线程中运行,我们应该在这里去处理所有的耗时任务。任务一旦完成就可以通过return语句来将任务的执行结果进行返回,如果AsyncTask的第三个泛型参数指定的是Void,就可以不返回任务执行结果。注意,在这个方法中是不可以进行UI操作的,如果需要更新UI元素,比如说反馈当前任务的执行进度,可以调用publishProgress(Progress…)方法来完成 。 - onProgressUpdate(Progress…)
当在后台任务中调用了publishProgress(Progress…)方法后,这个方法就很快会被调用,方法中携带的参数就是在后台任务中传递过来的。在这个方法中可以对UI进行操作,利用参数中的数值就可以对界面元素进行相应的更新。 - onPostExecute(Result)
当后台任务执行完毕并通过return语句进行返回时,这个方法就很快会被调用。返回的数据会作为参数传递到此方法中,可以利用返回的数据来进行一些UI操作,比如说提醒任务执行的结果,以及关闭掉进度条对话框等。
除了上述四个方法,AsyncTask 还提供了 onCancelled()方法,它同样在主线程执行,当异步任务被取消时,onCancelled() 方法会被调用,这个时候 onPostExecute则不会被调用。
AsyncTask 的工作原理
从它的 execute 方法开始分析,execute 方法又会调用 executeOnExecutor 方法:
- 1
- 2
- 3
- 1
- 2
- 3
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
在上面的代码中,sDefaultExecutor 实际上是一个串行的线程池,一个进程中的所有的 AsyncTask 全部在这个串行的线程池中排队执行。
在 executeOnExecutor 中,AsyncTask 的 onPreExecute() 方法最先被执行,然后线程池开始执行。
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 1
- 2
- 3
- 4
- 5
- 1
- 2
- 3
- 4
- 5
首先系统会把 AsyncTask 的 Params 参数封装为 FutureTask 对象,FutureTask 是一个并发类,在这里它充当了 Runnable 的作用。接着这个FutureTask会交给 SerialExecutor 的 execute 方法去处理,SerialExecutor 的 execute 方法首先会把 FutureTask 对象插入到任务队列 mTask中,如果这个时候没有正在活动的 AsyncTask 任务,那么就会调用SerialExecutor的scheduleNext方法来执行下一个AsyncTask任务。
同时当一个 AsyncTask 任务执行完后,AsyncTask 会继续执行其他任务直到所有的任务都被执行为止,从这一点可以看出,在默认情况下, AsyncTask 是串行执行的。
AsyncTask 有两个线程池(SerialExecutor 和 THREAD_POOL_EXECUTOR)和一个 Handler(IntentHandler),其中线程池 SerialExecutor 用于任务的排队,而线程池 THREAD_POOL_EXECUTOR 用于真正的执行任务,InternalHandler 用于将执行环境从线程池切换到主线程。
在 AsyncTask 的构造方法中有如下一段代码,由于 FutureTask 的 run 方法会调用 mWork 的 call 方法,因此 mWork 的 call 方法最终会在线程池中执行。
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
在 mWorker 的call方法中,首先将 mTaskInvoked 设置为true,表示当前任务已经调用过了,然后执行 AsyncTask 的 doBackground 方法,接着将其返回值传递给 postResult 方法。
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
postResult 方法会通过 sHandler 发送一个 MESSAGE_POST_RESULT 的消息:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
可以发现,sHandler 是一个静态对象,为了能够将执行环境切换到主线程,这就要求 sHandler 这个对象必须在主线程中创建。由于静态成员会在加载类的时候进行初始化,因此这就要求 AsyncTask 的类必须要在主线程中加载,否则同一个进程中的 AsyncTask 都将无法正常工作。
sHandler 收到 MESSAGE_POST_RESULT 这个消息后会调用 AsyncTask 的 finish 方法:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
如果 AsyncTask 被取消的话,那么就会调用onCancelled 方法,否则就会调用 onPostExecute 方法,doBackground 的返回结果会传递给 onPostExecute 方法,到这里整个 AsyncTask 的工作工程分析完毕了。
HandlerThread
HandlerThread 继承了Thread,它是一种可以使用 Handler 的 Thread,在 run 方法中通过 Looper.prepare() 来创建消息队列,并通过 Looper.loop() 来开启消息循环。
HandlerThread 的 run 方法:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
在 HandlerThread 的内部创建了一个具体的任务,外界需要通过Handler的消息方式来通知 HandlerThread 执行一个具体的任务。HandlerThread 的 run 方法是无限循环的,因此当明确不需要再使用 HandlerThread 时,可以通过它的 quit 或者 quitSafely 方法来终止线程的执行。
IntentService
IntentService 是一种特殊的 Service ,它继承了 Service 并且它是一个抽象类,因此必须创建它的子类才能使用 IntentService。
IntentService 可用于执行后台耗时的任务,当任务执行后它会自动停止,同时由于 IntentService 是服务的原因,这导致它的优先级比单纯的线程要高很多,比较适合一些高优先级的后台任务。
IntentService 封装了 HandlerThread 和 Handler:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
当 IntentService 被第一次启动时,它的 onCreate 方法就会被调用,onCreate方法会创建一个 HandlerThread,然后使用它的 Looper 来构造一个 Handler 对象 mServiceHandler,这样通过mServiceHandler 发送的消息最终都会在 HandlerThread 中执行,从这个角度来看,IntentService 也可用于执行后台任务。
每次启动 IntentService,它的 onStartCommand 方法就会调用一次,IntentService 在 onStartCommand 中处理每个后台任务的 Intent。
- 1
- 2
- 3
- 4
- 5
- 6
- 1
- 2
- 3
- 4
- 5
- 6
IntentService 仅仅通过 mServiceHandler 发送一个消息,这个消息会在 HandlerThread 中处理。 mServiceHandler 收到消息后,会将 Intent 对象传递给 onHandleIntent 方法去处理。
注意:这个 Intent 对象的内容和外界的 startService(intent) 中的内容是完全一致的,通过这个 Intent 对象即可解析出外界启动 IntentService 时所传入的参数,这样 onHandleIntent 方法就可以对不同的后台任务做处理了。当 onHandleIntent 方法执行结束后,IntentService 会通过 stopSelf(int startId) 方法来尝试停止服务。
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
IntentService 的 onHandleIntent 方法是一个抽象方法,它的作用就是从 Intent 参数中区分具体的任务并执行这些任务。 如果目前只存在一个后台任务,那么 onHandleIntent 方法执行完这个任务后,stopSelf(int starId)就会直接停止任务,如果有多个任务,会执行完最后一个任务再停止服务,这些任务会按照外界发起的顺序排队执行。
Android中的线程池
线程池的优点:
- 重用线程池的线程,避免因为线程的创建和销毁所带来的性能开销;
- 能有效的控制线程池的最大并发数,避免大量的线程之间因互相抢占系统资源而导致的阻塞现象;
- 能够对线程进行简单的管理,并提供定时执行以及指定间隔循环执行等功能;
Android 中的线程池来源于 Java 中的 Executor,Executor 是一个接口,真正的线程池实现为 ThreadPOOLExecutor。
ThreadPoolExecutor
ThreadPoolExecutor 是线程池的真正实现,它的构造方法提供了一系列参数来配置线程池:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
-
corePoolSie
线程池的核心线程数,默认情况下,核心线程会在线程池中一直存活,即使它们处于闲置状态。如果将 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).
ThreadPoolExecutor 执行任务时大致遵循如下规则:
- 如果线程池中的线程数量未达到核心线程的数量,那么会直接启动一个核心线程来执行任务。
- 如果线程池中的线程数量已经达到或者超过核心线程的数量,那么任务会直接插入到任务队列中排队等待执行。
- 如果在步骤2中无法将任务插入到任务队列里,这往往是由于任务队列已满,这个时候如果线程数量未达到线程池规定的最大值,那么会立刻启动一个非核心线程来执行任务。
- 如果步骤3中线程数量已经达到线程池规定的最大值,那么就拒绝执行此任务,ThreadPoolExecutor 会调用 RejectedExecutionHandler 的 rejectedExecution 方法来通知调用者。
线程池的分类
1. FixedThread
通过Executors 的 newFixedThreadPool 方法来创建,它是一种线程数量固定的线程池,当线程处于空闲状态时,它们不会被回收,除非线程池被关闭了。
当所有的线程都处于活动状态时,新任务都会处于等待状态,直到有空闲线程。由于此线程池只有核心线程并且这些核心线程不会被回收,这意味着它能够更加快速地响应外界的请求。
2. CacheThreadPool
通过Executors 的 newCacheThreadPool 方法来创建,它是一种线程数量不定的线程池,它只有非核心线程,并且最大线程数为 Integer.MAX_VALUE。当线程池中的线程都处于活动状态时,线程池会创建新的线程来处理新任务,否则就利用空闲的线程来处理新任务。线程池中的空闲线程超时时长为60s,超过就会被回收,线程池的任务会立即执行。所以这类线程池比较适合执行大量的耗时较少的任务。当整个线程池都处于闲置状态时,线程池中的线程都会超时而被停止,这个时候CacheThreadPool 之中实际上是没有任何线程的,它几乎是不占用任何系统资源的。
3. ScheduleThreadPool
通过Executors 的 ScheduleThreadPool 方法来创建,它的核心线程数是固定的,而非核心线程数是没有限制的,并且当非核心线程闲置时会被立即回收。ScheduleThreadPool 这类线程池主要用来执行定时任务和具有固定周期的重复任务。
4. SingleThreadExecutor
通过Executors 的 SingleThreadExecutor 方法来创建,这类线程池内部只有一个核心线程,它确保所有的任务都在同一个线程中按顺序执行。SingleThreadExecutor 的意义在于统一所有的外界任务到一个线程中,这使得在这些任务之间不需要处理线程同步的问题。