AsyncTask?AsyncTask串行and并行?
- 文章独家授权公众号:码个蛋
- 更多分享:http://www.catbro.cn
一、前言
- AsyncTask,相信你不会陌生,也许你很幸运,早已了解了AsyncTask这个家伙挖的坑,也许你已经被坑过了,也许你没坑了,然而还没有发觉!
- 本次笔者将带大家一起来看下AsyncTask这个坑是如何挖出来的。
- 啥也不说啦,我们先来段代码看看 1、首先创建一个AsyncTask类
2、假设你这样运行
new MyAsyncTask("MyAsyncTask#1").execute();
new MyAsyncTask("MyAsyncTask#2").execute();
new MyAsyncTask("MyAsyncTask#3").execute();
new MyAsyncTask("MyAsyncTask#4").execute();
new MyAsyncTask("MyAsyncTask#5").execute();
new MyAsyncTask("MyAsyncTask#6").execute();
3、你觉得会发生什么呢?(。ì _ í。)
- 在Android3.0之前执行的时间是一样的,在Android3.0之后每个执行时间相差2秒
- android3.0虽然已经基本不用适配了,但是我们了解一下还是不错的哦
二、AsyncTask知识补充(如果你对AsyncTask已经很熟了,可以跳过该章节)
- 在讲解AsyncTask的坑之前,我们先了解下AsyncTask的源码,有助于我们理解为什么他是个坑,我们应该如何处理这个坑,当然,你也可以直接跳到下一章节。
- 系统为了帮助开发者简化线程的切换问题,很人性化的提供了轻量级的AsyncTask。AsyncTask的发展经历了几次的修改,进而为我们挖下了一个很容易被忽略掉的坑。
- AsyncTask作为一种轻量级的异步任务类,在Android开发过程中颇受开发者的喜爱。使用AsyncTaskNike 以在线程池中之行后台任务,并把之行的进度和最终结果传递给UI线程做进一步的操作。
- 本质上,AsyncTask也是封装了Thread和Handler,前面我们在 Android 开发之Handler的前世今生中通过Handler的源码分析并学习了Handler的实现流程。有需要的小伙伴可以点击一下,有不好的地方欢迎留言哦。
- 大家都知道AsyncTask是一个抽象类,一般我们继承AsyncTask来创建我们的AsyncTask,而创建的时候我们需要提供三个泛型参数(~~||我怀疑我是不是在瞎逼逼了), 这三个泛型参数跟AsyncTask关系不浅哦。 1、Params:表示我们在调用execute方法时传递的参数类型 2、progress:表示后台任务的执行进度类型 3、Result:表示后台任务的返回结果类型。 不需要的的数据可以设置为Void哦
- AsyncTask也为我们提供了如下方法: 1、onPreExecute():该方法会在主线程中被调用,你可以在该方法内部编写一些如显示加载动画的逻辑代码 2、doInBackground:该方法在子线程中执行,一般在该方法中编写需要在子线程中执行的操作。如果你有更新进度的需求,可以通过调用publishProgress方法,该方法会调用onProgresssUpdate方法,调用publishProgress方法时需要传递一个进度值,该值最终会在onProgresssUpdate中调用,为什么要这样做呢?因为onProgresssUpdate实在主线程中执行的,只有主线程才可以更新UI哦。当然执行doInBackground时你需要返回一个结果集。该结果集最终在onPostExecute中获取。同样的原因,onPostExecute是在主线程中运行的。 ╮(╯_╰)╭(线程总是切换来切换去,真烦是不是?) 3、onProgresssUpdate:上面讲过了,这里就略过了 4、onPostExecute:同样onPostExecute也是, 5、onCancelled 在主线程中执行。当异步任务被取消时被毁掉。此时onPostExecute则不会被调用了哦
三、AsyncTask使用注意事项
- 1、AsyncTask类第一次加载必须在主线程中加载(这个不需要我们关系,ActivityThread已经帮我们做了)
- 2、AsyncTask对象的创建代码必须在主线程中调用
- 3、execute方法必须在主线程中调用
- 4、onPreExecute、onPostExecute、doInBackground、onProgressUpdate 四个方法不能在程序中直接调用
- 5、一个AsyncTask实例只能调用一次execute方法。
- 6、AsyncTask执行execute方法时在Android1.6之前串行;Android1.6之后并行;Android3.0后串行
四、以日常使用为起点分析AsyncTask的源码(^V^)
-
Ok,假设你使用AsyncTask时都是使用execute方法来调用,那么我们就从他入手吧!~^_^
-
我们进入execute的方法体看下,里面做了什么呢
@MainThread public final AsyncTask<Params, Progress, Result> execute(Params... params) { return executeOnExecutor(sDefaultExecutor, params); }
-
我先我们看到一个@MainThread的注解。╮(╯_╰)╭这是什么东东呢?我没用过呢。
-
其实它是google提供的一个注解,标注我们的这个方法必须在主线程中调用,如果不是的话,AS就会红色提示我们哦。
-
然后我们可以看到,execute里面其实是执行了executeOnExecutor方法,他传入了两个参数:1、sDefaultExecutor(这是干什么的呢?)2、params 从execute传递下来的可变参数,OK,重点就是sDefaultExecutor了,从名字上看sDefaultExecutor是个磨人的执行器的意思,但我们是很较真的人,所以必须看下sDefaultExecutor是个什么东东( ̄∇ ̄),所以就逗逼逗逼的去找相关代码了
public static final Executor SERIAL_EXECUTOR = new SerialExecutor(); private static final int MESSAGE_POST_RESULT = 0x1; private static final int MESSAGE_POST_PROGRESS = 0x2; private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;
-
Ok,从其定义的地方可以看到,sDefaultExecutor是一个Executor类型的实例,通过SerialExecutor()获得。我们下看SerialExecutor()里面都做了什么?
private static class SerialExecutor implements Executor { //1、线程队列 final ArrayDeque mTasks = new ArrayDeque(); //2、当前执行的线程 Runnable mActive; public synchronized void execute(final Runnable r) { //3、创建一个新的runable入队列 mTasks.offer(new Runnable() { public void run() { try { //5、这里有点绕,首先新创建的runnable会先调用我们外部的runnable,外部的runnable执行完了再调用scheduleNext(),外部的runnable其实就是我们调用时传入的runnable,我们在线程中编写的代码在调用r.run()时开始执行。mTasks中的runnable只是对我们的runnable做了一次包装 r.run(); } finally { scheduleNext(); } } }); //4、如果当前没有正在执行的线程任务,则马上调用scheduleNext()方法取出队列中的runnable并运行。 if (mActive == null) { scheduleNext(); } } protected synchronized void scheduleNext() { //6、线程出队列,如果队列部位空,就通过THREAD_POOL_EXECUTOR.execute(mActive);线程池执行runable。 if ((mActive = mTasks.poll()) != null) { THREAD_POOL_EXECUTOR.execute(mActive); } } }
-
从上面代码可以看到,里面有一个mTasks的数组队列。当我们调用Execute的时候就会在mTasks队列中插入一个runnable实例,也就是说,mTasks里面存放的是线程,可能你会很奇怪,首先这里public synchronized void execute(final Runnable r)传如了一个runnable,然后这里mTasks.offer(new Runnable() 又创建一个新的runnable入队列。
-
OK,更详细的分析我放到了代码中
-
OK我们继续回到executeOnExecutor方法
@MainThread public final AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec, Params... params) { //1、判断任务是否没有被执行过,mStatus != Status.PENDING为true为被执行过,这也就是前面说的execute只能执行一次的原因,如果执行过下面会报运行时异常 if (mStatus != 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 = Status.RUNNING; //3、先调用onPreExecute方法 onPreExecute(); mWorker.mParams = params; //4、通过执行器调用我 exec.execute(mFuture); return this; }
小结:
- 通过上面代码,我们知道,在执行AsyncTask的execute方法时,我们的线程默认是放入一个ArrayQueue队列中串行的执行的。也就是sDefaultExecutor是一个串行的执行器,所以我们的AsyncTask默认是串行自行的。
-
Ok,我们来进一步分析下AsyncTask的执行过程,首先我们看下AsyncTask的构造方法都做了什么
public AsyncTask() { //1、 mWorker = new WorkerRunnable<Params, Result>() { public Result call() throws Exception { mTaskInvoked.set(true); Result result = null; try { Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); //noinspection unchecked result = doInBackground(mParams); Binder.flushPendingCommands(); } catch (Throwable tr) { mCancelled.set(true); throw tr; } finally { postResult(result); } return result; } }; //2、 mFuture = new FutureTask<Result>(mWorker) { @Override protected void done() { try { 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) { postResultIfNotInvoked(null); } } }; }
-
1处:创建一个WorkerRunnable,注意第二个泛型参数Result,这个就是我们继承AsyncTask时传递的第三个参数哦,然后里面看到调用了result = doInBackground(mParams);最终都会调用postResult(result);方法
-
2处:创建一个FutureTask的对象,mFuture就是我们前面executeOnExecutor方能发中最重调用exec.execute(mFuture);传入的参数哦,说明mFuture是个runnable类型的实例。然后把mWorker传入创建,说明runable执行的是mWorker里面的工作,yes,所以我们的doInBackground方法是在runable线程中执行的
-
OK,我们之前只说了我们的代码是如何放入子线程中的,但是并没有看见启动子线程的代码啊?我们可以看奥,他最终都调用的postResult方法
private Result postResult(Result result) { @SuppressWarnings("unchecked") Message message = getHandler().obtainMessage(MESSAGE_POST_RESULT, new AsyncTaskResult<Result>(this, result)); message.sendToTarget(); return result; }
-
Ok,可以看到,里面是通过我们熟悉的Handler来进行实现的哦。我们看他他传入了MESSAGE_POST_RESULT和AsyncTaskResult,this就是我们的AsyncTask了,后面应该是在handler中调用了AsyncTask的某个方法,我们看下getHandler()
private static Handler getHandler() { synchronized (AsyncTask.class) { if (sHandler == null) { sHandler = new InternalHandler(); } return sHandler; } }
-
OK,里面是一个InternalHandler实例。而InternalHandler是这样的
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: // There is only one result result.mTask.finish(result.mData[0]); break; case MESSAGE_POST_PROGRESS: result.mTask.onProgressUpdate(result.mData); break; } } }
-
OK,里面看到了MESSAGE_POST_RESULT对应的是finish方法。也就是我们的AsyncTask的finish方法
private void finish(Result result) { if (isCancelled()) { onCancelled(result); } else { onPostExecute(result); } mStatus = Status.FINISHED; }
-
可以看到,里面最终会调用onCancelled(result);或者onPostExecute(result);
小结:
- 创建 AsyncTask是会创建mWorker和mFuture,mFuture是一个runable类型的对象,最终会在我们执行execute-》executeOnExecutor时,传入执行队列中等待执行。在传如前先调用了onPreExecute()方法,在mFuture被执行的时候,会回调mWorker的call方法,call方法里会调用doInBackground方法,获得doInBackground的执行结果后调用postResult方法,postResult内部通过handler切换线程,最终调用我们的finish方法,finish里面会调用onCancelled(result)或者onPostExecute(result);中的一个。这样我们的AsyncTask的一个关键流程就走完了。
五、我想让AsyncTask并行执行,可以么?
-
当然可以,我们前面分析了,串行还是并行,关键是执行器。因为默认传入的是sDefaultExecutor,sDefaultExecutor是个串行的执行器,所以我们传入一个并行的执行器,是不是就可以了呢?
-
我们可以看到AsyncTask也提供了一个THREAD_POOL_EXECUTOR,根据注解,使用它可以让execute 并行工作
/**
- An {@link Executor} that can be used to execute tasks in parallel. */public static final Executor THREAD_POOL_EXECUTOR;
-
看到这里我有点郁闷了,前面在分析SerialExecutor是,里面不也是将runable让入THREAD_POOL_EXECUTOR中执行么?
-
SerialExecutor为什么是串行呢?虽然也是放入THREAD_POOL_EXECUTOR中,但是他是从队列中取出来放入的哦,而且一次只取出一个,执行完再取第二个
总结:
- 通过本次学习,我们了解了AsyncTask从创建到执行返回结果的工作流程,也明白了为什么默认时串行执行的,当然,改为并行也是非常简单的,你可以自己自定义执行器,自己来控制,但是一般都不需要的( ̄∇ ̄);通过本次学习,相信在使用AsyncTask时,你会更加踏实。厚积而薄发!