大厂Android高频问题:谈谈AsyncTask原理?

2,079 阅读11分钟

「这是我参与11月更文挑战的第2天,活动详情查看:2021最后一次更文挑战


前言

AsyncTask原理可以说是Android开发面试高频的一道问题,但总有小伙伴在回答这道问题总不能让面试满意, 本篇就搞清楚面试官问你对AsyncTask原理时,他最想听到的和其实想问的应该是哪些?一个是你是否了解AsyncTask的使用,第二个是有没有研究过AsyncTask的源码,下面我们通过以下几点来剖析这道问题!

  1. AsyncTask的使用
  2. AsyncTask的源码解析
  3. AsyncTask的原理总结

AsyncTask的使用

AsyncTask是一个Android中封装的轻量级异步类,主要可以帮我们实现工作线程与UI线程之间的通信,即在工作线程中执行耗时操作,并将结果传递到主线程,然后在主线程中进行相关的UI操作。使用一般分三步:

  • 实现AsyncTask子类,按需重写相应的方法
  • 创建AsyncTask子类的实例对象
  • 手动调用AsyncTask的execute方法,触发AsyncTask的执行
/**
 * 创建AsyncTask:一般通过继承AsyncTask抽象类
 * 可指定3个泛型参数的类型,泛型参数分别是Params, Progress, Result
 * 按需重载内部提供的方法
 */
private static class MyAsyncTask extends AsyncTask<Integer, Integer, String> {
  
    /**
     * 执行后台任务前的操作,用于初始化操作,例如数据重置、显示加载进度条等
     */
    @Override
    protected void onPreExecute() {
        super.onPreExecute();
    }
  
    /**
     * 真正执行后台耗时任务,例如下载服务器资源
     *
     * @param params 后台任务的输入参数,类型与AsyncTask第一个泛型一致
     * @return
     */
    @Override
    protected String doInBackground(Integer[] params) {
        return null;
    }
  
    /**
     * 更新后台任务执行进度,回调到UI线程,通过手动调用 publishProgress 方法触发更新
     *
     * @param progress 执行进度,类型与AsyncTask第二个泛型一致
     */
    @Override
    protected void onProgressUpdate(Integer[] progress) {
        super.onProgressUpdate(progress);
    }
  
    /**
     * 后台任务执行结果,回调到UI线程
     *
     * @param result 执行结果,类型与AsyncTask第三个泛型一致
     */
    @Override
    protected void onPostExecute(String result) {
        super.onPostExecute(result);
    }
  
    /**
     * 后台任务被取消,回调到UI线程
     */
    @Override
    protected void onCancelled() {
        super.onCancelled();
    }
}
  
/**
 * AsyncTask的使用
 */
private void testAsyncTask() {
    //创建出MyAsyncTask实例
    MyAsyncTask myAsyncTask = new MyAsyncTask();
    //触发执行异步任务,该方法必须调用于UI线程
    myAsyncTask.execute();
}

上面的代码列出了AsyncTask的几个重要方法,注释已经标的比较清楚,整体上的执行顺序如下图。

核心方法调用时机执行线程
onPreExecute后台任务执行前,自动调用UI线程
doInBackground后台任务执行时,自动调用工作线程
onProgressUpdate当手动调用publishProgress方法时UI线程
onPostExecute后台任务执行结束时,自动调用UI线程
onCanceled后台任务被取消时,自动调用UI线程

image.png

除了上面四个方法,AsyncTask还提供了onCancelled()方法,它同样在主线程中执行,当异步任务取消时,onCancelled()会被调用,这个时候onPostExecute()则不会被调用,但是要注意的是,AsyncTask中的cancel()方法并不是真正去取消任务,只是设置这个任务为取消状态,我们需要在doInBackground()判断终止任务。就好比想要终止一个线程,调用interrupt()方法,只是进行标记为中断,需要在线程内部进行标记判断然后中断线程。

