深入了解Android中的AsyncTask
asynctask,即异步任务,是android给我们提供的一个处理异步任务的类。通过此类,可以实现ui线程和后台线程进行通讯,后台线程执行异步任务,并把结果返回给ui线程。 我们知道,android中只有ui线程,也就是主线程才能进行对ui的更新操作,而其他线程是不能直接操作ui的.这样的好处是保证了ui的稳定性和准确性,避免多个线程同时对ui进行操作而造成ui的混乱。 但android是一个多线程的操作系统,我们总不能把所有的任务都放在主线程中进行实现,比如网络操作,文件读取等耗时操作,如果全部放到主线程去执行,就可能会造成后面任务的阻塞。android会去检测这种阻塞,当阻塞时间太长的时候,就会抛出application not responsed(anr)错误.所以我们需要将这些耗时操作放在非主线程中去执行.这样既避免了android的单线程模型,又避免了anr。 虽说现在做网络请求有了volley全家桶和okhttp这样好用的库,但是在处理其他后台任务以及与ui交互上,还是需要用到asynctask。任何一个用户量上千万的产品绝对不会在代码里面使用系统原生的asyntask,因为它蛋疼的兼容性以及极高的崩溃率实在让人不敢恭维。 asynctask到底是什么呢?很简单,它不过是对线程池和handler的封装;用线程池来处理后台任务,用handler来处理与ui的交互。线程池使用的是executor接口,我们先了解一下线程池的特性。
jdk5带来的一大改进就是java的并发能力,它提供了三种并发武器:并发框架executor,并发集合类型如concurrenthashmap,并发控制类如countdownlatch等;尽量使用exector而不是直接用thread类进行并发编程。
asynctask内部也使用了线程池处理并发;线程池通过threadpoolexector类构造,这个构造函数参数比较多,它允许开发者对线程池进行定制,我们先看看这每个参数是什么意思,然后看看android是以何种方式定制的。
threadpoolexecutor的其他构造函数最终都会调用如下的构造函数完成对象创建工作:
public threadpoolexecutor(int corepoolsize, int maximumpoolsize, long keepalivetime, timeunit unit, blockingqueue<runnable> workqueue, threadfactory threadfactory, rejectedexecutionhandler handler);
corepoolsize: 核心线程数目,即使线程池没有任务,核心线程也不会终止(除非设置了allowcorethreadtimeout参数)可以理解为“常驻线程”
maximumpoolsize: 线程池中允许的最大线程数目;一般来说,线程越多,线程调度开销越大;因此一般都有这个限制。
keepalivetime: 当线程池中的线程数目比核心线程多的时候,如果超过这个keepalivetime的时间,多余的线程会被回收;这些与核心线程相对的线程通常被称为缓存线程
unit: keepalivetime的时间单位
workqueue: 任务执行前保存任务的队列;这个队列仅保存由execute提交的runnable任务
threadfactory: 用来构造线程池的工厂;一般都是使用默认的;
handler: 当线程池由于线程数目和队列限制而导致后续任务阻塞的时候,线程池的处理方式。
如果线程池中线程的数目少于corepoolsize,就算线程池中有其他的没事做的核心线程,线程池还是会重新创建一个核心线程;直到核心线程数目到达corepoolsize(常驻线程就位)。如果线程池中线程的数目大于或者等于corepoolsize,但是工作队列workqueue没有满,那么新的任务会放在队列workqueue中,按照fifo的原则依次等待执行。当有核心线程处理完任务空闲出来后,会检查这个工作队列然后取出任务默默执行去,如果线程池中线程数目大于等于corepoolsize,并且工作队列workqueue满了,但是总线程数目小于maximumpoolsize,那么直接创建一个线程处理被添加的任务。如果工作队列满了,并且线程池中线程的数目到达了最大数目maximumpoolsize,那么就会用最后一个构造参数handler处理;**默认的处理方式是直接丢掉任务,然后抛出一个异常。总结起来,也即是说,当有新的任务要处理时,先看线程池中的线程数量是否大于 corepoolsize,再看缓冲队列 workqueue 是否满,最后看线程池中的线程数量是否大于 maximumpoolsize。另外,当线程池中的线程数量大于 corepoolsize 时,如果里面有线程的空闲时间超过了 keepalivetime,就将其移除线程池,这样,可以动态地调整线程池中线程的数量。
asynctask里面有“两个”线程池;一个thread_pool_executor一个serial_executor;之所以打引号,是因为其实serial_executor也使用thread_pool_executor实现的,只不过加了一个队列弄成了串行而已。asynctask里面线程池是一个核心线程数为cpu + 1,最大线程数为cpu * 2 + 1,工作队列长度为128的线程池;并且没有传递handler参数,那么使用的就是默认的handler(拒绝执行)。如果任务过多,那么超过了工作队列以及线程数目的限制导致这个线程池发生阻塞,那么悲剧发生,默认的处理方式会直接抛出一个异常导致进程挂掉。假设你自己写一个异步图片加载的框架,然后用asynctask实现的话,当你快速滑动listview的时候很容易发生这种异常;这也是为什么各大imageloader都是自己写线程池和handlder的原因。这个线程池是一个静态变量;那么在同一个进程之内,所有地方使用到的asynctask默认构造函数构造出来的asynctask都使用的是同一个线程池,如果app模块比较多并且不加控制的话,很容易满足第一条的崩溃条件;如果你不幸在不同的asynctask的doinbackgroud里面访问了共享资源,那么就会发生各种并发编程问题。
在asynctask全部执行完毕之后,进程中还是会常驻corepoolsize个线程;在android 4.4 (api 19)以下,这个corepoolsize是hardcode的,数值是5;api 19改成了cpu + 1;也就是说,在android 4.4以前;如果你执行了超过五个asynctask;然后啥也不干了,进程中还是会有5个asynctask线程。
asynctask里面的handler很简单,如下(api 22代码):
private static final internalhandler shandler = new internalhandler(); public internalhandler() { super(looper.getmainlooper()); }
注意,这里直接用的主线程的looper;如果去看api 22以下的代码,会发现它没有这个构造函数,而是使用默认的;默认情况下,handler会使用当前线程的looper,如果你的asynctask是在子线程创建的,那么很不幸,你的onpreexecute和onpostexecute并非在ui线程执行,而是被handler post到创建它的那个线程执行;如果你在这两个线程更新了ui,那么直接导致崩溃。这也是大家口口相传的asynctask必须在主线程创建的原因。另外,asynctask里面的这个handler是一个静态变量,也就是说它是在类加载的时候创建的;如果在你的app进程里面,以前从来没有使用过asynctask,然后在子线程使用asynctask的相关变量,那么导致静态handler初始化,如果在api 16以下,那么会出现上面同样的问题;这就是asynctask必须在主线程初始化 的原因。事实上,在android 4.1(api 16)以后,在app主线程activitythread的main函数里面,直接调用了ascyntask.init函数确保这个类是在主线程初始化的;另外,init这个函数里面获取了internalhandler的looper,由于是在主线程执行的,因此,asynctask的handler用的也是主线程的looper。这个问题从而得到彻底的解决。
asynctask的使用较为简单,只需要关注三个参数和四个方法即可。
asynctask<params,progress,result>是一个抽象类,通常用于被继承.继承asynctask需要指定如下三个泛型参数:
- params:启动任务时输入的参数类型.
- progress:后台任务执行中返回进度值的类型.
- result:后台任务执行完成后返 回结果的类型.
asynctask主要有如下几个方法:
- doinbackground:必须重写,异步执行后台线程要完成的任务,耗时操作将在此方法中完成.
- onpreexecute:执行后台耗时操作前被调用,通常用于进行初始化操作.
- onpostexecute:当doinbackground方法完成后,系统将自动调用此方法,并将doinbackground方法返回的值传入此方法.通过此方法进行ui的更新.
- onprogressupdate:当在doinbackground方法中调用publishprogress方法更新任务执行进度后,将调用此方法.通过此方法我们可以知晓任务的完成进度.
下面通过代码演示一个典型的异步处理的实例--加载网络图片.网络操作作为一个不稳定的耗时操作,从4.0开始就被严禁放入主线程中.所以在显示一张网络图片时,我们需要在异步处理中下载图片,并在ui线程中设置图片。
mainactivity.java
import android.app.activity; import android.content.intent; import android.os.bundle; import android.view.view; import android.view.view.onclicklistener; import android.widget.button; public class mainactivity extends activity { private button btn_image; @override protected void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.activity_main); btn_image = (button) findviewbyid(r.id.btn_image); btn_image.setonclicklistener(new onclicklistener() { @override public void onclick(view v) { startactivity(new intent(mainactivity.this,imageactivity.class)); } }); } }
imageactivity.java
import android.app.activity; import android.graphics.*; import android.os.*; import android.view.view; import android.widget.*; import java.io.*; import java.net.*; public class imageactivity extends activity { private imageview imageview ; private progressbar progressbar ; private static string url = "http://tupian.baike.com/a2_50_64_01300000432220134623642199335_jpg.html?prd=so_tupian"; @override protected void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.image); imageview = (imageview) findviewbyid(r.id.image); progressbar = (progressbar) findviewbyid(r.id.progressbar); //通过调用execute方法开始处理异步任务.相当于线程中的start方法. new myasynctask().execute(url); } class myasynctask extends asynctask<string,void,bitmap> { //onpreexecute用于异步处理前的操作 @override protected void onpreexecute() { super.onpreexecute(); //此处将progressbar设置为可见. progressbar.setvisibility(view.visible); } //在doinbackground方法中进行异步任务的处理. @override protected bitmap doinbackground(string... params) { //获取传进来的参数 string url = params[0]; bitmap bitmap = null; urlconnection connection ; inputstream is ; try { connection = new url(url).openconnection(); is = connection.getinputstream(); //为了更清楚的看到加载图片的等待操作,将线程休眠3秒钟. thread.sleep(3000); bufferedinputstream bis = new bufferedinputstream(is); //通过decodestream方法解析输入流 bitmap = bitmapfactory.decodestream(bis); is.close(); bis.close(); } catch (ioexception e) { e.printstacktrace(); } catch (interruptedexception e) { e.printstacktrace(); } return bitmap; } //onpostexecute用于ui的更新.此方法的参数为doinbackground方法返回的值. @override protected void onpostexecute(bitmap bitmap) { super.onpostexecute(bitmap); //隐藏progressbar progressbar.setvisibility(view.gone); //更新imageview imageview.setimagebitmap(bitmap); } } }
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,同时也希望多多支持!
上一篇: Android避免内存溢出(Out of Memory)方法汇总
下一篇: HCIP之交换部分(二)