AsyncTask全解析

1、AsyncTask介绍

回顾上一节的问题:更新UI的方式有哪些?答案有:

  • Activity.runOnUiThread(Runnable)
  • View.post(Runnable),View.postDelay(Runnable, long)
  • Handler
  • AsyncTask
  • Rxjava
  • LiveData

本文的主角AsyncTask,本人现在用得比较少,更多用的是Rxjava。同时面试的时候知道AsyncTask是什么?怎么用?有什么不足之处或者需要注意什么?懂了这三个问题一般就可以了。至于看源码,可当作进阶,个人建议还是有必要学习一下的。那么AssyncTask究竟是什么呢?

AsyncTask是一个轻量级的异步任务类,可以在线程池里执行比较耗时的后台任务,然后把执行的进度和最后的结果传递给主进程,并在主进程中更新UI。使用容易,而且好理解。

1.1 AsyncTask的使用

同样从使用入手,看看代码。以下代码以往文件里写数据为例:

private class FileTestTask extends AsyncTask<Void, Long, Boolean> { //1

    @Override
    protected void onPreExecute() {
        //初始化一些数据
        initProcessInfo();
    }

    @Override
    protected Long doInBackground(Void... integers) {
        //创建目录
        ...
        while (mTotalWriteSize <= mInitFreeDataValue) {
            ...
            mTotalWriteSize += writeSize / 1024 / 1024; // M
            //2:反馈当前任务进度
            publishProgress(mTotalWriteSize);
        }

        return true;
    }

    @Override
    protected void onProgressUpdate(Long... progresses) { //3
        if (progresses != null && progresses.length >= 1) {
            updateProcessInfo(progresses[0]); //更新进度条
        }
    }

    @Override
    protected void onPostExecute(Boolean result) { //4
        if (result) {
            updateContentInfo(R.string.text_show_result_test_over);
        } else {
            updateContentInfo(R.string.text_show_result_fail);
        }
    }
}

//主线程中使用自定义的FileTestTask
if (mFileTestTask == null) {
    mFileTestTask = new FileTestTask();
    mFileTestTask.execute();
}

1.2 核心方法

  • onPreExecute():字面上就是“提前执行”,会在后台任务开始前调用,并在主线程中执行,因此常用于初始化一些数据和界面。

  • doInBackground(Params…):字面上是“在背后处理”,就是在后台处理所有耗时的任务,这些任务都会在子线程中处理。有两个需要注意的点:

    • 不能在这个函数进行UI操作,因为该函数是在子线程运行的。如果需要更新当前任务完成进度条,需要运行publishProgress(Progress…),如上面的注释2,而最终是调用onProgressUpdate()去更新进度条;
    • 执行完之后可以通过return将执行的结果返回,返回的结果会在onPostExecute()里执行,如注释4,我这边只是更新了一个文字内容。
  • onProgressUpdate(Progress…):如上一段所述,调用publishProgress(Progress…)后会把参数(如当前任务处理的进度)传递给onProgressUpdate()并被调用,因为该方法在主进程进行,因此可以进行UI修改。

  • onPostExecute():如上所述,doInBackground()会把执行结果返回给 onPostExecute()并被调用,该方法也在主线程运行,可以做一些结尾的动作,如提示任务完成等。

上面几个核心方法的调用顺序为: onPreExecute() –> doInBackground() –> publishProgress() –> onProgressUpdate() –> onPostExecute()。

如果不需要更新进度则为onPreExecute() –> doInBackground() –> onPostExecute()。

1.3 三个泛型类参数

在1.1小节案例代码里的注释1 可以看到AsyncTask<Void, Long, Boolean>,其原型是 AsyncTask<Params, Progress, Result>,这三个泛型类参数含义如下:

  • Params:开始异步任务执行时传入的参数类型,对应doInBackground(),本案例是Void,代表不需要传入参数;
  • Progress:异步任务执行过程中,返回下载进度值的类型,对应onProgressUpdate(),本案例是Long,代表目前已经写入的字节数;
  • Result:异步任务执行完成后,返回的结果类型,对应onPostExecute(),本案例返回的是Boolean类型,true代表任务完成;

