Android异步任务–AsyncTask

AsyncTask介绍

如果通过Thread执行耗时操作,那么在操作完成之后,我们可能需要更新UI,这时通常的做法是通过Handler投递一个消息给UI线程,然后更新UI,我们在Android消息机制–Handler解析也讲到过。这种方式对于整个过程的控制比较精细,但是也是有缺点的,例如,代码相对臃肿,在多个任务同时执行时,不易对线程进行精确的控制。

为了简化操作,Android1.5提供了工具类AysncTask,它使创建异步任务变得更加简单,不再需要编写任务线程和Handler实例即可完成相同的工作。它更重量级,更易于使用。

先看看AsyncTask的定义:

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

从定义可以看出AsyncTask是一个抽象类,通常用于被继承,继承时需要制定三个泛型参数:

  1. Params:启动任务执行的输入参数的类型
  2. Progress:后台任务执行时,如果需要在界面上显示当前的进度,则使用这里制定的泛型作为进度单位
  3. Result:当任务执行完毕后,如果需要对结果进行返回,则使用这里指定的泛型作为返回值类型

3种泛型参数类型,并不是所有类型我们都被需要,如果不需要某个参数,就可以设置为Void。

一个异步任务的执行一般包括以下几个步骤。

  1. execute(Params… params),执行一个异步任务,需要我们在代码中调用此方法,触发异步任务的执行。
  2. onPreExecute(),在execute(Params… params)被调用后立即执行,执行在UI线程,一般用来在执行后台任务前对UI做一些标记。
  3. doInBackground(Params… params),在onPreExecute()完成后立即执行,用于执行较为耗时的操作,此方法将收到输入参数和返回计算结果。在执行的过程中可以调用publishProgress(Progress… values)来更新进度消息。
  4. onProgressUpdate(Progress… values),执行在UI线程。调用publishProgress(Progresss… values)时,此方法被执行,直接将进度消息更新到UI组件上。
  5. onPostExecute(Result result),执行在UI线程。当后台操作结束时,此方法会调用,DoInBackground函数返回的计算结果将会作为参数传递到此方法中,直接将结果显示到UI组件上。

在使用的时候,有几点要格外注意:

  • 异步任务的实例必须在UI线程中创建
  • execute(Params… params)方法必须在UI线程中调用
  • 不能在DoInBackground(Params… params)中更改UI组件的信息
  • 一个任务实例只能执行一次,如果执行第二次将会抛出异常

AsyncTask的简单实践

我们对AsyncTask有了基本了解后,通过一个实例来上手体验一把。我们知道AsyncTask是一个抽象类,所以在使用时要自定义一个AsyncTask类并实现所需要的方法。我们实践的是通过过一个按钮进行下载网页,在下载完成后对网页的源码进行显示。实践的布局很简单,就不贴出,我们直奔主题,直接看AsyncTask的实现:

/**
 * Created by aotu on 2018/2/1.
 */

public class DownTask extends AsyncTask<URL, Integer, String> {
    ProgressDialog mProgressDialog;
    int hasRead = 0;
    Context mContext;
    TextView mTextView;

    public DownTask(Context context, TextView textView) {
        mContext = context;
        mTextView = textView;

    }