使用AsyncTask的注意事项

  1. 异步任务的实例必须在UI线程中创建,即AsyncTask对象必须在UI线程中创建。
  2. execute(Params... params)方法必须在UI线程中调用。
  3. 不要手动调用onPreExecute()doInBackground(Params... params)onProgressUpdate(Progress... values)onPostExecute(Result result)这几个方法。
  4. 不能在doInBackground(Params... params)中更改UI组件的信息。
  5. 一个任务实例只能执行一次,如果执行第二次将会抛出异常。

AsyncTask的源码解析

熟悉了AsyncTask的使用,并不能回答得上面试官的这个问题。 既然问的是原理,必然需要进入到源码层,才能一探究竟。

AsyncTask的构造方法

public AsyncTask(@Nullable Looper callbackLooper) {
          //创建主线程handler
        mHandler = callbackLooper == null || callbackLooper == Looper.getMainLooper()
            ? getMainHandler()
            : new Handler(callbackLooper);
          //初始化mWorker变量
        //WorkerRunnable其实就是Callable<Result>接口,所以这边实现了call方法
        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变量
        //mFuture是一个FutureTask对象,通过这个对象便可以取消后台任务以及获取后台任务的执行结果
        //在构造方法中将上面的mWorker对象作为参数传入,当mFuture被执行时,便会调用mWorker的call方法
        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);
                }
            }
        };
    }

构造方法中做了两件事,第一件事是创建了一个主线程的handler,这个主线程的handler很重要,它是AsyncTask线程切换的重要角色。第二件事是初始化了mWorker和mFuture这两个成员变量。并在初始化mFuture的时候将mWorker作为参数传入。mWorker是一个Callable对象,mFuture是一个FutureTask对象,这两个变量会暂时保存在内存中,稍后才会用到它们。 FutureTask实现了Runnable接口。

mWorker中的call()方法执行了耗时操作,即result = doInBackground(mParams);,然后把执行得到的结果通过postResult(result);,传递给内部的Handler跳转到主线程中。在这里这是实例化了两个变量,并没有开启执行任务。

AsyncTask的execute方法

  public final AsyncTask<Params, Progress, Result> execute(Params... params) {
       //execute最终会调用executeOnExecutor方法
       return executeOnExecutor(sDefaultExecutor, params);
  }

execute方法调用了executeOnExecutor方法并传递参数sDefaultExecutor和params。

再看executeOnExecutor方法:

public final AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec,
              Params... params) {
          //状态检查。
          //不能对正在执行任务的AsyncTask对象或已经执行完任务的AsyncTask对象调用execute方法
          //这其实是AysncTask的一个缺点:一个AsyncTask对象只能调用一次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)");
              }
          }
          //将状态置为RUNNING
          mStatus = Status.RUNNING;
          //注意!!!这里调用了onPreExecute方法,验证了我们前面所说的这个方法会执行在后台任务前面
          onPreExecute();
          //参数值赋值给mWorker.mParams
          mWorker.mParams = params;
          //关键点!!!
          //这里的exec方法传过来的sDefaultExecutor,意思就是调用sDefaultExecutor的execute方法
          //参数便是刚刚构造方法中提到的成员变量mFuture
          exec.execute(mFuture);
          return this;
  }

executeOnExecutor方法首先判断状态,若处于可执行态,则将状态置为RUNNING。然后调用了onPreExecute方法,交给用户进行执行任务前的准备工作。核心部分在于 exec.execute(mFuture)。exec即sDefaultExecutor。

sDefaultExecutor串行线程池

查看sDefaultExecutor定义:

//sDefaultExecutor是一个线程池,默认赋值SERIAL_EXECUTOR,是static的,全局唯一
private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;
//SERIAL_EXECUTOR是AsyncTask中线程池的默认实现
public static final Executor SERIAL_EXECUTOR = new SerialExecutor();

再看一下SerialExecutor类