2、AsyncTask使用心得

2.1 AsyncTask的使用注意事项

从第1小节,我们了解了AsyncTask的使用方法和步骤,现在再强调几个注意点,不仅面试官可能会问到,平时自己工作中也要多注意:

  • AsyncTask的实例需要在UI线程也就是主线程中创建,调用execute()开始执行任务时,也要在主线程执行;
  • 不要手动调用onPreExecute(),doInBackground(Params… params),onProgressUpdate(Progress… values),onPostExecute(Result result)这几个方法;
  • 只有onPreExecute(),onProgressUpdate(Progress… values),onPostExecute(Result result)可以执行UI相关操作,doInBackground(Params… params)不能更改UI界面;
  • 一个任务实例只能执行一次execute(),执行多次会报异常。

2.2 AsyncTask的不足之处

  1. 生命周期

AsyncTask不与任何组件绑定生命周期,因此会出现组件生命周期结束了,但AsyncTask还存在,所以需要在onDestroy()调用 cancel(boolean),手动结束AsyncTask;

  1. 内存泄漏

和之前说的Handler造成内存泄露的原因是一样的。如果AsyncTask被声明为Activity的非静态的内部类,如本文使用代码案例一样,那么AsyncTask会保留一个所在Activity的引用。如果Activiy被销毁,此时因此AsyncTask保留了它的引用,导致Activity无法被回收,引起内存泄露。

3.) 结果丢失

如果屏幕旋转或Activity在后台被系统杀掉等情况会导致Activity重新创建,如果之前运行的AsyncTask是非静态的内部类,此时就会持有之前旧的Activity的引用,而旧的Activity已经销毁了,这时调用onPostExecute()再去更新界面将不再生效,无法更新到新的Activity到界面。

3、 源码分析(进阶)

如果看完前面2节,去面试已经没问题了,对AsyncTask的源码分析属于进阶,如果有时间,建议看看源码,因为整个第3节的源码代码关联性很强,为了方便说明,所有代码块的关键代码行的注释码都是连贯的。

回顾下AsyncTak的使用方法:我们只要执行new FileTestTask().execute(),就会自动执行onPreExecute(),doInBackground(Params… params),onProgressUpdate(Progress… values),onPostExecute(Result result)这几个方法。这是怎么做到的呢?

带着这个思路,按顺序,我们先看看在初始化一个AsyncTask时,调用函数都做了什么。

public AsyncTask() {
        mWorker = new WorkerRunnable<Params, Result>() {//1
            public Result call() throws Exception {
                mTaskInvoked.set(true);
                Result result = null;
                try {
                    Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
                    //noinspection unchecked
                    result = doInBackground(mParams); //2
                    Binder.flushPendingCommands();
                } catch (Throwable tr) {
                    mCancelled.set(true);
                    throw tr;
                } finally {
                    postResult(result); //3
                }
                return result;
            }
        };

        mFuture = new FutureTask<Result>(mWorker) { //4
            @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);
                }
            }
        };
    }

我们可以看到这仅仅是初始化了两个变量,mWorker和mFuture,并在初始化mFuture的时候将mWorker作为参数传入,如注释1和注释4。此时仅仅完成初始化,什么都没做。不过记住这个注释1的call()函数,里面有注释2和注释3两个关键函数,后面是如何调用到的呢?接着执行execute()就开始执行任务了,来看看execute()的源码:


public final AsyncTask<Params, Progress, Result> execute(Params... params) {
    return executeOnExecutor(sDefaultExecutor, params); //5
}
  
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(); //6

    mWorker.mParams = params;
    exec.execute(mFuture); //7

    return this;
}

注释5,execute()调用executeOnExecutor(sDefaultExecutor, params),终于在注释6,看到了熟悉的函数onPreExecute()!执行完onPreExecute(),即完成了一些初始化工作后,调用注释7:exec.execute(mFuture)开始执行耗时任务,并把上面注释4初始化的mFuture实例传入。

进一步看看注释7这个exec是什么?看下代码调用,就知道exec是注释5的sDefaultExecutor,因此注释7的exec.execute(mFuture)实际上是执行sDefaultExecutor.execute(mFuture)。再追述源码可以看到sDefaultExecutor是SerialExecutor类,具体源码如下:

public static final Executor SERIAL_EXECUTOR = new SerialExecutor();
private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;

private static class SerialExecutor implements Executor {
    final ArrayDeque<Runnable> mTasks = new ArrayDeque<Runnable>(); //8
    Runnable mActive; //9

    public synchronized void execute(final Runnable r) { //10
        mTasks.offer(new Runnable() { //11
            public void run() {
                try {
                    r.run(); //12
                } finally {
                    scheduleNext(); //13
                }
            }
        });
        if (mActive == null) {
            scheduleNext();
        }
    }

    protected synchronized void scheduleNext() {
        if ((mActive = mTasks.poll()) != null) { //14
            THREAD_POOL_EXECUTOR.execute(mActive); //15
        }
    }
}

可以看出这个SerialExecutor是静态内部类,因此每个AsyncTask都共有。SerialExecutor维护了一个队列,也就是注释8的mTasks。在注释10通过加锁保证AsyncTask的任务是串行的。好好看注释10这个execute()函数,主要做了两件事:

  1. 在注释11向队列mTask加入一个新的任务,也就是注释7传入的mFuture对象;
  2. 调用注释 13的scheduleNext()方法,进而调用注释14的THREAD_POOL_EXECUTOR.execute(mActive)来执行队列头部任务。

所以,SerialExecutor只是保证任务是串行的,真正执行任务还是交给THREAD_POOL_EXECUTOR。继续追代码,看看THREAD_POOL_EXECUTOR是什么?

 ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
                CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE_SECONDS, TimeUnit.SECONDS,
                sPoolWorkQueue, sThreadFactory);
        threadPoolExecutor.allowCoreThreadTimeOut(true);
        THREAD_POOL_EXECUTOR = threadPoolExecutor; //16

注释16可以看到THREAD_POOL_EXECUTOR就是一个线程池,注释15调用了线程池的execute()方法,执行具体的耗时任务,而注释15的mActive是从注释14的mTasks队列获取的Runnable,这个Runnable又是注释11加入队列的,注释11这个Runnable就是注释7传入的mFuture,接着这个mFuture又是注释4传入的mWorker构造的。通过倒推,就知道注释15执行的具体的耗时任务其实就是注释1中的call()函数。终于,调用到了注释1,先执行完doInBackground()方法处理后台耗时任务,又执行postResult()方法返回处理结果,一切都和我们想的一样!

最后看看怎么结束任务吧。

 private Result postResult(Result result) {
        @SuppressWarnings("unchecked")
        Message message = getHandler().obtainMessage(MESSAGE_POST_RESULT,
                new AsyncTaskResult<Result>(this, result)); //17 
        message.sendToTarget();
        return result;
    }

没错,就是Handler机制。AsyncTask内部又一个Handler对象,这也是为什么2.1小节说AsyncTask需要在主线程创建,因为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]);  //18
                    break;
                case MESSAGE_POST_PROGRESS:
                    result.mTask.onProgressUpdate(result.mData); //19
                    break;
            }
        }
    }

代码非常清晰,如果收到MESSAGE_POST_PROGRESS消息,在1.1小节注释2的地方发送,就执行注释19,又是我们熟悉的onProgressUpdate(),如果收到MESSAGE_POST_RESULT消息,就执行注释18的finish()。

private void finish(Result result) {
        if (isCancelled()) {
            onCancelled(result); //20
        } else {
            onPostExecute(result); //21
        }
        mStatus = Status.FINISHED;
    }

如果按2.2小节说的调用cancel(boolean),则会回调注释20,否则执行注释21:onPostExecute(result)。

到此结束源码分析。相信文章开头4个问题大家都理解了。

4、总结

AsyncTask中有两个线程池(SerialExecutor和THREAD_POOL_EXECUTOR)和一个Handler(InternalHandler),其中线程池SerialExecutor用于任务的排队,而线程池THREAD_POOL_EXECUTOR用于真正地执行任务,InternalHandler用于将执行环境从线程池切换到主线程。