    @Override
    protected String doInBackground(URL... urls) {

        HttpURLConnection connection = null;
        BufferedReader bufferedReader = null;
        try {
            //发起网络请求
            connection = (HttpURLConnection) urls[0].openConnection();
            connection.setRequestMethod("GET");
            connection.setConnectTimeout(8000);
            connection.setReadTimeout(8000);
            InputStream inputStream = connection.getInputStream();
            //对获取到的输入流进行读取
            bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
            StringBuilder stringBuilder = new StringBuilder();
            String line;
            while ((line = bufferedReader.readLine()) != null) {
                stringBuilder.append(line + "\n");
                hasRead++;
                publishProgress(hasRead);

            }
            return stringBuilder.toString();

        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    @Override
    protected void onPostExecute(String s) {
        super.onPostExecute(s);
        mTextView.setText(s);
        mProgressDialog.dismiss();
    }

    @Override
    protected void onPreExecute() {
        super.onPreExecute();
        //初始化提示框
        mProgressDialog = new ProgressDialog(mContext);
        mProgressDialog.setTitle("download");
        mProgressDialog.setMessage("Downloading, please wait a moment");
        mProgressDialog.setCancelable(false);
        mProgressDialog.setMax(100);
        mProgressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
        mProgressDialog.setIndeterminate(false);
        mProgressDialog.show();

    }

    @Override
    protected void onProgressUpdate(Integer... values) {
        super.onProgressUpdate(values);
        //更新进度
        mProgressDialog.setProgress(values[0]);
    }
}

上面的代码很简单,就是讲下载得到的网页源码打印出来,我们运行一下:

点击START_DOWNLOAD按钮:


从运行结果看到实践成功了。

本次实践的重点是实现AsyncTask的子类,实现了该子类的如下四个方法:

  • doInBackground():在该方法中进行了实际的网络下载任务
  • onPreExecute():在方法中在下载开始前显示了一个进度条
  • onProgressUpdate():该方法复制在下载进度改变时更新进度条的进度值
  • onPostExecute():该方法的代码复制当下载完成后进行显示代码

对照前面对AsyncTask的介绍,我们对简单的实践有了更加清晰的认识。

项目源码:https://github.com/aotuzhao/AsyncTaskDemo

AsyncTask的实现基本原理

上面介绍了AsyncTask的基本使用,有些读者可能会疑问,AsyncTask内部是怎么执行的呐,我们使用Handler也可以实现上面实践的效果,那它的执行过程与我们使用Handler又有什么区别呢?下面我们来详细介绍一下AsyncTask的执行原理。

我们先看AsyncTask的几个核心方法(SDK-26,AsyncTask.java):

 /**
     * Override this method to perform a computation on a background thread. The
     * specified parameters are the parameters passed to {@link #execute}
     * by the caller of this task.
     *
     * This method can call {@link #publishProgress} to publish updates
     * on the UI thread.
     *
     * @param params The parameters of the task.
     *
     * @return A result, defined by the subclass of this task.
     *
     * @see #onPreExecute()
     * @see #onPostExecute
     * @see #publishProgress
     */
     //这是一个abstract方法,因此必须覆写
    @WorkerThread
    protected abstract Result doInBackground(Params... params);

    /**
     * Runs on the UI thread before {@link #doInBackground}.
     *
     * @see #onPostExecute
     * @see #doInBackground
     */
     //执行在DoInBackground之前,在此更新线程
    @MainThread
    protected void onPreExecute() {
    }

    /**
     * 

Runs on the UI thread after {@link #doInBackground}. The
     * specified result is the value returned by {@link #doInBackground}.

     * 
     * 

This method won't be invoked if the task was cancelled.

     *
     * @param result The result of the operation computed by {@link #doInBackground}.
     *
     * @see #onPreExecute
     * @see #doInBackground
     * @see #onCancelled(Object) 
     */
     //后台操作执行完成后会调用的方法,在此更新UI,result为DoInBackground返回的结果
    @SuppressWarnings({"UnusedDeclaration"})
    @MainThread
    protected void onPostExecute(Result result) {
    }

    /**
     * Runs on the UI thread after {@link #publishProgress} is invoked.
     * The specified values are the values passed to {@link #publishProgress}.
     *
     * @param values The values indicating progress.
     *
     * @see #publishProgress
     * @see #doInBackground
     */
     //在此更新进度
    @SuppressWarnings({"UnusedDeclaration"})
    @MainThread
    protected void onProgressUpdate(Progress... values) {
    }

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

    /**
     * Executes the task with the specified parameters. The task returns
     * itself (this) so that the caller can keep a reference to it.
     * 
     * 

This method is typically used with {@link #THREAD_POOL_EXECUTOR} to
     * allow multiple tasks to run in parallel on a pool of threads managed by
     * AsyncTask, however you can also use your own {@link Executor} for custom
     * behavior.
     * 
     * 

<em>Warning:</em> Allowing multiple tasks to run in parallel from
     * a thread pool is generally <em>not</em> what one wants, because the order
     * of their operation is not defined.  For example, if these tasks are used
     * to modify any state in common (such as writing a file due to a button click),
     * there are no guarantees on the order of the modifications.
     * Without careful work it is possible in rare cases for the newer version
     * of the data to be over-written by an older one, leading to obscure data
     * loss and stability issues.  Such changes are best
     * executed in serial; to guarantee such work is serialized regardless of
     * platform version you can use this function with {@link #SERIAL_EXECUTOR}.
     *
     * 

This method must be invoked on the UI thread.
     *
     * @param exec The executor to use.  {@link #THREAD_POOL_EXECUTOR} is available as a
     *              convenient process-wide thread pool for tasks that are loosely coupled.
     * @param params The parameters of the task.
     *
     * @return This instance of AsyncTask.
     *
     * @throws IllegalStateException If {@link #getStatus()} returns either
     *         {@link AsyncTask.Status#RUNNING} or {@link AsyncTask.Status#FINISHED}.
     *
     * @see #execute(Object[])
     */
     /**
     *执行任务,注意execute方法必须在UI线程中调用
     *exec 执行任务的线程池
     *params参数
     *返回该AsyncTask实例
     */
    @MainThread
    public final AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec,
            Params... params) {
        if (mStatus != Status.PENDING) {
            //状态检测,只有在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();
        //UI 线程传递过来的参数
        mWorker.mParams = params;
        //交给线程池管理器进行调度,参数为FutureTask类型,构造mFuture时mWorker被传递进去,后面继续分析
        exec.execute(mFuture);
        //返回自身,使得调用者可以保持一个引用
        return this;
    }

    /**
     * Convenience version of {@link #execute(Object...)} for use with
     * a simple Runnable object. See {@link #execute(Object[])} for more
     * information on the order of execution.
     *
     * @see #execute(Object[])
     * @see #executeOnExecutor(java.util.concurrent.Executor, Object[])
     */
    @MainThread
    public static void execute(Runnable runnable) {
        sDefaultExecutor.execute(runnable);
    }

    /**
     * This method can be invoked from {@link #doInBackground} to
     * publish updates on the UI thread while the background computation is
     * still running. Each call to this method will trigger the execution of
     * {@link #onProgressUpdate} on the UI thread.
     *
     * {@link #onProgressUpdate} will not be called if the task has been
     * canceled.
     *
     * @param values The progress values to update the UI with.
     *
     * @see #onProgressUpdate
     * @see #doInBackground
     */
     //发布进度,values进度值
    @WorkerThread
    protected final void publishProgress(Progress... values) {
        if (!isCancelled()) {
            getHandler().obtainMessage(MESSAGE_POST_PROGRESS,
                    new AsyncTaskResult<Progress>(this, values)).sendToTarget();
        }
    }

看上面的部分源码,通过注释(英文不自信的童鞋可以看下中文简单注释)我们可以了解到以下几点:

  1. doInBackground(Params… params)是一个抽象方法,继承AsyncTask时必须覆写此方法
  2. onPreExecute(),onProgressUpdate(Progress… values)、onPostExecute(Result result)、onCancelled()几个方法都是空的,需要的时候可以选择性地覆写它们。
  3. publishProgress(Progress… values)是final修饰的,不能覆写,只能去调用,一般会在doInBackground(Params… params)中调用此方法来更新进度条
  4. 另外,在源码中可以看到一个Status的枚举类和getStatus()方法,Status枚举代表了AysncTask的状态,Status枚举类代码(AsyncTask.java)如下:
/**
     * Indicates the current status of the task. Each status will be set only once
     * during the lifetime of a task.
     */
    public enum Status {
        /**
         * Indicates that the task has not been executed yet.
         */
        PENDING,
        /**
         * Indicates that the task is running.
         */
        RUNNING,
        /**
         * Indicates that {@link AsyncTask#onPostExecute} has finished.
         */
        FINISHED,
    }
    /**
     * Returns the current status of this task.
     *
     * @return The current status.
     */
    public final Status getStatus() {
        return mStatus;
    }

从上述源码中可以看到,AsyncTask的初始状态为PENDING,代表待定状态,RUNNING代表执行状态,FINISHED代表结束状态。这几种状态在AsyncTask一次生命周期内的很多地方被使用,十分重要。从上面源码中看到,调用execute时会判断任务的状态,如果是非PENDING状态就会抛出异常。这就是为什么一个AsyncTask实例只能运行一次,因为运行后,AsyncTask的状态就变成了FINISHED状态。此外,在execute函数中涉及到的三个陌生变量:sDefaultExecutor、mWorker、mFuture,我们来看看它们的庐山真面目。为了便于阅读分析,我们对AsyncTask.java源码按照需要进行截取和部分忽略,完整源码请自行查看(sdk\sources\android-26\android\os\AsyncTask.java)。

关于sDefaultExecutor,它是ThreadPoolExecutor的实例,用于管理提交到AsyncTask的任务。源码如下:

 
    private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
    // We want at least 2 threads and at most 4 threads in the core pool,
    // preferring to have 1 less than the CPU count to avoid saturating
    // the CPU with background work
    private static final int CORE_POOL_SIZE = Math.max(2, Math.min(CPU_COUNT - 1, 4));  //核心线程数
    private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;     //最大的线程数量
    private static final int KEEP_ALIVE_SECONDS = 30;       //线程空闲时的保留时间
    
    //线程工厂
    private static final ThreadFactory sThreadFactory = new ThreadFactory() {
        private final AtomicInteger mCount = new AtomicInteger(1);
        
        //新建一个线程
        public Thread newThread(Runnable r) {
            return new Thread(r, "AsyncTask #" + mCount.getAndIncrement());
        }
    };
    //线程队列
    private static final BlockingQueue<Runnable> sPoolWorkQueue =
            new LinkedBlockingQueue<Runnable>(128);

    /**
     * An {@link Executor} that can be used to execute tasks in parallel.
     */
    public static final Executor 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;
    }