//线程池的具体实现
private static class SerialExecutor implements Executor {
      //mTasks是基于数组实现的双向链表,作为SerialExecutor的任务缓存队列
        final ArrayDeque<Runnable> mTasks = new ArrayDeque<Runnable>();
        //代表当前正在执行的任务,一开始肯定为空
        Runnable mActive;
        //通过synchronized关键字,可以保证线程安全,即多个AsyncTask会按序入队列
          //这里的r就是上面传进来的mFuture
        public synchronized void execute(final Runnable r) {
              //通过offer方法,往缓存队列中添加一个任务
            mTasks.offer(new Runnable() {
                public void run() {
                    try {
                        r.run();
                    } finally {
                        scheduleNext();
                    }
                }
            });
            //如果mActive为空,执行scheduleNext方法
            if (mActive == null) {
                scheduleNext();
            }
        }
  
        protected synchronized void scheduleNext() {
            //如果缓存队列mTasks不为空,则调用THREAD_POOL_EXECUTOR.execute
            if ((mActive = mTasks.poll()) != null) {
                  //看名字就知道,又是一个线程池
                THREAD_POOL_EXECUTOR.execute(mActive);
            }
        }
    }

sDefaultExecutor是一个串行线程池,作用在于任务的排队执行。从SerialExecutor的源码可以看出,mFuture是插入到mTasks任务队列的对象。当mTasks中没有正在活动的AsyncTask任务,则调用scheduleNext方法执行下一个任务。若一个AsyncTask任务执行完毕,则继续执行下一个AsyncTask任务,直至所有任务执行完毕。通过分析可以发现真正去执行后台任务的是线程池THREAD_POOL_EXECUTOR

在这个方法中,有两个主要步骤:

  1. 向队列中加入一个新的任务,即之前实例化后的mFuture对象。
  2. 调用 scheduleNext()方法,调用THREAD_POOL_EXECUTOR执行队列头部的任务。

线程池THREAD_POOL_EXECUTOR

//真正执行任务的线程池
public static final Executor THREAD_POOL_EXECUTOR;
//核心线程数,最多4个,最少2个(注:各个版本的SDK有变化)
private static final int CORE_POOL_SIZE = Math.max(2, Math.min(CPU_COUNT - 1, 4));
//最大线程数为2n+1,n为CPU的数量
private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;
//非核心线程可存活时间为30s
private static final int KEEP_ALIVE_SECONDS = 30;
//阻塞队列为LinkedBlockingQueue,最大容量为128
private static final BlockingQueue<Runnable> sPoolWorkQueue =
            new LinkedBlockingQueue<Runnable>(128);
//通过静态代码块进行线程池初始化,并赋值给THREAD_POOL_EXECUTOR
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;
}

分析到这里,思路基本明朗了,我们来稍微捋一下。AsyncTask调用execute方法,先是经过了SerialExecutor这个线程池,并将自己的成员变量mFuture传入,但是SerialExecutor仅仅只是将mFuture包装成Runnable任务添加至缓存队列,这里有个注意点,入队时使用了synchronized关键字,保证了多个AsyncTask会依次入队。

接着SerialExecutor又从缓存队列里拿出队头任务,交给了另一个线程池THREADPOOLEXECUTOR去真正执行。至此,线程已经切换至工作线程。当真正执行缓存队列里的任务时,会调用mFuture的run方法,mFuture又会调到其构造方法中传入的mWorker的call方法

mWorker = new WorkerRunnable<Params, Result>() {
      public Result call() throws Exception {
           //mTaskInvoked置为true,表示当前任务已被调用过
           mTaskInvoked.set(true);
           Result result = null;
                try {
                    //设置线程的优先级为后台
                    Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
                    //重点!!!
                    //执行doInBackground方法,执行我们自己定义的后台任务,并将结果存入result中
                    result = doInBackground(mParams);
                    Binder.flushPendingCommands();
                } catch (Throwable tr) {
                    mCancelled.set(true);
                    throw tr;
                } finally {
                    //最终执行postResult方法
                    postResult(result);
                }
                return result;
            }
        };

postResult

private void finish(Result result) {
        if (isCancelled()) {
            //如果任务被取消了,回调onCancelled方法
            onCancelled(result);
        } else {
            //否则,回调onPostExecute方法
            onPostExecute(result);
        }
        mStatus = Status.FINISHED;
    }

该方法向Handler对象发送了一个消息,下面具体看AsyncTask中实例化的Hanlder对象的源码

