Android中AsyncTask的入门使用学习指南
前言
asynctask异步任务,用于执行耗时任务并在ui线程中更新结果。
我们都知道,android ui线程中不能执行耗时的任务,否则就会出现anr。对于耗时的操作就需要放到子线程中操作,操作完成后需要通知ui线程进行更新等操作,这就需要android的异步消息机制(创建一个message对象,使用handler发送出去,然后在handler的handlemessage()方法中获得刚才发送的message对象,然后在这里进行ui操作)。
不过本文要说的是asynctask,其实早在android 1.5版本就引入这个类,所以我知道大多数人对它的用法都已经非常熟悉了。基本用法在网上搜搜就有很多教程,然而,在使用时,仍需要注意其潜在的问题以及缺陷。
[toc]
asynctask 简单使用
public class mainactivity extends appcompatactivity implements view.onclicklistener { private static final string tag = "mainactivity"; private progressdialog mdialog; private asynctask masynctask; @override protected void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.activity_main); mdialog = new progressdialog(this); mdialog.setmax(100); mdialog.setprogressstyle(progressdialog.style_horizontal); mdialog.setcancelable(false); masynctask = new myasynctask(); findviewbyid(r.id.tv).setonclicklistener(this); } @override public void onclick(view view) { masynctask.execute(); } private class myasynctask extends asynctask<void, integer, void> { @override protected void onpreexecute() { mdialog.show(); log.e(tag, thread.currentthread().getname() + " onpreexecute "); } @override protected void doinbackground(void... params) { // 模拟数据的加载,耗时的任务 for (int i = 0; i < 100; i++) { try { thread.sleep(80); } catch (interruptedexception e) { e.printstacktrace(); } publishprogress(i); } log.e(tag, thread.currentthread().getname() + " doinbackground "); return null; } @override protected void onprogressupdate(integer... values) { mdialog.setprogress(values[0]); log.e(tag, thread.currentthread().getname() + " onprogressupdate "); } @override protected void onpostexecute(void result) { // 进行数据加载完成后的ui操作 mdialog.dismiss(); log.e(tag, thread.currentthread().getname() + " onpostexecute "); } } }
如以上实例中,当ui线程中需求处理耗时的操作时,我们可以放在asynctask的doinbackground方法中执行,这个抽象的类,有几个方法需要我们重新,除了doinbackground,我们可以在onpreexecute中为这个耗时方法进行一些预处理操作,同时我们在onpostexecute中对ui进行更新操作。实例中的publishprogress对应的回调是onprogressupdate,这样可以实时更新ui,提供更好的用户体验。
asynctask 原理
asynctask主要有二个部分:一个是与主线的交互,另一个就是线程的管理调度。虽然可能多个asynctask的子类的实例,但是asynctask的内部handler和threadpoolexecutor都是进程范围内共享的,其都是static的,也即属于类的,类的属性的作用范围是classpath,因为一个进程一个vm,所以是asynctask控制着进程范围内所有的子类实例。
1、与主线程交互
与主线程交互是通过handler来进行的,因为本文主要探讨asynctask在任务调度方面的,所以对于这部分不做细致介绍,感兴趣的朋友可以继续去看asynctask的源码部分。
2、线程任务的调度
内部会创建一个进程作用域的线程池来管理要运行的任务,也就就是说当你调用了asynctask#execute()
后,asynctask会把任务交给线程池,由线程池来管理创建thread和运行therad。对于内部的线程池不同版本的android的实现方式是不一样的:
asynctask 发展
接下来我们先简单的了解一下asynctask的历史
首先在android 3.0之前的版本,threadpool的限制是5个,线程的并发量是128个,阻塞队列长度10,也就是说超过138个则会抛出异常。因此我们在使用的时候,一定要主要这部分限制,正确的使用。
到了在android 3.0之后的,也许是google也意识到这个问题,对asynctask的api做了调整:
· execute()提交的任务,按先后顺序每次只运行一个也就是说它是按提交的次序,每次只启动一个线程执行一个任务,完成之后再执行第二个任务,也就是相当于只有一个后台线程在执行所提交的任务(executors.newsinglethreadpool()
)。
· 新增了接口executeonexecutor()
这个接口允许开发者提供自定义的线程池来运行和调度thread,如果你想让所有的任务都能并发同时运行,那就创建一个没有限制的线程池(executors.newcachedthreadpool()
),并提供给asynctask。这样这个asynctask实例就有了自己的线程池而不必使用asynctask默认的。
· 新增了二个预定义的线程池serial_executor和thread_pool_executor。其实thread_pool_executor并不是新增的,之前的就有,只不过之前(android 2.3)它是asynctask私有的,未公开而已。thread_pool_executor是一个corepoolsize为5的线程池,也就是说最多只有5个线程同时运行,超过5个的就要等待。所以如果使用executeonexecutor(asynctask.thread_pool_executor)
就跟2.3版本的asynctask.execute()
效果是一样的。而serial_executor是新增的,它的作用是保证任务执行的顺序,也就是它可以保证提交的任务确实是按照先后顺序执行的。它的内部有一个队列用来保存所提交的任务,保证当前只运行一个,这样就可以保证任务是完全按照顺序执行的,默认的execute()使用的就是这个,也就是executeonexecutor(asynctask.serial_executor)与execute()是一样的。
asynctask 源码简析
这里我们从asynctask的起点开始分析,主要有 execute()
、executeonexecutor()
。
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; }
- 从代码中可以看出,execute()其实也是通过执行
executeonexecutor()
方法,只是将其中的executor设置为默认值。 - 在
executeonexecutor()
中将当前asynctask的状态为running,上面的switch也可以看出,每个异步任务在完成前只能执行一次。 - 接下来就执行了
onpreexecute()
,当前依然在ui线程,所以我们可以在其中做一些准备工作。 - 将我们传入的参数赋值给了
mworker.mparams
- 最后
exec.execute(mfuture)
相信大家对代码中出现的mworker,以及mfuture都会有些困惑。接下来我们来看看mworker找到这个类:
private static abstract class workerrunnable<params, result> implements callable<result> { params[] mparams; }
可以看到是callable的子类,且包含一个mparams用于保存我们传入的参数,下面看初始化mworker的代码:
public asynctask() { 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)); } }; //... }
可以看到mworker在构造方法中完成了初始化,并且因为是一个抽象类,在这里new了一个实现类,实现了call方法,call方法中设置mtaskinvoked=true
,且最终调用doinbackground(mparams)
方法,并返回result值作为参数给postresult方法.可以看到我们的doinbackground出现了,下面继续看:
private result postresult(result result) { @suppresswarnings("unchecked") message message = shandler.obtainmessage(message_post_result, new asynctaskresult<result>(this, result)); message.sendtotarget(); return result; }
可以看到postresult中出现了我们熟悉的异步消息机制,传递了一个消息message, message.what为message_post_result;message.object= new asynctaskresult(this,result);
private static class asynctaskresult<data> { final asynctask mtask; final data[] mdata; asynctaskresult(asynctask task, data... data) { mtask = task; mdata = data; } }
asynctaskresult就是一个简单的携带参数的对象。
看到这,我相信大家肯定会想到,在某处肯定存在一个shandler,且复写了其handlemessage方法等待消息的传入,以及消息的处理。
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; } } }
这里出现了我们的handlemessage,可以看到,在接收到message_post_result消息时,执行了result.mtask.finish(result.mdata[0]);
其实就是我们的asynctask.this.finish(result)
,于是看finish方法
private void finish(result result) { if (iscancelled()) { oncancelled(result); } else { onpostexecute(result); } mstatus = status.finished; }
可以看到,如果我们调用了cancel()
则执行oncancelled回调;正常执行的情况下调用我们的onpostexecute(result);
主要这里的调用是在handler的handlemessage中,所以是在ui线程中。最后将状态置为finished。
mwoker看完了,应该到我们的mfuture了,依然实在构造方法中完成mfuture的初始化,将mworker作为参数,复写了其done方法。
public asynctask() { ... 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); } } }; }
任务执行结束会调用:postresultifnotinvoked(get());get()表示获取mworker的call的返回值,即result.然后看postresultifnotinvoked方法
private void postresultifnotinvoked(result result) { final boolean wastaskinvoked = mtaskinvoked.get(); if (!wastaskinvoked) { postresult(result); } }
如果mtaskinvoked不为true,则执行postresult;但是在mworker初始化时就已经将mtaskinvoked为true,所以一般这个postresult执行不到。好了,到了这里,已经介绍完了execute方法中出现了mworker和mfurture,不过这里一直是初始化这两个对象的代码,并没有真正的执行。下面我们看真正调用执行的地方。execute方法中的:还记得上面的execute中的:exec.execute(mfuture)
exec为executeonexecutor(sdefaultexecutor, params)
中的sdefaultexecutor
下面看这个sdefaultexecutor
private static volatile executor sdefaultexecutor = serial_executor; public static final executor serial_executor = new serialexecutor(); 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); } } }
可以看到sdefaultexecutor其实为serialexecutor的一个实例,其内部维持一个任务队列;直接看其execute(runnable runnable)
方法,将runnable放入mtasks队尾;再判断当前mactive是否为空,为空则调用schedulenext。方法schedulenext,则直接取出任务队列中的队首任务,如果不为null则传入thread_pool_executor进行执行。下面看thread_pool_executor为何方神圣:
public static final executor thread_pool_executor =new threadpoolexecutor(core_pool_size, maximum_pool_size, keep_alive, timeunit.seconds, spoolworkqueue, sthreadfactory);
可以看到就是一个自己设置参数的线程池,参数为:
private static final int core_pool_size = 5; private static final int maximum_pool_size = 128; 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>(10);
看到这里,大家可能会认为,背后原来有一个线程池,且最大支持128的线程并发,加上长度为10的阻塞队列,可能会觉得就是在快速调用138个以内的asynctask子类的execute方法不会出现问题,而大于138则会抛出异常。其实不是这样的,我们再仔细看一下代码,回顾一下sdefaultexecutor,真正在execute()
中调用的为sdefaultexecutor.execute
:
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); } } }
可以看到,如果此时有10个任务同时调用execute(s synchronized)方法,第一个任务入队,然后在mactive = mtasks.poll()) != null
被取出,并且赋值给mactivte,然后交给线程池去执行。然后第二个任务入队,但是此时mactive并不为null,并不会执行schedulenext();所以如果第一个任务比较慢,10个任务都会进入队列等待;真正执行下一个任务的时机是,线程池执行完成第一个任务以后,调用runnable中的finally代码块中的schedulenext,所以虽然内部有一个线程池,其实调用的过程还是线性的。一个接着一个的执行,相当于单线程。
总结:
asynctask在并发执行多个任务时发生异常。其实还是存在的,在3.0以前的系统中还是会以支持多线程并发的方式执行,支持并发数也是我们上面所计算的128,阻塞队列可以存放10个;也就是同时执行138个任务是没有问题的;而超过138会马上出现java.util.concurrent.rejectedexecutionexception;
而在在3.0以上包括3.0的系统中会为单线程执行(即我们上面代码的分析)
好了,以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对的支持。
上一篇: 主键和外键的作用
下一篇: JavaFx 实现按钮防抖功能
推荐阅读
-
浅析android studio3.5中使用recycleview的包
-
Android中AsyncTask的用法实例分享
-
Android开发中button按钮的使用及动态添加组件方法示例
-
Android 中CheckBox的isChecked的使用实例详解
-
Android多线程处理机制中的Handler使用介绍
-
Android 中 Fragment的使用大全
-
Android中的全局变量与局部变量使用小结
-
在Android Studio中Parcelable插件的简单使用教程
-
Android 中Lambda表达式的使用实例详解
-
android中ProgressDialog与ProgressBar的使用详解