AsyncTask不完全解析
简介
AsyncTask是一个专注于UI线程 和 后台线程 之间进行交流的辅助类。 一般用于 由后台线程发起指定计算任务,并将其结果展示在UI线程上 的场景。
目前已被弃用(因为持有context引用,有可 能造成内存泄漏,缺失回调等原因),官方推荐采用
java.util.concurrent或者Kotlin协程 代替。但还是值得学习下。
使用
AsyncTask是一个抽象类,必须被子类继承使用。 继承后必须要实现doInBackground方法,里面写需要在后台执行的任务;通常还会实现onPostExecute,里面做UI界面想要做的操作。
1 | private class DownloadFilesTask extends AsyncTask<URL, Integer, Long> { |
执行AsyncTask:
1 | new DownloadFilesTask().execute(url1, url2, url3); |
源码解析
主要跟着源码梳理流程,我使用的版本是android-30。
首先AsyncTask是一个抽象类,带了三个泛型:
Params, 执行任务execute(Params p)时传入 的类型。Progress, 用来表示后台线程执行进度的 类型。Result,后台线程计算完后,返回结果的 类型.
1 | public abstract class AsyncTask<Params, Progress, Result> { |
而且看常量命名看的出来用到了线程池,核心线程1,最大线程20,维持3秒。属于即用即销的线程池,所以AsyncTask更适用于时间较短(几秒钟)的操作。
execute
直接看execute方法。@MainThread表示这个方法只能在主线程运行。
1 |
|
- sDefaultExecutor【先混个面熟】
sDefaultExecutor是一个静态全局的SerialExecutor。接着往下走。
executeOnExecutor
1 |
|
先判断全局状态,一共有三种PENDING(待处理)、RUNNING(正在运行)和FINISHED(已结束)。必须要PENDING待处理才能往下走,然后把状态变更为RUNNING,接着执行onPreExecute,这是我们在自定义AsyncTask中要重写的方法,做执行前的准备工作。
接着先把传进来的数据params存到一个叫mWork的东西中。 再执行exec.execute(mFuture);。【返回的是AsyncTask本身】
exec.execute(mFuture);
mFuture是啥?是个FutureTask(Runnable runnable),里面持有mWorker。
1 | mFuture = new FutureTask<Result>(mWorker) { ... } |
mWorker是啥?是个继承自Callable名字叫WorkerRunnable的类。
1 | mWorker = new WorkerRunnable<Params, Result>(){ ... } |
WorkerRunnable继承自Callable,里面啥都没有,就存了个Params数组。
1 | private static abstract class WorkerRunnable<Params, Result> implements Callable<Result> { |
刚才的params就是存到了这里的。
exec就是DefaultExecutor
所以要看上面第一步👆传来的静态全局常量sDefaultExecutor。定义为SerialExecutor。
1 | private static class SerialExecutor implements Executor { |
这个Executor内部有一个叫mTask的存储Runnable的双端队列【Deque:两边都可以进,都可以出】。
调用execute方法无非就是往mTask里面存了个Runnable进去,这个Runnable的run()方法执行了**我们传进来的mFuture**的run方法。
还有一个名叫mActive的runnable对象。刚进来肯定是null,所以走cheduleNext() ——从mTask中提一个出来,赋值给mActive,如果不为空,就把他(赋值后的mActive)放到线程池中去执行。
mActive.run —> mFuture.run() —> mWork.call()
一条线下来,我们要看mWork的call方法。
1 | mWorker = new WorkerRunnable<Params, Result>() { |
一眼就看到了result = doInBackground(mParams);,这里面是我们自己写的后台要处理的运算。
然后把result post出去。
1 | private Result postResult(Result result) { |
Handler处理消息
可以看到底层是用的Handler,getHandler返回的是一个内部的mHandler,在构造方法中被初始化的。
1 |
|
默认为空的情况下得到的是getMainHandler()。
1 | private static Handler getMainHandler() { |
里面主要是加锁初始化这个自定义的Handler:InternalHandler
1 | private static class InternalHandler extends Handler { |
InternalHandler接到消息
回忆下上一步传递消息用的是MESSAGE_POST_RESULT,所以在handleMessage中得到消息后走的是result.mTask.finish(result.mData[0]);就是走task的finish方法。
1 | private void finish(Result result) { |
结束后会调用onPostExecute方法,传入result。然后我们在继承AsyncTask时继承这个方法,就得到了结果。
publishProgress进度
虽然一套流程走完了,但我们还是没看到怎么将进度更新到UI线程的。因为更新UI的起点不在AsyncTask源码中,而在用户自定义的AsyncTask中。对,就是那句publishProgress()方法。
1 |
|
调用了这个方法后,传入进度,内部才会通过Handler将进度传递过来。代码还是在InternalHandler中,另一种情况:result.mTask.onProgressUpdate(result.mData);。 执行onProgressUpdate就又跳转到了用户的逻辑去了,实现了进度的传递。
以上就是AsyncTask工作时的全部流程。
面试问题
AsyncTask是如何实现线程的调度的?
AsyncTask.execute()方法实际是执行的SerialExecutor的execute方法,该方法做的事就是:向一个名为mTask的Runnable队列中插入Runnable,然后再取出、执行,取出、执行,取出、执行…直到队列为空为止。
而执行用的是THREAD_POOL_EXECUTOR.execute(Runnable),这个Runnable是在线程池中执行的。而且这个Runnable是一个传入mWorker的FutureTask,所以最终执行的是mWorker【一个Callable】的call方法,call方法中放的doInBackground,所以可以保障doInBackground始终在后台运行。
而返回的结果是通过Handler返回到了InternalHandler中,这个Handler传入的是Looper.getMainLooper(),而且它是在AsyncTask初始化的时候 初始化的。这就保证了返回的onPostExecute和onProgressUpdate都是在主线程中执行的了。