InternalHandler

/**
 * mHandler的默认实现
 */
private static class InternalHandler extends Handler {
        public InternalHandler(Looper looper) {
            super(looper);
        }

        @Override
        public void handleMessage(Message msg) {
            AsyncTaskResult<?> result = (AsyncTaskResult<?>) msg.obj;
            switch (msg.what) {
                case MESSAGE_POST_RESULT:
                    //当接收到MESSAGE_POST_RESULT,会调用AsyncTask的finish方法
                    result.mTask.finish(result.mData[0]);
                    break;
                case MESSAGE_POST_PROGRESS:
                    //当接收到MESSAGE_POST_PROGRESS,回调onProgressUpdate方法
                    result.mTask.onProgressUpdate(result.mData);
                    break;
            }
        }
    }

InternalHandler接收到MESSAGE_POST_RESULT时调用result.mTask.finish(result.mData[0]);即执行完了doInBackground()方法并传递结果,那么就调用finish()方法。

private void finish(Result result) {
        if (isCancelled()) {
            //如果任务被取消了,回调onCancelled方法
            onCancelled(result);
        } else {
            //否则,回调onPostExecute方法
            onPostExecute(result);
        }
        mStatus = Status.FINISHED;
    }

这里需要确信一点,所有操作都已经处于主线程。mHandler只处理两种消息,一个是结果消息,一个是进度消息。进度消息很简单,直接回调我们AsyncTask 的onProgressUpdate方法。

当接收到结果消息时,会调用finish方法,里面通过isCancelled判断AsyncTask任务是否被取消,若取消了则回调onCancelled方法,否则回调onPostExecute方法。最终,把mStatus设为FINISHED,表示当前这个AsyncTask已经执行完毕。

AsyncTask的原理总结

AsyncTask是基于两个线程池 + 一个主线程的Handler实现了线程间通信。

  • 第一个线程池SerialExecutor,它内部维护了一个双向队列,充当的是任务队列以及任务调度的角色,通过synchronized关键字保证多个AsyncTask可以按序排队,并每次从队头取出任务交出执行。
  • 第二个线程池THREADPOOLEXECUTOR,它是后台任务的真正执行者,只要有任务过来,它便可以按照线程池的操作流程去执行。主线程的Handler是内部类InternalHandler,它负责将工作线程任务执行的结果与进度通过消息传递到主线程,从而在主线程更新UI。

拓展

常见问题

  1. 为什么AsyncTask在主线程创建执行?

因为AsyncTask需要在主线程创建InternalHandler,以便onProgressUpdateonPostExecuteonCancelled 可以正常更新UI。

  1. 为什么AsyncTask不适合特别耗时任务?

AsyncTask实际上是一个线程池。如果有线程长时间占用,且没有空闲,则其他线程只能处于等待状态,会造成阻塞。

  1. AsyncTask内存泄漏问题

如果AsyncTask被声明为Activity的非静态的内部类,那么AsyncTask会保留一个对创建了AsyncTask的Activity的引用。如果Activity已经被销毁,AsyncTask的后台线程还在执行,它将继续在内存里保留这个引用,导致Activity无法被回收,引起内存泄露。

AsyncTask使用不当的后果

  • 生命周期: AsyncTask不与任何组件绑定生命周期,所以在Activity/或者Fragment中创建执行AsyncTask时,最好在Activity/Fragment的onDestory()调用 cancel(boolean);
  • 内存泄漏
  • 结果丢失: 屏幕旋转或Activity在后台被系统杀掉等情况会导致Activity的重新创建,之前运行的AsyncTask(非静态的内部类)会持有一个之前Activity的引用,这个引用已经无效,这时调用onPostExecute()再去更新界面将不再生效。

看完三件事❤️

如果你觉得这篇内容对你还蛮有帮助,我想邀请你帮我三个小忙:

  1. 点赞,转发,有你们的 『点赞和评论』,才是我创造的动力。
  2. 关注公众号 『 小新聊Android 』,不定期分享原创知识
  3. 同时可以期待后续文章ing🚀