今天是年初一,打算新的一年有一个好的开始。过年放假再家里也没有什么事情。今天是放假第一天就计划挑一个简单一点的主题来写一个博客;在前一段时间总是时不时的听各位大佬提起基础的重要性,所以这次就Android的基本异步组件AsyncTask来做一个解析,虽然现在已经各种RxJava线程随意切,但是看看AsyncTask的实现多少还是可以学到一些东西的;
进入正题,AsyncTask的使用还是比较简单的最主要的就是去实现一个doInBackground()方法,大概的使用方法如下
new AsyncTask<String, Integer, String>() {
@Override
protected void onProgressUpdate(Integer... values) {
Log.d(TAG, "onProgressUpdate: 当前的进度 = " + values[0]);
}
@Override
protected String doInBackground(String... params) {
Log.e(TAG, "doInBackground: 參數 = " + params[0]);
for (int i = 0; i < 100; i++) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
//刷新进度条的方法
publishProgress(i);
}
return "后台任务结束了! ";
}
@Override
protected void onPostExecute(String s) {
Log.d(TAG, "onPostExecute: 结果 = " + s);//不出意外s = "后台任务结束了";
}
}.execute("我是参数");
使用方法很简单一般不需要管后台运行进度的话onProgressUpdate方法可以不用实现;如果不需要返回结果的话onPostExecute方法也可以不用实现;真正厉害的地方只有一个doInBackground();
虽然大家都说AsyncTask内部就是一个Thread Handler来实现异步,但是它到底是怎么用Thread来搞事情的还是可以稍微看看的;其实里面还是有一些套路的;可能用过(被坑)的人会比较了解;好了废话不多说,首先就直奔主题;它不就是new一个实例调用一下execute()方法嘛;那我们就从这个地方入手;
@MainThread
public final AsyncTask<Params, Progress, Result> execute(Params... params) {
//乍一看没啥东西,那就接着看呗 ctrl按住戳这个方法
return executeOnExecutor(sDefaultExecutor, params);
}
@MainThread
public final AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec, Params... params) {
[1]----------
if (mStatus != android.os.AsyncTask.Status.PENDING) {
switch (mStatus) {
case RUNNING:
throw new IllegalStateException("Cannot execute task:" + " the task is already running.");
case FINISHED:
throw new IllegalStateException("Cannot execute task:" + " the task has already been executed " + "(a task can be executed only" +
" once)");
}
}
[2]----------
mStatus = android.os.AsyncTask.Status.RUNNING;
onPreExecute();
mWorker.mParams = params;
[3]----------
exec.execute(mFuture);
return this;
}
直接看executeOnExecutor方法;发现进来直接给你来个判断,判断这个AsyncTask是不是正经的AsyncTask!!!
[1]这里首先就给你判断一下这个叫mStatus的东西;那这个东西干啥用的呢?戳进去再看看,看了发现就是用来标记以下AsyncTask的状态(PENDING,RUNNING,FINISHED看名字就知道了);然后看一下定义一开始就先给你初始化一个Status.PENDING;所以只要这个AsyncTask是正经的(第一次创建的)那肯定就是Status.PENDING;
从这里也可以看出一个正经的AsyncTask是不能重复用的
[2]这里AsyncTask先大声的说我要开始搞事了!!!(给mStatus设置一个状态Status.RUNNING)
接着给你一个回调方法(onPreExecute)再通知你一下我要开始搞事了!(这里注意一下目前都还是运行在AyncTask调用execute方法的线程,一般情况下都会在主线程里面调用,所以这里一般就是主线程)
接着往下看,看到一个mWorker;而且还给mWorker.mParams赋值了我们在execute方法里面传进来的参数;
mWorker这是什么鬼,从哪里冒出来的!一般像这种东西我直接new的AsyncTask中间啥乱七八糟的方法都没调用过就只是执行了execute,那肯定就是在构造函数的地方申明创建的;
好那就去瞅瞅构造函数里面有什么玄机;
public AsyncTask() {
mWorker = new WorkerRunnable<Params, Result>() {
public Result call() throws Exception {
[1]
mTaskInvoked.set(true);
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
[2]
//这个Param就是一开始execute方法赋值给Worker的参数传递过去
Result result = doInBackground(mParams);
//防止Thread从内核角度去长时间持有对象导致的内存泄漏?
Binder.flushPendingCommands();
//当前所在的线程就是sDefaultExecutor线程池分配的
// 通过handler发送随后执行的结果
[3]
return postResult(result);
}
};
...省略一些代码一会儿再分析
..
.
}
private static abstract class WorkerRunnable<Params, Result> implements Callable<Result> {
Params[] mParams;
}
[3]
private Result postResult(Result result) {
@SuppressWarnings("unchecked")
Message message = getHandler().obtainMessage(MESSAGE_POST_RESULT, new AsyncTaskResult<Result>(this, result));
message.sendToTarget();
return result;
}
这里看看!看到没有硕大的一个doInBackground方法!!先忍住,首先我们知道马上就要到重点了;
我们先看看这个mWorker(也就是WorkerRunnable)到底是什么东西;
往下看这个类其实非常非常的简单WorkerRunnable = Callable + 参数mParams;就是一个比普通Callable多一个参数的Callable;(如果不知道Callable是啥你就把他简单的认为是一个Runnable一样的东西,就是扔给别人让别人去做call()方法里面的事情的这么一个东西)
[1]这里他先给一个标记,标记这次任务是不是已经被执行了,反正这里可以先有个印象就行;
[2]这里,重点来了这里面调用了doInBackground方法
还记不记得刚刚看到这个mWorker的时候就是在给他mParams参数赋值的时候(execute方法里面);也就是说我们在执行了execute方法的时候mParams就已经是我们在execute方法里面给的参数了;然后这里把这个参数给了doInBackground方法;
这个doInBackground的方法就是我们需要实现的后台搞事情的方法
说实话下面那行Binder….的那串方法我也不知道干啥的知道的可以告诉我一下,不胜感谢。(避免持续引用防止内存泄漏?)
[3]这里最后等着doInBackground方法执行结束以后会把结果给一个postResult()的方法;戳进去看看里面发送了一个消息给handler;并且把结果也带上了;这里就是AsyncTask切换线程的重点了,通过Handler把线程切换到主线去了。
可以简单的看看这个Handler怎么工作的
private static class InternalHandler extends Handler {
public InternalHandler() {
super(Looper.getMainLooper());
}
@SuppressWarnings({"unchecked", "RawUseOfParameterizedType"})
@Override
public void handleMessage(Message msg) {
AsyncTaskResult<?> result = (AsyncTaskResult<?>) msg.obj;
switch (msg.what) {
case MESSAGE_POST_RESULT:
[1]
result.mTask.finish(result.mData[0]);
break;
[2]
case MESSAGE_POST_PROGRESS:
result.mTask.onProgressUpdate(result.mData);
break;
}
}
}
[1]
private void finish(Result result) {
if (isCancelled()) {
onCancelled(result);
} else {
onPostExecute(result);
}
mStatus = android.os.AsyncTask.Status.FINISHED;
}
[1]这里的方法就比较简单了,handler接受到消息已经吧消息里面的msg.obj的参数(参数包括asyncTask自己的实例和最后通过doinbackground执行完以后的结果)拿出来然后调用AsyncTask自己的finish方法;然后这个isCancelled是怎么回事先不管先看看这个在不主动cancel掉AsyncTask的时候会调用第二部分,也就是onPostExecute(result);
这里就实现了整个AsyncTask异步处理线程然后像主线程返回结果的过程
是不是有点乱了,莫慌!!! 那就先总结一下worker他到底在搞什么事情
- 首先这个worker就是一个带着参数的callable
- 这个callable的call方法会去执行我你们实现的doInBackground方法
- doInBackground方法会通过Message来发送doInBackground方法的结果给Handler(主线程)
- 最后AsyncTask通过回调方法onPostExecute在主线程告诉我们最后的结果是什么
完事了,应该清晰点了吧;
有人可能会问,不对啊这个callable是怎么回事?谁去调用了这个callable的call方法?他到底在哪个线程运行的?这就完事了?你逗我呢?
好吧,确实还有一点东西没说明白,那个。。。刚刚从哪边开始岔开的?对了!!!是想分析这个Worker是什么鬼!那再回去看看呗
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) {
...
..
.
[2]----------
mStatus = android.os.AsyncTask.Status.RUNNING;
onPreExecute();
mWorker.mParams = params;
[3]----------
exec.execute(mFuture);
return this;
}
刚刚都是在分析[2]里面的worker是什么东西,最后就剩下一步[3]了。
[3]这个exec就是sDefaultExecutor就是一个线程调度器,里面有一个线程池这个线程池;
mFuture里面就包含着这个worker,最后这个sDefaultExecutor(可以理解为线程池)会喊一个小弟(创建一个线程)来搞事情(执行这个包含worker(callable)的mFuture的call方法里面的东西)
到这里其实AsyncTask是怎么来异步来搞事情的已经很明确了;一般到这里大家应该可以清楚的了解AsyncTask异步工作的原理了。
但是这后面其实还有一些细节的问题。比如mFuture到底是什么东西?这个线程池又是怎么配置的?如果一坨AsyncTask一起创建一起去异步搞事情会怎么样?会爆炸嘛?!
好了,下面只是我自己的笔记(其实上面也是),你有兴趣也可以往下看会有收获哟(反正我自己感觉挺厉害的)
首先来看看第一个问题mFuture到底是什么?直接去找mFuture的实现(ctrl + 左键戳它)发现是在构造函数里面的刚刚被我省略的那块里面。。。那下面就来看看AsyncTask完整的构造函数
public AsyncTask() {
mWorker = new WorkerRunnable<Params, Result>() {
public Result call() throws Exception {
mTaskInvoked.set(true);
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
//noinspection unchecked
//这个Param就是一开始execute方法赋值给Worker的参数传递过去
Result result = doInBackground(mParams);
//防止Thread从内核角度去长时间持有对象导致的内存泄漏?
Binder.flushPendingCommands();
//当前所在的线程就是sDefaultExecutor线程池分配的
// 通过handler发送随后执行的结果
return postResult(result);
}
};
[0]
mFuture = new FutureTask<Result>(mWorker) {
@Override
protected void done() {
//这个方法是保险作用,保证在强行关闭或者异常的情况下任然会有最后的回调,只不过回调的结果会是null
try {
//这个时候应该是未开始执行的时候强行cancel导致的
[1]
postResultIfNotInvoked(get());
} catch (InterruptedException e) {
android.util.Log.w(LOG_TAG, e);
} catch (ExecutionException e) {
throw new RuntimeException("An error occurred while executing doInBackground()", e.getCause());
} catch (CancellationException e) {
//主动调用cancel方法时候会回调这个方法,這個方法执行的条件必须是mWorker没有被执行的时候提前调用
//cancel方法,这个时候再去主动调用上面的get方法会有CancellationException异常,这个时候主动回调
//一个null的结果回去,保证接下来的onCancel会被回调
[2]
postResultIfNotInvoked(null);
}
}
};
}
public class FutureTask<V> implements RunnableFuture<V>
public interface RunnableFuture<V> extends Runnable, Future<V>
上面有一些是我自己加的注释(中文的都是我自己加的)看的懂可以看看看不懂我也会分析的,还是那句话,莫慌!!!
首先看mFuture 他是FutureTask的实例,那FutureTask又是什么鬼呢?
[0] 看一下FutureTask的继承关系。明白了吧,这个东西就是Runnable和Future的结合体;(如果不知道future到底是什么的建议还是度娘一下吧很多的,这里反正我假装你已经知道了)他的构造函数刚刚好是需要一个callable也就是那个worker当参数的
呐,记住他是一个混血儿(即是Runnable又是Future它实现了两个接口)他是一个Runnable,所以可以把他丢给Executor去执行;
他是一个Future所以他可以自由的控制cancel和获得callable最后执行的结果;是不是很厉害。。
这个done方法是callable方法彻底结束(这里结束可能是被强行cancel掉结束也可能是正常运行callable的call方法结束)以后回调的(有兴趣可以看看源码,是通过Locksuppor来先释放挂起的get方法然后再回调done方法);这里再done方法里面拿结果应该是保证在任何情况下都能够被回调。
[1]这里是能够正常通过future的get方法获得结果时候调用的
[2]这个地方被调用一般情况是futureTask里面的callable**还没有被执行就被cancel掉**了然后这个时候去调用了一下future.get方法试图去获取结果,这个时候往往会丢出CancellationException异常,这里也是保证一下能正常的回调onCancel方法吧。
接着再来看看第二个问题,AsyncTask里面的线程池(线程调度器)到底是怎么配置的?
AsyncTask里面有一个默认的线程池和线程调度器
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 BlockingQueue<Runnable> sPoolWorkQueue =
new LinkedBlockingQueue<Runnable>(128);
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;
}
线程池(调度器)配置:
- 线程池核心线程数为2-4个(主要根据cpu核数来定,考虑到性能速度)
- 因为设置了allowCoreThreadTimeOut(true),所以这个不管是不是核心线程如果超过KEEP_ALIVE_SECONDS(30秒)以后接不到活就会被回收释放
它用的缓存队列是LinkedBlockingQueue,也就是说核心线程如果都在干活(2-4个都在干活)并且队列没有超过128个限制则所有新加进去的任务都会阻塞等待核心线程里面有干完活的线程再来处理队列中的任务(偷偷的告诉你,其实AsyncTask自己有队列,这个队列(LinkedBlockingQueue)它根本就没用到)
线程池这块有兴趣的可以去了解一下,里面的规则还是挺有用的尤其是里面的不通过锁来实现安全的并发处理事情的方法还是不错的,可能后面我也会整理一下。不对没有可能!我一定会整理的!
然后是第三个问题,如果一坨AsyncTask一起创建一起去异步搞事情会怎么样?
其实上面已经解答了一部分了,再怎么样最多也只能是同时执行2-4个线程,但是真的能同时执行2-4个任务嘛,这里主要还是取决于真正去执行Future的那个executor,也就是sDefaultExecutor (SerialExecutor)
private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;
private static class SerialExecutor implements Executor {
final ArrayDeque<Runnable> mTasks = new ArrayDeque<Runnable>();
Runnable mActive;
public synchronized void execute(final Runnable r) {
[1]
mTasks.offer(new Runnable() {
public void run() {
try {
r.run();
} finally {
// 当第一个Runnable执行完以后又会调用scheduleNext() 方法去取出一个然后再用线程池(THREAD_POOL_EXECUTOR)来分配线程执行Runnable
//后面就是循环一个执行完成再拿一个出来执行(有顺序的 一个执行完执行另外一个,这里相当于是同步的执行任务)
scheduleNext();
}
}
});
//这里的这个方法只会执行一次,就是再第一次运行的时候(调用execute方法的时候)
//因为只有第一次调用的时候mActive才会是null后面会在scheduleNext()方法里面赋值,每次指向的都是队列头部刚刚取出来准备执行的Runnable
//第一次启动是真正的去用线程池执行一个Runnable,后面调用execute相当于只是执行上面的mTasks.offer方法(往队列中放Runnable)
[2]
if (mActive == null) {
scheduleNext();
}
}
protected synchronized void scheduleNext() {
if ((mActive = mTasks.poll()) != null) {
THREAD_POOL_EXECUTOR.execute(mActive);
}
}
}
SerialExecutor看名字就知道了这个是一个有序列的执行器,最一开始在execute()方法(AsyncTask的execute方法)里面看到的其实就是这个东西(exec.execute(mFuture) 记起来了吧 这个exec就是SerialExecutor)
[1]先看看这个Executor最主要的方法execute
二话不说管你什么Runnable我先装起来打包(自己创建一个runnable封装以下)然后给你丢到队列里面去,你就在队列里面乖乖的等着吧,注意这里的队列指的是ArrayDeque mTasks这个队列,不是线程池的队列哦;
为什么要创建Runnable呢其实就是为了在丢给他的runnable(的run()方法)执行完以后再执行一个scheduleNext();
scheduleNext()方法非常简单,就干了两件事情
1. 给一个叫mActive的赋值一下,把当前准备去执行的runnable赋值给它
2. 拿着(刚刚分析的那个)线程池(只运行2-4个核心线程的那个)去执行这个runnable;
[2]这里是针对第一次的时候,因为刚刚也说了管你什么Runnable先装起来打包丢进队列再说;所以前面只是把runnable装起来丢进队列而已;真正的其实还没有执行呢,所以这里要开头一下,去主动调用一次scheduleNext();(因为这个Executor刚刚创建或者之前把任务执行完了都会是null所以这个if判断就通过了)
后面只要一个任务的run方法执行完了都会调用scheduleNext方法,scheduleNext方法又会找一个任务(runnable)出来再去执行run方法,就这么循环;
只要正在处理着任务后面要是继续调用execute加入新的Runnabel,也只不过打包丢到队列里面去;
看了上面的那坨分析,你有没有发现不对的地方;一个runnable执行完了才调用下一个?坑爹了,这个AsyncTask的执行器只允许任务一个一个的去异步执行!!!!所以这也解释了第四个问题,AsyncTask不会爆炸!因为他是一个一个搞事情一个搞完搞下一个;
好了上面就是我对AsyncTask的分析
总结一下上面值得学习的东西
里面有一些可以用到自己项目中的东西,比如你希望线程异步去执行一个有顺序的并且可以缓存起来一个一个去执行的需求(比如你需要异步去写一些重要的日志到本地文件里面来帮助分析一些release包或者线上bug,这个日志的顺序又不能变,写日志又是耗时的)就可以用SerialExecutor的写法一样搞定;这也是为什么要看源码的原因,在了解源码实现逻辑的同时也可以看看有什么东西是可以捞出来用的。
上面写的有任何不对的地方都可以指出来,大家互相学习一起提升。那最后祝大家新年快乐!