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的所有流程基本都分析完了。
- Android中AsyncTask的介绍,示例和原理分析
- Android中AsyncTask的分析和使用
- Android AsyncTask的使用介绍与示例
- Android AsyncTask运作原理和源码分析
- Android AsyncTask运作原理和源码分析
- Android AsyncTask运作原理和源码分析
- Android AsyncTask运作原理和源码分析
- Android AsyncTask原理分析
- android中异步任务AsyncTask的应用和工作原理
- Android中AsyncTask的用法示例
- 基于Android 7.1的AsyncTask原理分析
- Android中AsyncTask的使用和源码分析
- Android中线程形态AsyncTask、HandlerThread和IntentService工作原理分析
- Android的AsyncTask原理和配置
- 【android】AsyncTask的使用和工作原理
- android之AsyncTask原理分析
- Android AsyncTask案例原理分析
- android AsyncTask介绍 AsyncTask和Handler对比
- position 定位相互覆盖的问题
- 基于AXI VDMA的图像采集系统
- 信道安全
- 雪城大学信息安全讲义 3.1 Set-UID 机制如何工作
- 腾讯课堂IMWeb Vue.js 笔记
- Android中AsyncTask的介绍,示例和原理分析
- react-router 学习笔记
- poj3348 Cows【凸包面积】
- Android 网络请求 框架
- 1023. Have Fun with Numbers (20)
- UVA 7340 Sum of MSLCM
- centos7环境下搭建storm集群
- 浏览器兼容问题项目总结(五)table中td和li高度问题
- (原创)分享自己写的几个工具类(四)BigDecimal精确计算