Android中AsyncTask的介绍,示例和原理分析

来源:互联网 发布:淘宝热卖商品排行榜 编辑:程序博客网 时间:2024/06/04 01:26

    默认情况下,在一个相同的android应用程序当中,其里面的组件都是运行在同一个线程里面的,这个线程我们称之为main thread,当我们通过某个组件来启动另一个组件的时候,这个时候默认是在同一个线程当中完成的。Main thread用来加载我们的ui界面,完成系统和我们用户之间的交互,并将交互的结果展示给我们用户,所以main thread 又称为ui thread。因此,在android的多线程编程中,有两个重要的原则:1.绝对不能在ui thread当中进行耗时操作,不能阻塞我们的ui thread,2.不能在ui thread之外的线程当中操纵我们的ui元素。但是,如果我们想要进行一些耗时操作,而操作途中产生的结果需要随时反馈给我们的ui界面或者更通俗说是主线程,同时还不能阻塞,那么该如何实现呢?Android中提供了异步处理函数AsyncTask来实现上述需求。

    在使用AsyncTask之前,记得AsyncTask的相应限制:

    1. AsyncTask的类必须在主线程中加载
    2. AsyncTask的对象必须在主线程中创建
    3. execute方法必须在Ui线程调用
    4. 不要在程序中调用onPreexecute,onPostExecute,doInbackground,onProgressUpdate方法
    5. 一个AsyncTask对象只能执行一次,即只能调用一次execute方法,否则会报运行异常

    6. 在android1.6之前,AsyncTask是执行串行任务的,android1.6的时候开始采用线程池的方式来处理并行任务,但是从android3.0开始,为了避免带来的并发错误实现线程安全,AsyncTask又采用一个线程来串行执行任务,但我们仍然可以使用executeOnExecutor来并行的执行任务

    其中1.2.3.5点的理由在下述中会解释清楚。

    接下来介绍AsyncTask的相关函数

    1.  onPreExecute()
    这个方法会在后台任务开始执行之前调用,用于进行一些界面上的初始化操作,比如显示一个进度条对话框等。
    2.  doInBackground(Params...)
    这个方法中的所有代码都会在子线程中运行,我们应该在这里去处理所有的耗时任务。任务一旦完成就可以通过 return 语句来将任务的执行结果返回,如果 AsyncTask的第三个泛型参数指定的是 Void,就可以不返回任务执行结果。注意,在这个方法中是不可以进行 UI操作的,如果需要更新 UI元素,比如说反馈当前任务的执行进度,可以调用 publishProgress(Progress...)方法来完成。
   3.  onProgressUpdate(Progress...)
    当在后台任务中调用了 publishProgress(Progress...)方法后,这个方法就会很快被调用,方法中携带的参数就是在后台任务中传递过来的。在这个方法中可以对 UI 进行操作,利用参数中的数值就可以对界面元素进行相应地更新。 
4.  onPostExecute(Result)
   当后台任务执行完毕并通过 return语句进行返回时,这个方法就很快会被调用。返回的数据会作为参数传递到此方法中,可以利用返回的数据来进行一些 UI 操作,比如说提醒任务执行的结果,以及关闭掉进度条对话框等。

 一个完整的asyncTask的例子:

class DownloadTask extends AsyncTask<Void, Integer, Boolean> {@Overrideprotected void onPreExecute() {progressDialog.show(); // 显示进度对话框}@Overrideprotected Boolean doInBackground(Void... params) {try {while (true) {int downloadPercent = doDownload(); // 这是一个虚构的方法publishProgress(downloadPercent);if (downloadPercent >= 100) {break;}}} catch (Exception e) {return false;}return true;}@Overrideprotected void onProgressUpdate(Integer... values) {// 在这里更新下载进度progressDialog.setMessage("Downloaded " + values[0] + "%");}@Overrideprotected void onPostExecute(Boolean result) {progressDialog.dismiss(); // 关闭进度对话框// 在这里提示下载结果if (result) {Toast.makeText(context, "Download succeeded",Toast.LENGTH_SHORT).show();} else {Toast.makeText(context, " Download failed",Toast.LENGTH_SHORT).show();}}}
   如果想要启动这个任务,只需编写以下代码即可:
new DownloadTask().execute();
  下面结合源代码来分析AsyncTask的工作原理,首先来看其调用方法execute(),直接上源代码:
 public final AsyncTask<Params, Progress, Result> execute(Params... params) {        return executeOnExecutor(sDefaultExecutor, params);    }    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;    }
     在这里有很多疑问,mWorker.mParams = params是什么鬼?为什么execute进来的参数只有mFuture,mFuture又是什么?难道不需要传入params了吗?首先来看mWorker.:
 private final WorkerRunnable<Params, Result> mWorker;
    

