AsyncTask 用法及源码解析

来源:互联网 发布:百度一下淘宝网裤子 编辑:程序博客网 时间:2024/06/08 06:23

AsyncTask 是一种轻量级异步任务类,可以在线程池执行后台任务,获取到的结果传递给主线程并且在主线程中更新 UI。AsyncTask 比较适合执行短时间任务,对于长时间任务推荐使用 Executor,ThreadPoolExecutor 和 FutureTask。

AsyncTask 是一个抽象类,提供三个泛型参数,分别是 Params,Progress 和 Result;以及 4 个步骤:onPreExecute,doInBackground,onProgressUpdate 和 onPostExecute。

AsyncTask 三个泛型参数

  • Params:发送给执行任务的参数类型。

  • Progress:执行后台任务进度的类型。

  • Result:执行完后台任务返回的结果类型。

AsyncTask 四个核心方法

  • onPreExecute():在任务执行之前调用,主线程执行;主要做一些初始化工作,比如在用户界面展示进度条。

  • doInBackground(Params…):onPreExecute() 执行完成后被调用,在线程池执行;所有的异步操作都在这个方法执行,执行结果被返回时,onPostExecute(Result) 会被调用。如果在该方法中调用 publishProgress(Progress…),那么方法 onProgressUpdate(Progress…) 也会被调用,主要用于更新后台任务进度。

  • onProgressUpdate(Progress…):publishProgress(Progress…) 执行完之后被调用,在主线程执行;主要在用户界面显示后台任务执行进度。

  • onPostExecute(Result):doInBackground(Params…) 执行完之后调用,在主线程执行;参数 Result 是 doInBackground(Params…) 的返回值。

一个异步任务可以通过调用 cancel(boolean) 随时取消,此时 isCancelled() 被调用,这就导致 doInBackground(Params…) 执行完后 onPostExecute(Result) 不会被调用。

在使用 AsyncTask 的过程中,要注意以下几点:

  • AsyncTask 类必须在 UI 线程加载,Android 4.1 已经自动绑定了。

  • AsyncTask 实例必须在 UI 线程创建。

  • execute(Params…) 必须在 UI 线程调用。

  • 不要手动调用 onPreExecute()、onPostExecute(Result)、doInBackground(Params…)、onProgressUpdate(Progress…)。

  • 一个 AsyncTask 对象只能被执行一次;否则会抛异常。

以上是 AsnycTask 基本知识点,掌握知识点后就要学会如何使用它。那么接下来就来学习 AsyncTask 用法。

AsyncTask 用法

AsyncTask 是抽象类,不能直接实例化,必须创建新类并继承它,抽象方法 doInBackground(Params…) 是一定要重写的,其它三个方法根据自己的需求确定。以下通过 URL 获取数据为例子来讲解 AsyncTask 的用法。代码如下:

public class AsyncTaskExample extends AsyncTask<String, Integer, String> {    @Override    protected void onPreExecute() {        super.onPreExecute();        mLoad.setVisibility(View.VISIBLE);    }    @Override    protected String doInBackground(String... params) {        return getUrlResponse(params[0]);    }    @Override    protected void onPostExecute(String s) {        super.onPostExecute(s);        mLoad.setVisibility(View.GONE);        mText.setText(s);     }}

从代码中可以很清晰地看出,第 5 行是显示加载进度条,表示正在获取数据;第 10 行是核心代码,异步操作,网络请求数据并将结果返回;第 16 - 17 行代码主要操作是隐藏进度条,表示数据加载完毕,并将获取到的结果显示出来。这里主要给出核心代码,至于其它代码也就调用而已。

那么该如何调用呢?很简单,一行代码就搞定

new AsyncTaskExample().execute(url);

AsyncTask 源码解析

知其然必知其所以然。对于新知识点,学会使用之后,就应该探究其原理。由于个人倾向于通过画图来理解知识点的流程,因此先简单地给出 AsyncTask 任务执行的流程图,再根据流程图和源码进行讲解。流程图如下:

这里写图片描述

对于源码的理解,一般是以最终调用的方法为入口,一步一步地理解整个流程。那么对于 AsyncTask 该从哪里入手呢?当然是从方法 execute(Params… params) 入手了,代码如下:

public final AsyncTask<Params, Progress, Result> execute(Params... params) {    return executeOnExecutor(sDefaultExecutor, params);}

对于 AsyncTask 不同版本,execute(Params… params) 方法的执行方式是不一样的。Android 1.6 以前,AsyncTask 是单线程串行执行任务的;Android 1.6,AsyncTask 是线程池多线程并行执行任务;但是到 Android 3.0,AsyncTask 又改为单线程串行执行任务的。该方法的逻辑很简单,直接调用方法 executeOnExecutor(Executor exec, Params… params),将我们传入的参数 params 和 sDefaultExecutor 传到该方法里,并将的返回值返回。那么来看下该方法的具体实现,代码如下:

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

