Android AsyncTask源码分析
来源:互联网 发布:金山软件股价 编辑:程序博客网 时间:2024/06/05 02:58
https://segmentfault.com/a/1190000004699080
简介
Android中只能在主线程中进行UI操作,如果是其它子线程,需要借助异步消息处理机制Handler。除此之外,还有个非常方便的AsyncTask
类,这个类内部封装了Handler
和线程池。本文先简要介绍AsyncTask
的用法,然后分析具体实现。
1.1 AsyncTask实例使用
下面是一个使用AsyncTask的实例,通过指定URL利用网络下载资源(此例模拟资源为字符串),以模拟耗时任务。在下载过程中,会通过进度条对话框向用户展示进度。在完成任务后将字符串展示在TextView上。具体实现细节后面会加以讲述,顺便引出AsyncTask的知识。
基本用法
AsyncTask
是一个抽象类,我们需要创建子类去继承它,并且重写一些方法。AsyncTask
接受三个泛型参数:
Params
: 指定传给任务执行时的参数的类型Progress
: 指定后台任务执行时将任务进度返回给UI线程的参数类型Result
: 指定任务完成后返回的结果的类型
除了指定泛型参数,还需要根据需要重写一些方法,常用的如下:
onPreExecute()
: 这个方法在UI线程调用,用于在任务执行前做一些初始化操作,如在界面上显示加载进度控件doInBackground
: 在onPreExecute()
结束之后立刻在后台线程调用,用于耗时操作。在这个方法中可调用publishProgress
方法返回任务的执行进度onProgressUpdate
: 在doInBackground
调用publishProgress
后被调用,工作在UI线程onPostExecute
: 后台任务结束后被调用,工作在UI线程
源码分析
下面分析这个类的实现,主要有线程池以及Handler
两部分。
线程池
当执行一个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 onPreExecute(); mWorker.mParams = params; exec.execute(mFuture); return this; }
execute
方法会调用executeOnExecutor
。在这个方法中先检查任务是否已经执行或者执行结束,然后把任务标记为running
。最开始执行的是onPreExecute
,接着把参数赋值给mWorker
对象。这个mWorker
是一个Callable
对象,最终被包装为FutureTask
,
【拓展:FutureTask是什么】
FutureTask表示一个异步运算的任务。FutureTask里面可以传入一个Callable的具体实现类,可以对这个异步运算的任务的结果进行等待获取、判断是否已经完成、取消任务等。当然,由于FutureTask也是Runnable接口的实现类,所以FutureTask也可以放入线程池中。
代码如下:
private static abstract class WorkerRunnable<Params, Result> implements Callable<Result> { Params[] mParams; } mWorker = new WorkerRunnable<Params, Result>() { public Result call() throws Exception { mTaskInvoked.set(true); Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); //noinspection unchecked return postResult(doInBackground(mParams)); } }; 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 occured while executing doInBackground()", e.getCause()); } catch (CancellationException e) { postResultIfNotInvoked(null); } } };
从上面的代码可以看出,在mWorker
对象中的call()
方法会调用doInbackground
,返回值交给postResult
方法,这个方法通过Handler
发送消息,这一点稍后再详细分析。
在mWorker
对象被封装成FutureTask
之后交由线程池执行,从execute
方法可以看出,使用的是sDefaultExecutor
,它的值默认为SERIAL_EXECUTOR
,也就是串行执行器,实现如下:
private static class SerialExecutor implements Executor { //线性双向队列,用来存储所有的AsyncTask任务 final ArrayDeque<Runnable> mTasks = new ArrayDeque<Runnable>(); //当前正在执行的AsyncTask任务 Runnable mActive; public synchronized void execute(final Runnable r) { //将新的AsyncTask任务加入到双向队列中 mTasks.offer(new Runnable() { public void run() { try { //执行AsyncTask任务 r.run(); } finally { //当前任务执行结束后执行下一个任务 scheduleNext(); } } }); if (mActive == null) { scheduleNext(); } } protected synchronized void scheduleNext() { //从任务队列中取出队列头部的任务,如果有就交给并发线程池去执行 if ((mActive = mTasks.poll()) != null) { THREAD_POOL_EXECUTOR.execute(mActive); } } }public static final Executor THREAD_POOL_EXECUTOR = new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE, TimeUnit.SECONDS, sPoolWorkQueue, sThreadFactory);
execute()方法中首先将FurtherTask对象(充当了Runnable的作用)插入到任务队列mTasks的尾部。新建的Runnable对象判空后进入scheduleNext方法。然后通过mTasks.poll()被取出,然后交给一个叫THREAD_POOL_EXECUTOR的线程池去执行(前面的SerialExecutor用于任务的排队)。
在上面的代码中,如果有任务执行,那么SerialExecutor
的execute
方法会被调用,它的逻辑是把Runnable
对象加入ArrayDeque
队列中,然后判断mActivie
是否为空。第一次执行时mActive
当然为空,所以执行scheduleNext
,其实就是取出任务队列中的第一个任务交给线程池(THREAD_POOL_EXECUTOR
)执行。加入mTask
队列的Runnable
对象的run
方法里最终一定会调用scheduleNext
,那么又会从任务队列中取出队头任务执行。这样便实现了单线程顺序执行任务,所以在AsyncTask
中默认启用的是单线程执行,只有上一个任务执行后才会执行下一个任务。如果想要启用多线程执行任务,可以直接调用 executeOnExecutor(Executor exec, Params... params)
,这里的Executor
参数可以使用AsyncTask
自带的THREAD_POOL_EXECUTOR
,也可以自己定义。
Handler
AsyncTask
内部用Handler
传递消息,它的实现如下:
private static class InternalHandler extends Handler { @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; } } }
如果消息类型是任务执行后的返回值(MESSAGE_POST_RESULT
)将调用finish()
方法:
private void finish(Result result) { if (isCancelled()) { onCancelled(result); } else { onPostExecute(result); } mStatus = Status.FINISHED; }
从上面可以知道,如果任务取消了,将调用onCancelled
,否则调用onPostExecute
,所以一个AsyncTask
任务如果取消了,那么onPostExecute
将不会得到执行。
如果消息类型是执行进度(MESSAGE_POST_PROGRESS
)将调用onProgressUpdate
,这个方法默认是空方法,我们可以根据自己的需要重写。
总结
AsyncTask
的主要逻辑就如上面所分析的,总结几个需要注意的地方:
AsyncTask
的类必须在UI线程加载(从4.1开始系统会帮我们自动完成)AsyncTask
对象必须在UI线程创建execute
方法必须在UI线程调用不要手动调用
onPreExecute()
、doInBackground
、onProgressUpdate
方法一个任务只能被调用一次(第二次调用会抛出异常)
其它还有一些细节可以自行研究源码,另外推荐几篇不错的文章:
Android源码分析—带你认识不一样的AsyncTask
Android AsyncTask完全解析,带你从源码的角度彻底理解
- Android AsyncTask源码分析
- Android源码分析--AsyncTask
- 源码分析Android AsyncTask
- Android AsyncTask源码分析
- Android-AsyncTask源码分析
- Android AsyncTask 源码分析
- android-----AsyncTask源码分析
- Android AsyncTask源码分析
- Android AsyncTask源码分析
- Android源码分析-AsyncTask
- 【Android】 AsyncTask 源码分析
- Android AsyncTask源码分析
- android源码分析-AsyncTask
- Android的AsyncTask源码分析
- Android AsyncTask源码简单分析
- Android AsyncTask的源码分析
- Android线程AsyncTask源码分析
- Android AsyncTask源码简要分析
- 中国地理-一些知识的总结
- Pointers on C——11 Dynamic Memory Allocation.3
- JDK下载、安装和环境变量配置
- vm虚拟机安装centos7,配置网卡
- springmvc报400错误,并用@inintBinder解决类型转换问题
- Android AsyncTask源码分析
- Pointers on C——11 Dynamic Memory Allocation.4
- Pointers on C——11 Dynamic Memory Allocation.5
- 从FTP获取PDF文件流并展示到页面
- (CodeForces
- CCF-201604-1-折点计数
- python 字典处理的一些坑
- CentOS-7.0.中安装与配置Tomcat-7的方法
- 大数据分析的基本概念