AsyncTask 源码分析 - 使用

343 阅读8分钟

这是我参与更文挑战的第11天,活动详情查看: 更文挑战

AsyncTask 的使用方式

使用示例

private class DownloadFileTask extends AsyncTask<String, Integer, Long> {
    @Override
    public void onPreExecute() {
        mProgress.setVisibility(View.VISIBLE);
        mProgress.setMax(100);
        mProgress.setProgress(0);
    }

    @Override
    public Long doInBackground(String... uris) {
        int count = uris.length;
        long size = 0;
        for (int i = 1; i <= count; i ++) {
            try {
                // 休眠5秒模拟下载过程
                Thread.sleep(5 * 1000);
                // 假设每个下载文件的大小为(序号*100)
                size += i * 100;
                // 发布进度更新
                publishProgress( (100* i )/count);
            } catch (InterruptedException ie) {
                ie.printStackTrace();
            }
        }
        return size;
    }
    
    @Override
    public void onProgressUpdate(Integer... progress) {
        mProgress.setProgress(progress[0]);
    }
    
    @Override
    public void onPostExecute(Long result) {
        mText.setText(String.valueOf(result));
    }
}

通过这段代码可以看到要使用AsyncTask实行异步任务是非常容易的,只需要做两件事:

  • 确定在整个处理过程中需要的参数类型,包括Params,ProgressResult,分别对应着输入参数、进度参数和结果参数。
  • 实现必要的回调方法,其中必须是实现的是doInBackground,耗时任务正是在这里进行处理的,可以想象doInBackground一定是在子线程里进行的;其他可选实现方法包括onPreExecute,onProgressUpdateonPostExecute,这些在示例中都参与了UI的更新,所以一定是在主线程中进行的。

参数介绍

public abstract class AsyncTask<Params, Progress, Result> { ... }

可以发现AsyncTask中使用的都是泛型参数,在使用过程中要根据需求选择合适的参数类型,在示例中使用的参数类型分别是String,IntegerLong,如果某一个参数是不需要的,可以用Void来表示,下面通过一个表格来对每个参数进行说明:

参数声明含义作用产生处/调用处注意事项
Params输入参数任务开始执行时客户端发送开始参数execute()中发送,在doInBackground()中调用。可变参类型
Progress过程参数任务后台执行过程中服务端发布的当前执行进度在doInBackground()中产生并通过publishProgess()发送,在onProgressUpdate()调用。可变参类型
Result结果参数任务执行完成后服务端发送的执行结果在doInBackground()中产生并在onPostExecute()中调用。

参数类型不能是基本数据类型,要使用对应的封装类型,例如示例的ProgressResult参数使用的IntegerLong而不是intlong

回调接口

回调方法运行线程作用执行顺序是否需要重新实现
onPreExecute主线程在开始执行后台任务前进行初始化首先开始执行可选
doInBackground后台线程执行后台耗时任务完成后返回结果onPreExecute 执行完成后执行必须实现
publishProgress后台线程在执行任务过程中发布执行进度在 doInBackground 中执行无须实现,直接调用。
onProgressUpdate主线程接收进度并在主线程处理在 publishProgress 之后执行可选
onPostExecute主线程接收执行结果并在主线程处理在 doInBackground 执行完成后执行可选

AsyncTask 源码分析

回到我们一开始提到的那个示例代码,当我们定义了好自己的AsyncTask之后,要开始运行这个任务时非常简单,只需要一行代码:

new DownloadFileTask().execute(url1, url2, url3);

我们就以这个为切入点来分析,首先看下execute()做了什么

首先,new DownloadFileTask() ,执行DownloadFileTask的构造方法,因此必然会执行DownloadFileTask的父类AsyncTask的构造方法,也就是 AsyncTask() :

/**
 * Creates a new asynchronous task. This constructor must be invoked on the UI thread.
 */
public AsyncTask() {
    this((Looper) null);
}
public AsyncTask(@Nullable Looper callbackLooper) {
    mHandler = callbackLooper == null || callbackLooper == Looper.getMainLooper()
        ? getMainHandler()
        : new Handler(callbackLooper);

    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);
            }
        }
    };
}

构造方法的工作很简单,就是完成了mWorker 和 mFuture 的初始化工作,也就是Callable和Future 的初始化,并关联他们,让mFuture 可以获取mWorker 的执行结果,或者停止mWorker 的执行。

这里主要由两个方法call()和done(),总的来说当mFuture 开始被执行的时候,call() 就会执行,当这个任务执行完毕后done()方法就会执行。

那么这个mFuture 什么 时候会被执行呢?继续往下看execute(Params... params)

@MainThread
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) {
    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;
    // 回调方法中首先被调用的方法,由于"execute()"是在主线程中执行的,
    // 目前为止也没有进行线程的切换,所以"onPreExecute"也是在主线程中执行的。
    onPreExecute();

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

    return this;
}

到这里就很清楚了,mStatus 默认状态为PENDING,因此任务开始执行后首先将其状态改为RUNNING;同时从异常判断我们也可以看出一个AsyncTask的execute方法不能同时执行两次。

接下来,onPreExecute(),我们是在onCreate 中开启了AsyncTask的任务,因此这个时候,依旧属于主线程,onPreExecute()方法也会工作在主线程,我们可以在这个方法中执行一些预备操作,初始相关内容。

mWorker,前面已经说过他就是实现了Callable接口,并添加了一个参数属性,在这里我们把executor中传入的参数赋给了这个属性。exec=sDefaultExecutor=SerialExecutor ,这里任务就开始真正的执行了;按照之前所说就会开始执行mFuture这个任务,因此就会开始执行mWorker的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;
    }
};