executeOnExecutor(Executor exec, Params… params) 方法是在线程池 THREAD_POOL_EXECUTOR 执行,允许多任务并发执行,但是不推荐采用多任务并发执行;在主线程执行。该方法实现的主要功能是:

  • 检查任务状态,并记录任务当前状态;

  • 调用 onPreExecute() 方法,根据我们自己的需求可以重写该方法;

  • 将我们传入的参数 params 赋值给 WorkRunnable 中字段 mParams(稍后解释);

  • 调用 SerialExecutor 中方法 execute(Runnable r) 执行任务。

mWorker 是 WorkerRunnable

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

该回调方法实现的主要功能:

  • 将 mTaskInvoked 设置为 true,表示任务已经被调用过;

  • 设置线程优先级为后台线程;

  • 调用 doInBackground(mParams) 方法,异步执行,后台执行的逻辑都写在这个方法里面,一定要被重写;如果任务执行抛出异常时,取消任务;

  • 调用 postResult(result) 方法;

postResult(result) 的具体实现如下:

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

从代码里可以看出,将执行结果通过 sHandler 发送 MESSAGE_POST_RESULT 的消息,然后 handleMessage() 方法收到消息后进行相应的处理。sHandler 是 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:                result.mTask.finish(result.mData[0]);                break;            case MESSAGE_POST_PROGRESS:                result.mTask.onProgressUpdate(result.mData);                break;         }     }}

从构造函数 InternalHandler() 就可以看出了,获取主线程 Looper,而 Handler 必须与 Looper 进行绑定,因此可以断定是在主线程里。handleMessage() 函数对两种消息进行处理:MESSAGE_POST_RESULT 和 MESSAGE_POST_PROGRESS;而我们刚刚发送的消息是 MESSAGE_POST_RESULT,那就先来看该消息收到后会做什么处理吧?很显然,调用 finish(Result result),具体实现如下:

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

如果任务被取消了,直接调用 onCancelled(result) 方法,onPostExecute(result) 方法不会被调用;否则就调用 onPostExecute(result) 方法,该方法需要被重写,在主线程执行,根据返回的结果进行相应的处理;最后修改任务的状态。那么对于消息 MESSAGE_POST_PROGRESS 是从哪里发出来的呢?还记得在前面的知识点讲解中有提到过如果在 doInBackground(mParams) 方法中调用 publishProgress(Progress…) 方法时,方法 onProgressUpdate(Progress…) 也会被调用,用于后台任务进度更新。没错,消息 MESSAGE_POST_PROGRESS 就是用来处理进度更新的。先看下 publishProgress(Progress…) 具体实现:

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

很显然,如果任务没有被取消的话,就会发送消息 MESSAGE_POST_PROGRESS,那么来看下收到该消息后的处理逻辑,即调用 onProgressUpdate(Progress… values) 方法,该方法需要我们根据自己的需求进行重写。

再回到 executeOnExecutor(Executor exec, Params… params) 方法,第 14 行代码开始执行任务,在理解如何执行任务之前,先来理解参数 mFuture 和 sDefaultExecutor 的含义。

mFuture 是 FutureTask 实例,在 AsyncTask 构造方法中初始化。将 mWorker 作为参数传入 FutureTask 构造函数,个人认为传入该参数的作用是由于 FutureTask 中 run() 方法会被调用,而在该方法里会通过传入参数 mWorker 调用 call() 方法,进而使任务得到执行。FutureTask 是一个并发执行任务类,可以执行任务、取消任务、查询结果、获取结果;提交到线程池执行。实现的接口有 Future、Runnable。

对于传入的参数 sDefaultExecutor,究竟是什么啥玩意呢?让我们来探个究竟吧。sDefaultExecutor 是 SerialExecutor 的实例,而 SerialExecutor 实际上是一个串行的线程池,主要的功能是一个进程中所有的 AsyncTask 任务都在这个串行的线程池中排队执行。看到这里,是不是还不知道任务真正在哪里被开始执行?其实以上都只是铺垫,下面才真正拉开序幕。真正开始执行任务的逻辑是在 SerialExecutor 中 execute(Runnable r) 方法里,具体实现如下:

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

首先将 AsyncTask 通过线程池 SerialExecutor 添加到队列里(从这里可以看出 SerialExecutor 的作用),然后重写 run() 方法,并判断 mActive 是否为 null,即当前是否有任务在执行,如果有任务执行的话就等待该任务执行完后再执行其他任务,否则就执行任务,即调用 scheduleNext() 方法,该方法的主要功能是从队列 mTasks 获取任务,任务不为空的话就直接提交到线程池 THREAD_POOL_EXECUTOR 里执行(任务真正开始执行),即启动任务,根据个人的理解,任务被启动后,会调用第 6 行代码,即 run() 方法,进而调用 FutureTask 中 run() 方法,从而会调用 WorkerRunnable 中 call() 方法,因此任务被执行,我们重写的方法也会被调用。结合以上流程图应该能更清晰地理解 AsyncTask 执行流程。

以上是自己在学习 《Android 开发艺术探索》 这本书第十一章关于 AsyncTask 这个主题的学习笔记,由于自己能力有限,有错误的地方欢迎指出。

参考资料

https://developer.android.com/reference/android/os/AsyncTask.html

《Android 开发艺术探索》》中 第 11 章 Android 的线程和线程池

0 0
原创粉丝点击