  可以看到mWorker关注的就是输入的参数Params和输出的参数Result,这里再来看WorkerRunnable的定义:

private static abstract class WorkerRunnable<Params, Result> implements Callable<Result> {        Params[] mParams;    }
  看了代码之后原来是这样:输入的params先放到mWorker里存起来了,那么mFutrue呢?直接来看源代码:
private final FutureTask<Result> mFuture;
  可以看到mFuture就只关注输出的参数result了,那么params哪里去了?怎么这里就直接变成esult了,所以我猜想,肯定是mFuture中对我们存入params的mWorker.mParams做了处理,为了验证我们的猜想,我们再来看mFuture是在哪里进行初始化的,最后发现是在AsyncTask的构造函数中实现的,直接上源代码:
public AsyncTask() {        mWorker = new WorkerRunnable<Params, Result>() {            public Result call() throws Exception {                mTaskInvoked.set(true);                Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);                //noinspection unchecked                Result result = doInBackground(mParams);                Binder.flushPendingCommands();                return postResult(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);                }            }        };    }
  可以看到mFuture其实是封装之后的mWorker,而mWorker的参数不就是关键的Params, Result吗?因此可以理解为mWorker关注的是输入的参数和输出参数,而其call方法最终会在线程池THREAD_POOL_EXECUTOR中执行,这里也可以看成是任务被处理的地方,而mFuture只有Result这个参数,表示到它这里任务已经处理完了,它只关心处理之后的结果,即Result,那么,我们来看看任务被处理的地方call方法,直接上源代码:
public Result call() throws Exception {                mTaskInvoked.set(true);                Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);                //noinspection unchecked                Result result = doInBackground(mParams);                Binder.flushPendingCommands();                return postResult(result);            }
  mTaskInvoked.set(true)表示当前方法已经被调用过了,同时在执行 doInBackground之前将进程的环境切换为BACKGROUND环境:
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
  紧接着就正式进行doInBackground工作了,获得了处理的结果result ,将其传给postResult来实现对返回结果的最终处理,因此来看其源代码:
private Result postResult(Result result) {        @SuppressWarnings("unchecked")  Message message = getHandler().obtainMessage(MESSAGE_POST_RESULT,                new AsyncTaskResult<Result>(this, result));        message.sendToTarget();        return result;    }
  在上面代码中,postResult会通过变量sHandler(通过getHandler()获得)来发送一个MESSAGE_POST_RESULT的消息(向自己发消息,即发送result),那么这个result在哪处理呢?来看sHandler的初始化:(来波广告,有对handler或者android消息机制不清楚,不理解的同学可以参考下我的前几篇博客哈)
private static InternalHandler sHandler;
   再来看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:                    // There is only one result                    result.mTask.finish(result.mData[0]);                    break;                case MESSAGE_POST_PROGRESS:                    result.mTask.onProgressUpdate(result.mData);                    break;            }        }    }
   注意看在源代码中InternalHandler是静态变量,那么在java中的静态变量代表了什么呢?意味着声明为static的变量实质上就是全局变量。当声明一个对象时,并不产生static变量的拷贝,而是该类所有的实例变量共用同一个static变量。静态变量与静态方法类似。所有此类实例共享此静态变量,也就是说在类装载时,只分配一块存储空间,所有此类的对象都可以操控此块存储空间,因此为了对成功的实现对ui进行操作,sHandler这个对象的创建必须在主线程中。

  又由于静态成员会在加载类的时候进行初始化,因此变相意味着AsyncTask类必须在主线程中进行加载。接下来再来看源码中出现的的finish方法:

private void finish(Result result) {        if (isCancelled()) {            onCancelled(result);        } else {            onPostExecute(result);        }        mStatus = Status.FINISHED;    }
  源码很简单,不想讲了,同时在源码中也可以看到对于执行进度不同的任务,在asyncTask中是如何进行标记的呢?由 mStatus = Status.FINISHED;可知是根据枚举Status进行标记的,Status中有三种状态:
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,    }
   到这里,我们再来回到execute说调用的executeOnExecutor来解答我们一开始的疑问:
    mWorker.mParams = params;        exec.execute(mFuture);
  其实在executeOnExecutor中上述两句代码就已经实现了异步消息的处理了,因此在execute中进来的就只有mFuture了。

  AsyncTask的异步处理原理基本也快分析完了,那么我们就继续往下走在调用executeOnExecutor的时候我们其实传入的是sDefaultExecutor,那么这个是啥呢?不废话,上代码:

public static final Executor SERIAL_EXECUTOR = new SerialExecutor();private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;
  在上述代码中sDefaultExecutor实际上是一个串行的线程池,一个进程中所有的asyncTask全部在这个串行的线程池中排队执行:(线程池的相关分析在下篇博客,敬请期待)
 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);            }        }    }
  从SerialExecutor 的实现可以看到asyncTask的排队执行过程。首先系统会把asyncTask的params参数对应源代码中的mWorker封装为FutureTask对象,对应源代码中的mFuture,在这里FutureTask是一个并发类,充当了Runnable的作用。接着这个FutureTask会交给SerialExecutor的execute方法去处理,代码中对应为exec.execute(mFuture);该方法实现了将FutureTask插入到任务队列 mTasks中:
