从事 Android 开发的都知道, 我们在进行耗时操作的时候是不能在主线程进行的, 不然会报 ANR 异常, 因此我们必须开启一个子线程, 在线程中处理耗时操作. 但是在子线程中我们拿到了耗时操作返回的数据之后需要在 UI 上展示, 但是在子线程又不能对 UI 进行更新, 于是乎在 Android 内部就有了消息通信机制 Handler 以及 AsyncTask. 今天我们来讲讲 AsyncTask 的使用以及配合他的源码来讲讲他的内部原理.
介绍
AsyncTask 是一个抽象类, 内部其实他定义了两个线程池 (ThreadPoolExecutor,SerialExecutor) 以及一个 Handler(InternalHandler). 而我们在使用 AsyncTask 的时候, 一般会往这个类里面传递三个参数, Params,Progress,Result
Params: 这个参数代表需要传进来进行加载或者进行耗时操作的参数. 一般来说我们可以传入一个网络请求的地址.
Progress: 这个参数代表我们需要在进行耗时操作的时候更进进度条的参数返回值.
Result: 这个参数代表我们在进行完成耗时操作之后拿到的结果数据.
而在这个类内部拥有四个方法.
onPreExecute(): 我们在进行初始化数据的时候调用这个方法, 这个方法是在主线程执行.
doInBackground(): 我们在进行耗时操作的时候调用这个方法, 所有的耗时操作都在这个里面进行操作. 并且将耗时操作的结果返回回去.
onProgressUpdata(): 对控件的进度进行操作.
onPostExecute(): 这个方法里面会拿到 doInBackground()方法中返回的参数结果, 我们在这个方法里面可以对 UI 进行操作, 从而达到更新 UI 的结果.
整体代码如下所示:
- class MyAsyncTask extends AsyncTask<String,Integer,String>{
- @Override
- protected void onPreExecute() {
- // 数据初始化操作
- super.onPreExecute();
- }
- @Override
- protected String doInBackground(String... strings) {
- // 耗时操作, 并将结果返回
- return null;
- }
- @Override
- protected void onProgressUpdate(Integer... values) {
- // 对进度条进行更新操作
- super.onProgressUpdate(values);
- }
- @Override
- protected void onPostExecute(String s) {
- //UI 更新操作
- super.onPostExecute(s);
- }
- }
复制代码
之后我们在主线程中使用 execute()方法来开启整个任务, 执行任务.
AsyncTask 的使用其实非常简单, 很多地方其本身就已经帮助我们封装好了, 这使得我们使用起来简单方便. 但是作为一个有追求的工程师, 单单会使用是远远不够的, 我们还必须了解其内部的原理, 从而达到知其然也知其所以然的目的. 下面我们来结合源码来谈谈内部原理.
源码解析
我们在之前说过, 其实 AsyncTask 的内部主要是对三个东西进行了封装处理, 两个线程池 (ThreadPoolExecutor,SerialExecutor) 以及一个 Handler(InternalHandler). 那内部是怎么对这些东西进行耗时操作的呐? 我们结合我们的时候, 一步一步来进行分析.
首先, 我们在使用 AsyncTask 的时候, 一般都会在主线程中 new 出这个对象来, 那么我们先来看看他的构造方法里面做了什么处理.
- /**
- * Creates a new asynchronous task. This constructor must be invoked on the UI thread.
- */
- public AsyncTask() {
- 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;
- }
- };
- 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);
- }
- }
- };
- }
复制代码
我们可以看到这里面代码很长, 但是主要就是做了两步操作. 第一, 实例化了一个 WorkerRunnable 方法, 第二个实例化了 FutureTask 方法. WorkerRunnable 方法其实是一个 Runnable 方法, 在第 12 行我们看到了我们之前的耗时操作 doInBackground()方法, 可见我们在进行耗时操作的时候, 其实是在这个方法中执行的. 执行完成之后将结果通过 postResult()方法返回. 而 FureTask 方法其实是实现了 Runnable 接口, 我们将 WorkerRunnable 这个方法传递进去, 具体的使用我们在后面会进行讲解.
在执行 AsyncTask 的时候, 我们会调用 execute(). 我们看下这个方法内部进行的操作.
- @MainThread
- public final AsyncTask<Params, Progress, Result> execute(Params... params) {
- return executeOnExecutor(sDefaultExecutor, params);
- }
复制代码
可以看到这个方法内部其实是调用了 executeOnExecutor()这个方法, 我们在来看看这个方法内部做了什么操作.
- @MainThread
- public final AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec,
- Params... params) {
- 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)");
- }
- }
- mStatus = Status.RUNNING;
- onPreExecute();
- mWorker.mParams = params;
- exec.execute(mFuture);
- return this;
- }
复制代码
我们看到这个方法内部其实做了这么几步操作.
首先通过一个 switch 来进行了两次判断, 第一次判断是否任务正在运行, RUNNING. 如果正在运行则抛出一个异常. 这就代表一个任务只能调用一次 execute, 调用两次则会报错. 第二次判断这个任务是否已经结束了, FINISHED, 如果结束了还执行也会抛出异常, 这个任务已经执行完成, 一个 task 只能执行一次操作.
之后我们用调用 onPreExecute()方法来进行初始化数据操作.
然后我们看到在最后我们执行了一个方法 exec.execute(mFutrue)将我们之前的 mFuture 任务传递进行.
那么 exec 是什么? 其实 exec 是 sDefaultExecutor, 再追溯上去发现这个 sDefaultExecutor 其实是 SerialExecutor. 那我们在跳进这个 SerialExecutor 这个类中看看里面执行了什么方法.
- private static class SerialExecutor implements Executor {
- final ArrayDeque<Runnable> mTasks = new ArrayDeque<Runnable>();
- Runnable mActive;
- public synchronized void execute(final Runnable r) {
- mTasks.offer(new Runnable() {
- public void run() {
- try {
- r.run();
- } finally {
- scheduleNext();
- }
- }
- });
- if (mActive == null) {
- scheduleNext();
- }
- }
- protected synchronized void scheduleNext() {
- if ((mActive = mTasks.poll()) != null) {
- THREAD_POOL_EXECUTOR.execute(mActive);
- }
- }
- }
复制代码
可以看到, 里面逻辑并不复杂. 首先是实例化了一个任务队列 ArrayDeque, 之后用一个 synchronized 进行一个同步锁操作. 这样保证的目的是一次只能往任务队列中添加一个任务, 只有等任务队列头部执行完成之后, 才能调用 scheduleNext 去执行下一个任务. 这就导致一个什么结果呐? 这就说明, 其实我们的 AsyncTask 在执行耗时操作任务的时候, 并不是并行操作, 而是使用了 SerialExecutor 来行了维护了一个任务队列, 通过同步锁的形式一个一个往里面添加执行的任务, 最后拿出来一个一个的操作. 其实其内部是个串行操作.
之后我们在来看下 ThreadPoolExecutor.
- ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
- CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE_SECONDS, TimeUnit.SECONDS,
- sPoolWorkQueue, sThreadFactory);
- threadPoolExecutor.allowCoreThreadTimeOut(true);
- THREAD_POOL_EXECUTOR = threadPoolExecutor;
复制代码
我们可以看到, 其实这里面维护了一个线程池的操作, 开启了一定数量的线程, 然后调用 execute()方法来执行这个线程, 即为我们开头说的那个 workerRunnable 线程中的任务操作. 最后将结果交给 postResult()方法去处理.
我们再来看看这个 postResult()方法内部是怎么进行操作的.
- private Result postResult(Result result) {
- @SuppressWarnings("unchecked")
- Message message = getHandler().obtainMessage(MESSAGE_POST_RESULT,
- new AsyncTaskResult<Result>(this, result));
- message.sendToTarget();
- return result;
- }
复制代码
我们可以从代码中看到, 其实内部有一个 Message 的消息机制, 他通过 sendToTarget()方法想 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:
- // There is only one result
- result.mTask.finish(result.mData[0]);
- break;
- case MESSAGE_POST_PROGRESS:
- result.mTask.onProgressUpdate(result.mData);
- break;
- }
- }
- }
复制代码
从源码中我们可以看到, 这个 handleMessage 里面做了两步操作, 第一是判断这个任务是否后已经结束了 MESSAGE_POST_RESULT, 如果是返回了这个则代表任务已经结束了, 调用 finish()方法来结束掉. 第二个是判断 MESSAGE_POST_PROGRESS, 代表更新进度条的操作, 等于说我们在使用更新进度条的时候一般会去调用 onPorgressUpdate()方法就是在这里面进行操作的.
最后我们在来看下 finish()方法中做了什么操作.
- private void finish(Result result) {
- if (isCancelled()) {
- onCancelled(result);
- } else {
- onPostExecute(result);
- }
- mStatus = Status.FINISHED;
- }
复制代码
我们可以很清楚的看到, 这里面其实也做了两步操作, 第一是判断这个异步任务是否取消 cancel 了, 如果 cancle 了就直接调用 onCancelled()方法取消任务; 如果不是 cancle 了那么就直接调用 onPostExecute()方法将 result 结果返回给主线程去使用.
到此, AsyncTask 的内部原理就全部分析完成了.
我们来总结一下:
AsyncTask 内部其实是维护了两个线程池 (ThreadPoolExecutor,SerialExecutor) 和一个消息机制 Handler(InternalHandler). 我们在使用 AsyncTask 的时候, 在构造方法中会先实例化一个 Runnable 接口对象 WorkerRunnable 和一个任务对象 FureTask.WorkerRunnable 是一个 Runnable, 一般我们的 doInbackground 等耗时操作都在这个里面进行, 最后将这个对象添加进入 FureTask 内部.
而在 AsyncTask 内部有两个线程池, SerialExecutor 的作用其实并不是执行线程操作. 而是维护了一个任务队列, 通过一个同步锁的形式不断往任务队列中添加异步任务, 并且在执行完队列头才能再去执行下一个任务, 这就导致了我们在执行多个异步任务的时候并非我们所想的是并行, 而是串行. 当然如果你想进行并行操作直接调用 executeOnExecutor()方法即可.
而另一个线程池 ThreadPoolExecutor 则是真正的执行异步操作的线程池对象. 他开启线程, 执行 FutureTask 中的任务, 在执行完成之后通过一个内部的 Handler, 即 InternalHandler 将结果通知给主线程, 即 onPostExecutor()方法, 从而执行更新 UI 的操作. 从而完成以上所以内部的请求.
来源: https://juejin.im/post/5b76620bf265da28084ec898