到这里,我们终于看到了熟悉的 doInBackground,这是我们必须实现的一个方法,在其中完成耗时操作,并返回结果。由于已经设置了Process的优先级,因此这个方法会处于后台进程。 在 doInBackground 里,我们还可以返回当前执行进度

@Override
    public Long doInBackground(String... uris) {
        int count = uris.length;
        long size = 0;
        for (int i = 1; i <= count; i ++) {
            try {
                // 休眠5秒模拟下载过程
                Thread.sleep(5 * 1000);
                // 假设每个下载文件的大小为(序号*100)
                size += i * 100;
                // 发布进度更新
                publishProgress( (100* i )/count);
            } catch (InterruptedException ie) {
                ie.printStackTrace();
            }
        }
        return size;
    }

我们调用了 publishProgress 可以将 doInBackground耗时任务的进度发送出去,大家都知道这个进度会发送到onProgressUpdate() 方法中,在onProgressUpdate我们可以方便的进行UI 更新,比如进度条进度更新等。那么他是怎么实现的呢?这就要看publishProgress这个方法的实现了。

@WorkerThread
protected final void publishProgress(Progress... values) {
    if (!isCancelled()) {
        getHandler().obtainMessage(MESSAGE_POST_PROGRESS,
                new AsyncTaskResult<Progress>(this, values)).sendToTarget();
    }
}

AsyncTaskResult 顾名思义,很好理解了,就是AsyncTask的执行结果,这是一个静态的内部类,包括两个属性mTask和mData 。

@SuppressWarnings({"RawUseOfParameterizedType"})
    private static class AsyncTaskResult<Data> {
        final AsyncTask mTask;
        final Data[] mData;

        AsyncTaskResult(AsyncTask task, Data... data) {
            mTask = task;
            mData = data;
        }
    }
}

因此publishProgress中 new AsyncTaskResult 就是创建了一个AsyncTaskResult,他的两个两个属性为当前的AsyncTask和任务任务执行进度。

到这里的逻辑很清楚了,如果当前任务没有被取消, 那么就从消息池中获取一个Message的实例,同时设置这个Message对象的msg.what=MESSAGE_POST_PROGRESS, msg.obj为一个AsyncTaskResult对象,最后执行sendToTarget方法,通过之前对Handler实现机制的了解,我们知道sendXXX方法殊途同归,所完成的任务都是将Message对象插入到MessageQueue当中,等着Looper的loop方法一个个取出。由于我们是在主线程开启了AsyncTask任务的执行,因此,一旦我们将一个消息插入到队列,那么就会执行Handler的handleMessage方法。下面就来看看你这个InternalHandler 的实现。

private static class InternalHandler extends Handler {
    public InternalHandler(Looper looper) {
        super(looper);
    }

    @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中首先取出结果,并强制转换为AsyncTaskResult对象,在msg.what=MESSAGE_POST_PROGRESS时,就会执行result.mTask.onProgressUpdate(result.mData); mTask 就是当前AsyncTask,因此就会执行AsyncTask中声明的onProgressUpdate方法。这样,就把参数从一个子线程传递到了UI 线程,非常方便开发人员用这个完成相关业务。

我们再回到mWorker 的call() 方法中,当doInBackground执行完毕后,最后就会执行postResult。

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

这个方法和publishProgress逻辑一样,懂事把result 封装到一个AsyncTaskResult 对象中,做为一个Message对象的obj属性插入到MessageQueue中,只不过msg.what=MESSAGE_POST_RESULT.

这样就会来到InternalHandlerhandleMessage中,这一次msg.what=MESSAGE_POST_RESULT.时执行result.mTask.finish(result.mData[0]);

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

这个方法也很简单,任务未取消时,onPostExecute(result) 方法被执行。这个onPostExecute(result)就是我们最后要执行的方法,在这个方法中得到最终的执行结果;并将任务状态标记为FINISHED

其他

串行or并行?

在SimpleAsyncTask中,我们使用private static final Executor EXECUTOR = Executors.newCachedThreadPool()作为线程池,而实际上,源码中的默认线程池是自定义的,这个类是SerialExecutor,从类的命名上看,Serial是串行的意思,所以很明显,AsyncTask默认是串行的。除此之外,AsyncTask里还有个线程池 THREAD_POOL_EXECUTOR,实在需要并行的话我们就用这个线程池。

如果都些都不满足要求,我们也可以自定义符合自己业务要求的线程池,并通过setDefaultExecutor(Executor exec)改变默认的线程池。

executeOnExecutor中我们还可以传入自己自定义的线程池:

//跟默认一样的按顺序执行
asyncTask.executeOnExecutor(Executors.newSingleThreadExecutor());
//无限制的Executor
asyncTask.executeOnExecutor(Executors.newCachedThreadPool());
//同时执行数目为10的Executor
asyncTask.executeOnExecutor(Executors.newFixedThreadPool(10));

postResultIfNotInvoked的作用是什么?

AsyncTask有很多逻辑干扰了我们解读源码,postResultIfNotInvoked便是其中一个。它实际上是Google解决的一个bug,确保如果cancel()方法过早调用的场景下,onCancelled()仍然能顺利的执行,参考stackoverflow这篇文章

用这个玩意 退出页面必须取消运行 否则你下个页面用的时候就知道问题了 串行坑了我一次

参考

理解 AsyncTask 原理

关于AsyncTask的一次深度解析

AsyncTask:一只命途多舛的小麻雀