AsyncTask源码分析
2016-03-17 16:25
363 查看
在Android中,主线程是UI线程,当需要根据其他数据进行更新UI时,如果获取数据的操作比较耗时的话,会触发ANR,所以我们应该讲耗时的操作进行异步操作,尤其是请求网络数据的操作应该放在后台线程进行,避免ANR。
而AsyncTask是Android里很常用的异步任务请求方法,AsyncTaks基本用法都会用,网上也要好多教程,就不写例子了。
以前用的时候分析过源码,发现学问还是挺多的,今天总结一下,错误之处的请诸位指正。
分析一个AsyncTask的源码,需要了解的知识:线程池,future模式,无锁操作,handler、looper和Message之间的关系。
[b]1.先看一下AsyncTask的基本成员们:[/b]
首先我们看到了AsyncTask必备的3大参数Params,Progress,Result。后续我们会再见到它们。
CPU_COUNT是java虚拟机可用的处理器个数,可以通过Runtime类的availableProcessors()方法得到。
看到刚开始的几个静态常量CORE_POOL_SIZE和MAXMUM_POOL_SIZE,就知道AsyncTask实现了一个线程池的机制。
定义了一个线程工场,以便于自定义线程
任务阻塞队列是一个定义了一个大小为128的无界任务队列。
ThreadPoolExecutor定义了一个线程池,核心池大小是CORE_POOL_SIZE,线程池最大容量是MAXIMUM_POOL_SIZE,多余空闲线程的存活时间是KEEP_ALIVE,单位是秒,使用一个大小为128的LinkedBlockingQueue作为任务队列,用一个无锁整型计数的线程工场产生线程。
定义了两个常量MESSAGE_POST_RESULT和MESSAGE_POST_PROGRESS,用于与主线程通信的标签,表示当获取到与主线程交互的数据类型,是进度信息还是结果信息。
定义了两个无锁布尔值作为任务激活或者取消的标志位,无锁操作即CAS,使用于并发情况下数据的更新。
然后看一下这里自定义的SeiralExcuter,实现了Executor接口,用于串行执行任务,并且设置为默认的Executor.
随着Android版本的不同,AsyncTask处理任务默认是串行还是并行几经更迭,至今又改回串行,因为如果有一个UI的状态信息需要不同任务下反复更改,那么并行处理显然会出现更新顺序的问题,很麻烦,所以尽量使用串行的方法。
[b]2.执行任务的方式。[/b]
一个自定义WorkerRunnable,实现了Callable接口
再看一下AsyncTask的初始化方法:
随后用这个WorkerRunnable实例构造了一个FutureTask实例,这个FutureTask则是JDK中对于future模式的一种实现。future模式是多线程开发中常用的一种设计模式,主要思想是异步调用。当Client想Server请求数据时,如果这个请求操作耗时比较长,那么Server会先立即返回一个FutureData,这个数据是一份虚拟的数据,此时Client就可以去做其他的事情了,过一段时间在调用get方法,如果这个时候Server已经准备好返回数据了,就会返回我们真正需要的数据RealData,否则Client会进入等待状态,直至返回RealData。简单的说,可以把FutureData理解成一份契约,可以用来装配RealData的契约。
这样Client就不用一直等待数据的返回,拿到FutureData后就可以先去做别的事情了,到需要的时候调用get方法获得RealData.
在重复一遍,WorkerRunnable实现了Callable接口,然后用这个实例去构造一个FuntureTask实例。
当准备好future模式后,我们需要决定让任务串行执行还是并行执行,Android官方认为大多数情况下尽量选择串行执行,直接调用execute方法即默认使用SeiralExcuter。
如果一定要并行执行,则调用executeOnExecutor方法,并且设置Executor为前文定义的线程池THREAD_POOL_EXECTOR:
execute方法也是调用executeOnExecutor方法,只是将executor直接设置为默认的SeiralExecutor。
再强调一遍,必须在UI线程中调用执行方法。
[b]3.执行的过程[/b]
直接看一下executeOnExector方法:
可以看到,在执行前先检查任务状态,如果正在执行或者已经完成,则抛出异常。处于挂起状态则设置为RUNNING状态,随后执行onPreExecute方法,此方法需要重写,用于执行任务前对UI的动作。
随后将执行任务所需参数赋给我们自定义的WorkerRunnable实例mWorker构造future实例mFuture,执行futureTask,处理任务。最后返回this,是为了保持UI线程对这个AsyncTask的引用。
到这里,我们回头看一下AsyncTask初始化的方法了。 (上文已引用,此处隐藏)
View Code
WorkerRunnable中的Call方法是继承自Callable的方法,这个方法会返回需要构造的实际数据。任务线程被激活后就将其优先级设置为后台线程,将我们任务需要的Param参数传递给doInBackground方法,去在后台执行耗时任务,整个耗时任务包括网络请求和信息处理的过程,在执行该方法的时候,可以激活publishProcess方法来进行主线程进度更新的显示,当处理完数据后返回Result。
在mFuture中重写了done方法,即当mFuture的mWorker处理完数据后,mFuture调用了get方法和postResultIfNotInvoked方法,get方法如下:
调用future模式的get方法,获取到了我们想要的结果数据,之后通过postResultIfNotInvoked方法,将数据发送出去。
终于到这一步了,可以看到数据处理完毕后,我们把在FutureTask中处理后的数据最终传给了一个message,用来与UI线程进行通信,有了Message,Handler还远么?通过getHandler方法得到一个Handler,这个Handler是自定义的InternalHandler
此处Looper.getMainLooper () 获得UI线程的Lopper,准备更新主线程的UI啦!
根据请求的标签信息判定所需行为,当需要更新进度信息时,将数据分发给onProgressUpdate方法去主线程显示进度的更新,当需要传递结果时调用finish方法,此时如果请求已被取消则不发送,如果没被取消则发送至UI线程进行UI信息的更新。
至此,AsyncTask的基本流程就走了一遍了。
总结一下:
利用FutureTask进行异步请求,通常情况下串行处理请求,如果有并行需求则使用线程池应对并发请求,线程池的配置取决于当前虚拟机所能使用的CPU数量。
三大参数:Params任务后台执行所需的参数类型,Progress是如果需要前台进度更新的更新值类型,Result则是任务返回值的类型。
使用execute方法激发异步任务执行,在发出请求之前,使用onPreExecute可以在执行任务前对UI进行标记,随后调用了doInBackground方法,进行后台耗时任务的操作,在这个过程中可以调用publishProgress方法将信息放入message,通过InternalHandler传递,调用onProgressUpdate方法将进度更新信息发送至UI线程,展示进度情况,处理完毕后,InternalHandler将结果数据的message通过onPostExecute发送至UI线程,UI线程根据结果数据进行UI的更新。
所以,在使用AsyncTask时,我们需要重写上面标红的四个方法。最少得重写一个doInBackground方法,一般情况下还得重写一个onPostExecute方法。
这种大的流程框架已经被写好(注意executrOnExecutor方法是final的),我么只需要自己定制流程一部分具体实现的方式,也就是设计模式之中的模板方法模式。
而AsyncTask是Android里很常用的异步任务请求方法,AsyncTaks基本用法都会用,网上也要好多教程,就不写例子了。
以前用的时候分析过源码,发现学问还是挺多的,今天总结一下,错误之处的请诸位指正。
分析一个AsyncTask的源码,需要了解的知识:线程池,future模式,无锁操作,handler、looper和Message之间的关系。
[b]1.先看一下AsyncTask的基本成员们:[/b]
public abstract class AsyncTask<Params, Progress, Result> { private static final String LOG_TAG = "AsyncTask"; private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors(); private static final int CORE_POOL_SIZE = CPU_COUNT + 1; private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1; private static final int KEEP_ALIVE = 1; 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 = new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE, TimeUnit.SECONDS, sPoolWorkQueue, sThreadFactory); /** * 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 final int MESSAGE_POST_RESULT = 0x1; private static final int MESSAGE_POST_PROGRESS = 0x2; private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR; private static InternalHandler sHandler; private final WorkerRunnable<Params, Result> mWorker; private final FutureTask<Result> mFuture; private volatile Status mStatus = Status.PENDING; private final AtomicBoolean mCancelled = new AtomicBoolean(); private final AtomicBoolean mTaskInvoked = new AtomicBoolean(); //后续省略 }
首先我们看到了AsyncTask必备的3大参数Params,Progress,Result。后续我们会再见到它们。
CPU_COUNT是java虚拟机可用的处理器个数,可以通过Runtime类的availableProcessors()方法得到。
看到刚开始的几个静态常量CORE_POOL_SIZE和MAXMUM_POOL_SIZE,就知道AsyncTask实现了一个线程池的机制。
定义了一个线程工场,以便于自定义线程
任务阻塞队列是一个定义了一个大小为128的无界任务队列。
ThreadPoolExecutor定义了一个线程池,核心池大小是CORE_POOL_SIZE,线程池最大容量是MAXIMUM_POOL_SIZE,多余空闲线程的存活时间是KEEP_ALIVE,单位是秒,使用一个大小为128的LinkedBlockingQueue作为任务队列,用一个无锁整型计数的线程工场产生线程。
定义了两个常量MESSAGE_POST_RESULT和MESSAGE_POST_PROGRESS,用于与主线程通信的标签,表示当获取到与主线程交互的数据类型,是进度信息还是结果信息。
定义了两个无锁布尔值作为任务激活或者取消的标志位,无锁操作即CAS,使用于并发情况下数据的更新。
然后看一下这里自定义的SeiralExcuter,实现了Executor接口,用于串行执行任务,并且设置为默认的Executor.
随着Android版本的不同,AsyncTask处理任务默认是串行还是并行几经更迭,至今又改回串行,因为如果有一个UI的状态信息需要不同任务下反复更改,那么并行处理显然会出现更新顺序的问题,很麻烦,所以尽量使用串行的方法。
[b]2.执行任务的方式。[/b]
一个自定义WorkerRunnable,实现了Callable接口
private static abstract class WorkerRunnable<Params, Result> implements Callable<Result> { Params[] mParams; }
再看一下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); } } }; }
随后用这个WorkerRunnable实例构造了一个FutureTask实例,这个FutureTask则是JDK中对于future模式的一种实现。future模式是多线程开发中常用的一种设计模式,主要思想是异步调用。当Client想Server请求数据时,如果这个请求操作耗时比较长,那么Server会先立即返回一个FutureData,这个数据是一份虚拟的数据,此时Client就可以去做其他的事情了,过一段时间在调用get方法,如果这个时候Server已经准备好返回数据了,就会返回我们真正需要的数据RealData,否则Client会进入等待状态,直至返回RealData。简单的说,可以把FutureData理解成一份契约,可以用来装配RealData的契约。
这样Client就不用一直等待数据的返回,拿到FutureData后就可以先去做别的事情了,到需要的时候调用get方法获得RealData.
在重复一遍,WorkerRunnable实现了Callable接口,然后用这个实例去构造一个FuntureTask实例。
当准备好future模式后,我们需要决定让任务串行执行还是并行执行,Android官方认为大多数情况下尽量选择串行执行,直接调用execute方法即默认使用SeiralExcuter。
public final AsyncTask<Params, Progress, Result> execute(Params... params) { return executeOnExecutor(sDefaultExecutor, params); }
如果一定要并行执行,则调用executeOnExecutor方法,并且设置Executor为前文定义的线程池THREAD_POOL_EXECTOR:
execute方法也是调用executeOnExecutor方法,只是将executor直接设置为默认的SeiralExecutor。
再强调一遍,必须在UI线程中调用执行方法。
[b]3.执行的过程[/b]
直接看一下executeOnExector方法:
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; }
可以看到,在执行前先检查任务状态,如果正在执行或者已经完成,则抛出异常。处于挂起状态则设置为RUNNING状态,随后执行onPreExecute方法,此方法需要重写,用于执行任务前对UI的动作。
随后将执行任务所需参数赋给我们自定义的WorkerRunnable实例mWorker构造future实例mFuture,执行futureTask,处理任务。最后返回this,是为了保持UI线程对这个AsyncTask的引用。
到这里,我们回头看一下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); } } }; }
View Code
WorkerRunnable中的Call方法是继承自Callable的方法,这个方法会返回需要构造的实际数据。任务线程被激活后就将其优先级设置为后台线程,将我们任务需要的Param参数传递给doInBackground方法,去在后台执行耗时任务,整个耗时任务包括网络请求和信息处理的过程,在执行该方法的时候,可以激活publishProcess方法来进行主线程进度更新的显示,当处理完数据后返回Result。
在mFuture中重写了done方法,即当mFuture的mWorker处理完数据后,mFuture调用了get方法和postResultIfNotInvoked方法,get方法如下:
public final Result get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { return mFuture.get(timeout, unit); }
调用future模式的get方法,获取到了我们想要的结果数据,之后通过postResultIfNotInvoked方法,将数据发送出去。
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; }
终于到这一步了,可以看到数据处理完毕后,我们把在FutureTask中处理后的数据最终传给了一个message,用来与UI线程进行通信,有了Message,Handler还远么?通过getHandler方法得到一个Handler,这个Handler是自定义的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; } } } private void finish(Result result) { if (isCancelled()) { onCancelled(result); } else { onPostExecute(result); } mStatus = Status.FINISHED; }
此处Looper.getMainLooper () 获得UI线程的Lopper,准备更新主线程的UI啦!
根据请求的标签信息判定所需行为,当需要更新进度信息时,将数据分发给onProgressUpdate方法去主线程显示进度的更新,当需要传递结果时调用finish方法,此时如果请求已被取消则不发送,如果没被取消则发送至UI线程进行UI信息的更新。
至此,AsyncTask的基本流程就走了一遍了。
总结一下:
利用FutureTask进行异步请求,通常情况下串行处理请求,如果有并行需求则使用线程池应对并发请求,线程池的配置取决于当前虚拟机所能使用的CPU数量。
三大参数:Params任务后台执行所需的参数类型,Progress是如果需要前台进度更新的更新值类型,Result则是任务返回值的类型。
使用execute方法激发异步任务执行,在发出请求之前,使用onPreExecute可以在执行任务前对UI进行标记,随后调用了doInBackground方法,进行后台耗时任务的操作,在这个过程中可以调用publishProgress方法将信息放入message,通过InternalHandler传递,调用onProgressUpdate方法将进度更新信息发送至UI线程,展示进度情况,处理完毕后,InternalHandler将结果数据的message通过onPostExecute发送至UI线程,UI线程根据结果数据进行UI的更新。
所以,在使用AsyncTask时,我们需要重写上面标红的四个方法。最少得重写一个doInBackground方法,一般情况下还得重写一个onPostExecute方法。
这种大的流程框架已经被写好(注意executrOnExecutor方法是final的),我么只需要自己定制流程一部分具体实现的方式,也就是设计模式之中的模板方法模式。
相关文章推荐
- fastjson
- 属性类:Properties
- 字典转成字符串iOS
- block循环引用情况
- linux 中FORK()函数详解
- Liferay Portal 6.2 GA6 SDK Plugin Maven开发
- U3D同步加载Assetbundle
- git
- CURL 参数解释
- 容易令人忽视的if、else语句逻辑错误
- 32位整数的二进制表示中有多少个1
- Python计算机视觉编程练习14:pyspider爬虫--安装篇
- HTML5 - 使用<video>播放视频
- zoj2109fatmouse
- ElasticSearch最新版本下载地址
- C - Sigma Function(LightOJ 1336)
- instance variable 'name' accessed in class method
- SpringMVC的拦截器Interceptor
- 设计模式之装饰模式
- 首贴纪念