综述
Android提供了四种常用的操作多线程的方式,分别是:
- HandlerThread
- AsyncTask
- IntentService
- ThreadPoolExecutor
- HandlerThread: 为某些回调方法或者等待某些任务的执行设置一个专属的线程,并提供线程任务的调度机制。
- AsyncTask: 为 UI 线程与工作线程之间进行快速的切换提供一种简单便捷的机制。适用于当下立即需要启动,但是异步执行的生命周期短暂的使用场景。
- IntentService: 适合于执行由 UI 触发的后台 Service 任务,并可以把后台任务执行的情况通过一定的机制反馈给 UI。
- ThreadPool: 把任务分解成不同的单元,分发到各个不同的线程上,进行同时并发处理。
HandlerThread在之前的一篇文章里已经讲过了,ThreadPoolExecutor是Java中处理多线程的方法,下面从源码角度分析AsyncTask和IntentService的原理
AsyncTask
基本用法
1 | //继承抽象类 实现抽象方法doInBackground |
AsyncTask是个抽象类,我们需要实现doInBackground
,通常我们还会实现onProgressUpdate
来做一个进度的回调与onPostExecute
实现结果的回调。在doInBackground
内部通过publishProgress
来通知onProgressUpdate
,怎么通知会放到下面doInBackground的分析中讲解。
1 | public abstract class AsyncTask<Params, Progress, Result> {...} |
还需要传入三个泛型来分别表示执行参数Params,进度参数Progress,结果参数Result。
Params对应着execute
和doInBackground
的参数类型
1 | public final AsyncTask<Params, Progress, Result> execute(Params... params){...} |
Progress对应着onProgressUpdate
和publishProgress
的参数类型
1 | protected void onProgressUpdate(Progress... values) {...} |
Result对应着onPostExecute
的参数类型
1 | protected void onPostExecute(Result result) {...} |
知道了每个方法对应着什么样的作用,用起来就很简单了。
源码分析
构造函数
不管怎么调,最后都是来到这个方法
1 | public AsyncTask(@Nullable Looper callbackLooper) { |
当我们调用的是无参的构造函数时,mHandler就是InternalHandler的实例,下面在doInBackground中会详细说明。
mWorker是一个基于Callable接口封装的WorkerRunnable的实例,也就多了个储存执行参数的数组而已
1 | private final WorkerRunnable<Params, Result> mWorker; |
mFuture就是FutureTask<Result>
的实例
execute
1 |
|
有2个值得注意的点:
- @MainThread 意味着excute需要在主线程执行
- 检测mStatus来抛出异常,说明一个AsyncTask对象只能执行一次
检查和赋值完状态,调用了onPreExecute
,用户可以重写这个方法来做一些预处理。
然后把执行的参数赋值给mWorker,然后调用exec.execute(mFuture);
,结合调用关系,知道exec就是sDefaultExecutor
Executor
sDefaultExecutor是SerialExecutor的实例,SerialExecutor实现了Executor,内部维护一个双端队列mTasks与一个代表当前任务的mActive。
1 | public static final Executor SERIAL_EXECUTOR = new SerialExecutor(); |
代码很简单,执行上述的exec.execute(mFuture);
实际上就是把mFuture经过一个匿名的Runnable包装后添加到sDefaultExecutor的mTasks中,然后从mTasks中取出队列头的Runnable任务并通过线程池THREAD_POOL_EXECUTOR来执行。
由此可以知道AsyncTask是串行执行任务的。执行的任务是被包装后的mFuture,实际上就是mFuture的callable变量,即mWork。
如果你不想串行执行任务,直接使用内部的线程池THREAD_POOL_EXECUTOR来执行,那么可以直接调用executeOnExecutor
方法,指定Executor来执行任务
1 | asyncTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); |
doInBackground
在回到构造函数中看看mHandler和mWork的创建过程。
1 | private static class InternalHandler extends Handler { |
mHandler是InternalHandler的实例,针对MESSAGE_POST_RESULT
和MESSAGE_POST_PROGRESS
做了处理,分别对应这结果的通知和进度的通知。
再来看看mWork。
1 | mWorker = new WorkerRunnable<Params, Result>() { |
mWorker最后会被Executor调用,call方法中调用了由用户重写的doInBackground,执行具体的工作任务。
那么为什么在doInBackground中执行publishProgeress
就可以回调通知到onProgressUpdate
呢,看了上面InternalHandler的封装心里基本也有点数了
1 |
|
publishProgress
发送了MESSAGE_POST_PROGRESS
同样的在mWorker的call方法中finally块执行了postResult
,发送了MESSAGE_POST_RESULT
内存泄漏
与Handler一样,如果作为内部类,AsyncTask也是持有外部Activity的引用的,当两者生命周期不同步的时候,容易造成内存泄露。务必在Activity的onDestroy中调用onCancelled
方法,在onCancelled
中关闭网络连接与资源请求等。
IntentService
HandlerThread
还是再讲讲HandlerThread吧,发现又有点忘了。
1 | public class HandlerThread extends Thread { |
源码没什么好分析的,一个线程,封装好Looper,就一直loop循环等待消息到来直到quit。
跟一般的线程比起来就是提高了与主线程的交互能力,主线程可以在HandlerThread启动之后,通过getLooper
新建一个Handler来与HandlerThread建立起连接,通过sendMessage
来通知HandlerThread线程,并重写这个Handler的handleMessage
来做对应的处理。
实例:
1 | Handler mUIHandler = new Handler(); |
基本用法
1 | public class MyIntentService extends IntentService { |
继承IntentService,实现构造方法和onHandleIntent
即可,剩下的按照普通Service处理(清单注册,intent启动)即可。
下面来分析一下内部实现。
onCreate
1 |
|
跟上述我们使用HandlerThread一样的用法,让mServiceHandler与HandlerThread建立起关联。
onStart
1 |
|
一启动就使用mServiceHandler发了个消息
1 | private final class ServiceHandler extends Handler { |
回调了onHandleIntent
,该方法由用户实现,并且调用了stopSelf
,体现IntentService用完就走的设计思路。
ThreadPoolExecutor
再补充一下线程池中相关。ThreadPoolExecutor提供了4个构造方法,最后都会调用到下面这个。通过构造方法的一系列参数,来构成不同配置的线程池。
1 | public ThreadPoolExecutor(int corePoolSize, |
挑几个比较重要的拿出来讲一下。
BlockingQueue
- SynchronousQueue 直接将Runnable交给核心线程处理,如果核心线程都在工作则新建工作线程来执行(一般来说maximumPoolSize为Integer.MAX_VALUE)。
- LinkedBlockingDeque 当前线程数小于核心线程数,新建核心线程处理任务。如果当前线程数等于核心线程数,则进入队列等待。
- ArrayBlockingQueue 创建时可以指定capacity,能新建核心线程就新建,不能就入队等待,等待数如果超过capacity,则新建工作线程执行任务,如果总线程数超过了maximumPoolSize则报错。
ThreadFactory threadFactory,提供对线程一些属性定制的操作
执行
1 | public void execute(Runnable command) { |
ctl变量
原子级操作的整数,前3位表示运行状态,后29位表示运行的线程数。
1 | private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0)); |
addWorker
1 | private boolean addWorker(Runnable firstTask, boolean core) { |
1 | private final class Worker extends AbstractQueuedSynchronizer implements Runnable |
去掉了一些对状态检测的代码,检测没问题就会实例化一个Worker的实例,然后对状态再进行一次check,没问题就调用t = w.thread,t.start()
。根据Worker的封装 这个t在实例的时候传入的Runnable参数就是worker本身,所以这个t.start调用的就是w的run方法
1 | public void run() { |
在runWorker中取出firstTask,并调用其run方法来执行任务。
常见的4个线程池
newCachedThreadPool
1 | public static ExecutorService newCachedThreadPool() { |
核心线程数0,最大工作线程数为Integer.MAX_VALUE,可灵活回收空闲线程,若无可回收,则新建线程。
newFixedThreadPool
1 | public static ExecutorService newFixedThreadPool(int nThreads) { |
核心线程数即最大工作线程数,由用户控制,可控制线程最大并发数,超出的线程会在队列中等待。
newScheduledThreadPool
1 | public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) { |
核心线程数由用户控制,最大工作线程数为Integer.MAX_VALUE,可创建支持定时及周期性任务执行的线程。比起Timer来应该优先使用这个。
newSingleThreadExecutor
1 | public static ExecutorService newSingleThreadExecutor() { |
核心线程=最大工作线程=1。创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序执行。
ExecutorCompletionService
Callable、Future和FutureTask
通常做网络请求的时候,在Runnable中回调response来起到获取请求结果的效果。Callable就避免了这种尴尬的做法,因为他有返回值。
通常与线程池一起使用,通过线程池的submit方法返回一个Future对象。
1 | <T> Future<T> submit(Callable<T> task); |
正常的使用如下:
1 | // 队列 |
获取结果可以直接使用Future的get方法,来阻塞获取也可以循环判断future.isDone()
来获取。
再来看一下FutureTask这个类:
1 | public class FutureTask<V> implements RunnableFuture<V> { ... } |
通过接口的多继承性来同时具备了Runnable和Future的特性。
使用如下,与Future区别的具体使用场景还没搞懂。。
1 | final FutureTask<String> futureTask = new FutureTask<>(() -> "task done"); |
ExecutorCompletionService
todo