mTasks.offer(new Runnable() {                public void run() {                    try {                        r.run();                    } finally {                        scheduleNext();                    }                }            });
  调用r.run方法(可以理解为执行了mWorker的call方法)实现对任务的处理,具体的处理过程上文已经分析过了(请参见上文对call的分析),这里就不啰嗦了。接着回到任务队列,如果这个时候没有正在活动的SerialExecutor任务,就会调用SerialExecutor的scheduleNext方法来执行下一个asyncTask任务,同时当一个asyncTask任务执行完后,asyncTask会继续执行其他任务直到所有任务都被执行完,从这一点可以看出,asyncTask是串行执行的。
  AsyncTask里有两个线程池(SerialExecutor 和THREAD_POOL_EXECUTOR)和一个Handler(InternalHandler),其中SerialExecutor和InternalHandler都已经介绍过了,还剩下THREAD_POOL_EXECUTOR。  
  我们来看看在AsyncTask中的SerialExecutor 和THREAD_POOL_EXECUTOR是如何相互配合工作的。我们再结合SerialExecutor的源代码来看,mTasks.offer()方法可以理解为将params参数封装为futureTask对象,然后再将其通过offer()来插入消息队列mTasks当中。因此SerialExecutor 中是先简单的将任务进行排队(当然,这里排队的任务实际上是充当了runnable作用的futureTaks对象mFuture),然后再对队列中的当前任务进行处理,当这个任务的所有内容都被处理完了,再来调用scheduleNext()方法:
 protected synchronized void scheduleNext() {            if ((mActive = mTasks.poll()) != null) {                THREAD_POOL_EXECUTOR.execute(mActive);            }        }
   再来看THREAD_POOL_EXECUTOR的定义:
public static final Executor THREAD_POOL_EXECUTOR            = new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE,                    TimeUnit.SECONDS, sPoolWorkQueue, sThreadFactory);
  其实就是最常规的线程池的定义方式(有关线程池的只是以后会讲)。再来看scheduleNext() 中的关键参数mActive,实际定义是runnable,而mTasks.poll()方法表示获得任务队列中最新的任务,也就是我们一开始传进去的mFuture。
   至此,AsyncTask的所有流程基本都分析完了。

0 0
原创粉丝点击