频道栏目
首页 > 程序开发 > 移动开发 > Android > 正文
Android中AsyncTask的介绍,示例和原理分析
2017-04-19 10:44:41      个评论    来源:qq_28269905的博客  
收藏   我要投稿

Android中AsyncTask的介绍,示例和原理分析。默认情况下,在一个相同的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 {
@Override
protected void onPreExecute() {
progressDialog.show(); // 显示进度对话框
}
@Override
protected Boolean doInBackground(Void... params) {
try {
while (true) {
int downloadPercent = doDownload(); // 这是一个虚构的方法
publishProgress(downloadPercent);
if (downloadPercent >= 100) {
break;
}
}
} catch (Exception e) {
return false;
}
return true;
}
@Override
protected void onProgressUpdate(Integer... values) {
// 在这里更新下载进度
progressDialog.setMessage("Downloaded " + values[0] + "%");
}
@Override
protected 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 execute(Params... params) {
        return executeOnExecutor(sDefaultExecutor, params);
    }

    public final AsyncTask 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 mWorker;

 

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

private static abstract class WorkerRunnable implements Callable {
        Params[] mParams;
    }
看了代码之后原来是这样:输入的params先放到mWorker里存起来了,那么mFutrue呢?直接来看源代码:
private final FutureTask mFuture;
可以看到mFuture就只关注输出的参数result了,那么params哪里去了?怎么这里就直接变成esult了,所以我猜想,肯定是mFuture中对我们存入params的mWorker.mParams做了处理,为了验证我们的猜想,我们再来看mFuture是在哪里进行初始化的,最后发现是在AsyncTask的构造函数中实现的,直接上源代码:
public AsyncTask() {
        mWorker = new WorkerRunnable() {
            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(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(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 mTasks = new ArrayDeque();
        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的所有流程基本都分析完了。
点击复制链接 与好友分享!回本站首页
上一篇:Android学习之——真机安装apk时出现INSTALL_FAILED_NO_MATCHING_ABIS错误的解决办法
下一篇:Android 网络请求 框架
相关文章
图文推荐
点击排行

关于我们 | 联系我们 | 广告服务 | 投资合作 | 版权申明 | 在线帮助 | 网站地图 | 作品发布 | Vip技术培训 | 举报中心

版权所有: 红黑联盟--致力于做实用的IT技术学习网站