详解Android中用于线程处理的AsyncTask类的用法及源码
为什么要用asynctask
我们写app都有一个原则,主线程不能够运行需要占用大量cpu时间片的任务,如大量复杂的浮点运算,较大的磁盘io操作,网络socket等,这些都会导致我们的主线程对用户的响应变得迟钝,甚至anr,这些会使应用的用户体验变差,但是有时又的确需要执行这些耗时的任务,那么我们通常可以使用asynctask或者new thread
来处理,这样把任务放入工作线程中执行,不会占用主线程的时间片,所以主线程会及时响应用户的操作,如果使用new thread来执行任务,那么如果需要中途取消任务执行或者需要返回任务执行结果,就需要我们自己维护很多额外的代码,而asynctask是基于concurrent架包提供的并发类实现的,上面的二个需求都已经帮我们封装了,这也是我们选择asynctask的原因。
怎么用asynctask
我们还是简单介绍下asynctask一些使用示例。我们先新建一个类demoasynctask继承asynctask,因为asynctask是抽象类,其中doinbackground方法必须重写。
private class demoasynctask extends asynctask<string, void, void> { @override protected void onpreexecute() { super.onpreexecute(); } @override protected void doinbackground(string... params) { return null; } @override protected void onpostexecute(void avoid) { super.onpostexecute(avoid); } @override protected void onprogressupdate(void... values) { super.onprogressupdate(values); } @override protected void oncancelled(void avoid) { super.oncancelled(avoid); } @override protected void oncancelled() { super.oncancelled(); } } demoasynctask task = new demoasynctask(); task.execute("demo test asynctask"); //task.executeonexecutor(asynctask.serial_executor, "test"); //mytask.executeonexecutor(asynctask.thread_pool_executor, "test");
简单分析下
上面就是asynctask最简单的使用方法,我们上面重写的方法中,oninbackground方法运行在工作线程,其他的方法全部运行在主线程,另外它的运行方式android提供给我们2个方法,上面都列出了。
1.第一个方法会使用默认的executor执行我们的任务, 其实也就是serial_executor,serial_executor我们其实也是可以通过方法去自定义的,android帮我们的默认实现是逐个执行任务,也就是单线程的,关于asynctask的任务执行是单线程实现还是多线程实现还有一段很有意思的历史,较早的版本是单线程实现,从android2.x开始,google又把它改为多线程实现,后来google发现,多线程实现的话,会有很多需要保证线程安全的额外工作留给开发者,所以从android3.0开始,又把默认实现改为单线程了,今天我们演示的是framwork代码版本是21(android5.0)。
2.同时也提供了多线程实现的接口,即我们上面写的最后一行代码 asynctask.thread_pool_executor。
public final asynctask<params, progress, result> execute(params... params) { return executeonexecutor(sdefaultexecutor, params); } private static volatile executor sdefaultexecutor = serial_executor;
其实相信大家平时工作学习中经常使用asynctask,我们直接去看asynctask类源码(插一句题外话,平时大家也可以把自己工作学习中的心得体会总结一下,记下来~~)
public abstract class asynctask<params, progress, result> { .... }
asynctask抽象类,有三个泛型参数类型,第一个是你需要传递进来的参数类型,第二个是任务完成进度的类型一般是integer,第三个是任务完成结果的返回类型,有时这些参数不是全部需要,不需要的设为void即可,另外result只有一个,但params可以有多个。
我们可以看到asynctask的默认构造器初始化了二个对象,mworker和mfuture。
private final workerrunnable<params, result> mworker; private final futuretask<result> mfuture; 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); } } };
mwoker实现了callback接口,callback接口是jdk1.5加入的高级并发架包里面的一个接口,它可以有一个泛型返回值。
public interface callable<v> { /** * computes a result, or throws an exception if unable to do so. * * @return computed result * @throws exception if unable to compute a result */ v call() throws exception; }
futuretask实现了runnablefuture接口,runnablefuture接口是jdk提供的,看名称就知道它继承了runnable和future接口,mfuture是futuretask的一个实例,可以直接被executor类实例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; }
先调用onpreexecute()方法,此时还在主线程(严格来说是调用asynctask执行的线程),然后exec.execute(mfuture),把任务交给exec处理,execute mfuture其实就是invoke mwoker,然后调用postresult(doinbackground(mparams)),此时已经运行在工作线程池,不会阻塞主线程。然后给mhandler发送message_post_result消息,然后调用finish方法,如果iscancelled,回调oncancelled,否则回调onpostexecute。
private result postresult(result result) { @suppresswarnings("unchecked") message message = shandler.obtainmessage(message_post_result, new asynctaskresult<result>(this, result)); message.sendtotarget(); return result; } private static final internalhandler shandler = new internalhandler(); 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; } } } private void finish(result result) { if (iscancelled()) { oncancelled(result); } else { onpostexecute(result); } mstatus = status.finished; }
现在其实我们已经把asynctask整个执行任务的过程走完了,其中暴露给我们的那几个回调方法也都走到了。现在我们回过头来看,asynctask其实只是对jdk 1.5提供的高级并发特性,concurrent架包做的一个封装,方便开发者来处理异步任务,当然里面还有很多细节处理的方法值得大家学习,如任务执行进度的反馈,任务执行原子性的保证等,这些留给大家自己学习了。
源码分析
下面我们再深入一些,来看asynctask的源码。下面分析这个类的实现,主要有线程池以及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,代码如下:
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);
在上面的代码中,如果有任务执行,那么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方法
- 一个任务只能被调用一次(第二次调用会抛出异常)