    /**
     * An {@link Executor} that executes tasks one at a time in serial
     * order.  This serialization is global to a particular process.
     */
    public static final Executor SERIAL_EXECUTOR = new SerialExecutor();
    //默认的任务调度是顺序执行
    private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;
    private static InternalHandler sHandler;

    private final WorkerRunnable<Params, Result> mWorker;
    private final FutureTask<Result> mFuture;
    private final Handler mHandler;
    //顺序执行的Executor
    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执行
                THREAD_POOL_EXECUTOR.execute(mActive);
            }
        }
    }

从上述代码中可以看到,sDefaultExecutor只负责将异步任务分发给THREAD_POOL_EXECUTOR线程池,因此,真正执行任务的地方是THREAD_POOL_EXECUTOR。而mWorker实际上是AsyncTask的一个抽象内部类的实现对象实例,它实现了Callable接口中的call()方法,代码如下

 private static abstract class WorkerRunnable<Params, Result> implements Callable<Result> {
        Params[] mParams;
    }

而mFuture实际上是FutureTask的实例,FutureTask类是一个可管理的异步任务,使得这些异步任务可以被更精确地控制。mWorker和mFuture的初始化是在AsyncTask的构造函数中,源码如下:

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
                    //执行doInBackground
                    result = doInBackground(mParams);
                    Binder.flushPendingCommands();
                } catch (Throwable tr) {
                    mCancelled.set(true);
                    throw tr;
                } finally {
                    //调用postResult将结果投递到UI线程
                    postResult(result);
                }
                return result;
            }
        };
        //在mFuture实例中,将会调用mWorker做后台任务,完成后执行done方法
        //这里将mWorker作为参数传递给了mFuture对象
        mFuture = new FutureTask<Result>(mWorker) {
            @Override
            protected void done() {
                try {
                    //如果postResult没有被执行,那么执行postResultIfNotInvoked
                    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);
                }
            }
        };
    }

    private void postResultIfNotInvoked(Result result) {
        final boolean wasTaskInvoked = mTaskInvoked.get();
        if (!wasTaskInvoked) {
            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;
    }

从上面代码中,Worker的call函数中调用了doInBackground函数,并且最后将结果通过postResult投递出去。如果期间出现异常,postResult将不会被调用,那么最终在Future的done函数中会检测是否执行成功,如果执行成功且未调用postResult,那么postResult函数分发结果,否则忽略该执行结果。

创建AsyncTask对象之后,我们通常就会执行AsyncTask的execute函数,我们继续看execute的执行流程:
AsyncTask execute
在onPreExecute函数执行是用户在该函数中执行一些操作。然后将参数设置给mWorker,最终都是进入到ThreadPoolExecutor的execute函数,ThreadPoolExecutor的部分源码如下:

ThreadPoolExecutor
ThreadPoolExecutor

忽略各种对工作线程、任务数量的判断,可以看到这段代码的主要功能是将异步任务mFuture加入到将要执行的队列中,我们继续追踪函数addWorker,代码如下:

ThreadPoolExecutor_addworker
ThreadPoolExecutor

在addWorker中我们将FutureTask传递到Worker中,并且将该Worker对象添加到了线程队列等待执行,由于mFuture是FutureTask类型,我们继续追踪到FutureTask源码中。可以看出其工作函数,即上面我们构造mFuture时用的构造函数,我们传递的就是mWorker:

futuretask
FutureTask

可以看到构造函数又将mWorker交给了Callable类,当启动该mFuture时会执行run方法:

futuretask_run
FutureTask

从上面源码中我们看到,最终调用Worker对象的call方法,而在mWorker的call函数中最终调用了AsyncTask的doInBackground函数,至此,线程真正启动了!!获取call函数的结果后,最终调用set(result),我们看这段代码:

futuretask_set
FutureTask
futuretask——finish
FutureTask

最终判断任务的状态,如果任务顺利执行,那么调用done函数。我们前面说过,在AsyncTask构造方法中创建的mFuture对象覆写了done方法,在这个方法中获取调用结果,最后通过postResult将结果投递给UI线程。

再来分析AsyncTask中的sHandler。这个sHandler实例实际上是AsyncTask内部类InternalHandler的实例,而InternalHandler正是继承了Handler,下面我们分析一下它的代码:

InternalHandler
InternalHandler

从上面源码中可以看到,在处理消息时,遇到“MESSAGE_POST_RESULT”时,它会调用AsyncTask中的finish()方法,我们看一下finish()方法的定义:
finish
原来finish()方法是根据执行的情况来调用不同的方法的。那么执行情况在执行的时候进行了设置:
AsyncTask_mFuture
postResult
可以看到如果执行中出现了异常,那么最终在finish()方法中将会调用onCancelled方法,执行顺利会调用onPostExecute方法,将执行结果进行显示,并将AsyncTask状态进行修改。

概括来说,当我们调用execute(Params… params)方法后,execute方法会调用onPreExecute()方法,然后由ThreadPoolExecutor实例sDefaultExecutor执行一个FutureTask任务,这个过程中doInBackground(Params… params)将被调用,如果被开发者覆写的doInBackground(Params… params)方法中调用了publishProgress(Progress… values)方法,则通过InternalHandler实例sHandler发送一条MESSAGE_POST_PROGRESS消息,更新进度,sHandler处理消息时onProgressUpdate(Progress… values)方法将被调用;在doInBackground(sDefaultExecutor)执行完后,(如果发生异常会将mCancelled.set(true)),postResult被调用,在postResult方法中发出一条MESSAGE_POST_RESULT的消息,sHandler处理消息时调用finish函数,在finish函数中,如果执行出现异常(mCancelled.get()为true),则调用onCancelled()方法;如果执行成功则调用onPostExecute(Result result)方法让用户得以在UI线程处理结果。

经过上面介绍,相信你已经认识到了AsyncTask的本质了,它是对线程池加上Handler的封装,减少了开发者处理问题的复杂度,提高了开发效率,希望读者多多体会。

Android消息机制–Handler

发表评论

电子邮件地址不会被公开。

3 × 2 =