Android-AsyncTask

来源:互联网 发布:新晨科技 知乎 编辑:程序博客网 时间:2024/06/07 22:51

AsyncTask,是Android提供的轻量级的异步类,可以直接继承AsyncTask,在类中实现异步操作,并提供接口反馈当前异步执行的程度(可以通过接口实现UI进度更新),最后反馈执行的结果给UI主线程.

优点:简单,快捷
AsyncTask是封装好的线程池,比起Thread+Handler的方式,AsyncTask在操作UI线程上更方便,因为onPreExecute()、onPostExecute()及更新UI方法onProgressUpdate()均运行在主线程中,这样就不用Handler发消息处理了

Asynctask的不足之处:
1.AsyncTask可能存在新开大量线程消耗系统资源和导致应用FC的风险
2.AsyncTask一旦执行了
doInBackground,就算调用取消方法,也会将
doInBackground里面的代码执行完毕,才会停止。
3.线程池不经维护,当大量异步发生时,导致线程池满了,会出异常。

1.corePoolSize=CPU核心数+1;2.maximumPoolSize=2倍的CPU核心数+1;3.核心线程无超时机制,非核心线程在闲置时间的超时时间为1s;4.任务队列的容量为128

当一个任务通过asynct.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, 0)方法欲添加到线程池时:
①如果此时线程池中的数量小于corePoolSize,即使线程池中的线程都处于空闲状态,也要创建新的线程来处理被添加的任务。
②如果此时线程池中的数量等于 corePoolSize,但是缓冲队列 workQueue未满,那么任务被放入缓冲队列。
③如果此时线程池中的数量大于corePoolSize,缓冲队列workQueue满,并且线程池中的数量小于maximumPoolSize,建新的线程来处理被添加的任务。
④如果此时线程池中的数量大于corePoolSize,缓冲队列workQueue满,并且线程池中的数量等于maximumPoolSize,那么通过 handler所指定的策略来处理此任务。
⑤当线程池中的线程数量大于 corePoolSize时,如果某线程(非核心线程)空闲时间超过keepAliveTime,线程将被终止。这样,线程池可以动态的调整池中的线程数。
也就是:处理任务的优先级为:核心线程corePoolSize、任务队列workQueue、最大线程maximumPoolSize,如果三者都满了,使用handler处理被拒绝的任务(一般为抛出java.util.concurrent.RejectedExecutionException异常)。

AsyncTask首次引入时,异步任务是在一个独立的线程中顺序地执行,也就是说一次只能执行一个任务,不能并行地执行,从1.6开始,AsyncTask中引入了线程池,支持同时执行5个异步任务,也就是说同时只能有5个线程运行,超过的线程只能等待,等待前面的线程某个执行完了才被调度和运行。换句话说,如果一个进程中的AsyncTask实例个数超过5个,那么假如前5个都运行很长时间的话,那么第6个只能等待机会了。这是AsyncTask的一个限制,而且对于2.3以前的版本无法解决。如果你的应用需要大量的后台线程去执行任务,那么你只能放弃使用AsyncTask,自己创建线程池来管理Thread,或者干脆不用线程池直接使用Thread也无妨。不得不说,虽然AsyncTask较Thread使用起来比较方便,但是它最多只能同时运行5个线程,这也大大局限了它的实力,你必须要小心的设计你的应用,错开使用AsyncTask的时间,尽力做到分时,或者保证数量不会大于5个,否则就可能遇到上面提到的问题。
从1.6开始,AsyncTask中引入的线程池:
1、线程池中的工作线程少于5个时,将会创建新的工作线程执行异步任务(红色表示新任务,下同)

2、线程池中已经有5个线程,缓冲队列未满,异步任务将会放到缓冲队列中等待

3、线程池中已经有5个线程,缓冲队列已满,那么线程池将新开工作线程执行异步任务

问题:Android的设备一般不超过2个cpu核心,过多的线程会造成线程间切换频繁,消耗系统资源。

4、线程池中已经有128个线程,缓冲队列已满,如果此时向线程提交任务,将会抛出RejectedExecutionException

问题:抛出的错误不catch的话会导致程序FC。

参考:

AsyncTask优缺点(两种线程池)

AsyncTask是抽象类.AsyncTask定义了三种泛型类型 Params,Progress和Result

Params 启动任务执行的输入参数,比如下载URL
Progress 后台任务执行的百分比,比如下载进度
Result 后台执行任务最终返回的结果,比如下载结果

继承AsyncTask可以实现的函数

onPreExecute()
//此函数是在任务没被线程池执行之前调用 运行在UI线程中 比如现在一个等待下载进度Progress,也可以不用实现

doInBackground(Params… params)
//此函数是在任务被线程池执行时调用 运行在子线程中,在此处理比较耗时的操作 比如执行下载,此函数是抽象函数必须实现

onProgressUpdate(Progress… values)
//此函数是任务在线程池中执行处于Running状态,回调给UI主线程的进度 比如上传或者下载进度,也可以不用实现

onPostExecute(Result result)
//此函数任务在线程池中执行结束了,回调给UI主线程的结果 比如下载结果,也可以不用实现

onCancelled(Result result)/onCancelled()
//此函数表示任务关闭

AsyncTask主要公共函数

cancel (boolean mayInterruptIfRunning)
//尝试取消这个任务的执行,如果这个任务已经结束或者已经取消或者不能被取消或者某些其他原因,那么将导致这个操作失败,当调用此方法时,此方法执行成功并且这个任务还没有执行,那么此任务将不再执行。如果任务已经开始,这时执行此操作传入的参数mayInterruptIfRunning为true,执行此任务的线程将尝试中断该任务

execute (Params… params)//用指定的参数来执行此任务,这个方法将会返回此任务本身,所以调用者可以拥有此任务的引用。此方法必须在UI线程中调用

executeOnExecutor(Executor exec,Params… params)//用指定的参数,运行在指定的线程池中,这个方法将会返回此任务本身,所以调用者可以拥有此任务的引用。此方法必须在UI线程中调用

get ()//等待计算结束并返回结果

get (long timeout, TimeUnit unit)//等待计算结束并返回结果,最长等待时间为:timeOut(超时时间)

getStatus ()//获得任务的当前状态 PENDING(等待执行)、RUNNING(正在运行)、FINISHED(运行完成)

isCancelled ()//如果在任务正常结束之前取消任务成功则返回true,否则返回false

AsyncTask需要注意的地方:

1.)生命周期

关于AsyncTask存在一个这样广泛的误解,很多人认为一个在Activity中的AsyncTask会随着Activity的销毁而销毁。然后事实并非如此。AsyncTask会一直执行doInBackground()方法直到方法执行结束。一旦上述方法结束,会依据情况进行不同的操作。

如果cancel(boolean)调用了,则执行onCancelled(Result)方法
如果cancel(boolean)没有调用,则执行onPostExecute(Result)方法

AsyncTask的cancel方法需要一个布尔值的参数,参数名为mayInterruptIfRunning,意思是如果正在执行是否可以打断,如果这个值设置为true,表示这个任务可以被打断,否则,正在执行的程序会继续执行直到完成。如果在doInBackground()方法中有一个循环操作,我们应该在循环中使用isCancelled()来判断,如果返回为true,我们应该避免执行后续无用的循环操作。

总之,我们使用AsyncTask需要确保AsyncTask正确地取消。

通过cancel( )这个函数并没有真正的终止子线程继续运行,只是舍弃了运行结果。也就是说当我们调用cancel (boolean mayInterruptIfRunning)函数之后,在doInBackground()return后 ,我们将会调用onCancelled(Object) 不在调用onPostExecute(Object)。

如果你调用了AsyncTask的cancel(false),doInBackground()仍然会执行到方法结束,只是不会去调用onPostExecute()方法。但是实际上这是让应用程序执行了没有意义的操作。那么是不是我们调用cancel(true)前面的问题就能解决呢?并非如此。如果mayInterruptIfRunning设置为true,会使任务尽早结束,但是如果的doInBackground()有不可打断的方法会失效,比如这个BitmapFactory.decodeStream() IO操作。但是你可以提前关闭IO流并捕获这样操作抛出的异常。但是这样会使得cancel()方法没有任何意义。

如图:

2.)内存泄漏

如果AsyncTask被声明为Activity的非静态的内部类,那么AsyncTask会保留一个对创建了AsyncTask的Activity的引用。如果Activity已经被销毁,AsyncTask的后台线程还在执行,它将继续在内存里保留这个引用,导致Activity无法被回收,引起内存泄露。

那问题怎么解决呢,有两种办法:第一,在Activity生命周期结束前,去cancel AsyncTask,因为Activity都要销毁了,这个时候再跑线程,绘UI显然已经没什么意义了。第二,如果一定要写成内部类的形式,对context采用WeakRefrence,在使用之前判断是否为空。

3.) 结果丢失

在屏幕旋转等造成Activity重新创建时AsyncTask数据丢失的问题。当Activity销毁并创新创建后,还在运行的AsyncTask会持有一个Activity的非法引用即之前的Activity实例。导致onPostExecute()没有任何作用。

4.)串行并行多版本不一致

new AsyncTask1().execute();new AsyncTask2().execute();

上面的两个任务时同时执行(并行)呢,还是AsyncTask1执行结束之后,AsyncTask2才能执行(串行)呢?实际上是结果依据API不同而不同。

1.6之前为串行,1.6到2.3为并行,3.0之后又改回为串行,但是可以通过executeOnExecutor(Executor)实现并行处理任务。

参考:

android中如何处理耗时操作, asyncTask有什么不足之处

Android线程管理之AsyncTask异步任务

译文:Android中糟糕的AsyncTask

能否同时并发100+asynctask呢?

AsyncTask用的是线程池机制,容量是128,最多同时运行5个core线程,剩下的排队。

FC:force close(强制关闭)

它是一个什么东西?全称force close,就是崩溃了,要强制关闭。

导致出现Force Close的原因有很多,常见的有比如空指针啦,类没有找到啦,资源没找到,就连Android API使用的顺序错误也可能导致(比如 setContentView()之前进行了findViewById()操作)
Force Close有的人说可以用来让应用完全退出 而故意导致这个问题,让程序强制关闭,这种做法我还是不常用。

如何避免弹出Force Close窗口 :可以实现Thread.UncaughtExceptionHandler接口的uncaughtException方法

代码如下:

import java.lang.Thread.UncaughtExceptionHandler;import android.app.Application;public class MyApplication  extends Application implements UncaughtExceptionHandler {    @Override    public voidonCreate() {      // TODOAuto-generated method stub      super.onCreate();    }    @Override    public void  uncaughtException(Thread thread, Throwable ex) {      thread.setDefaultUncaughtExceptionHandler(this);     }}

想要哪个线程可以处理未捕获异常,thread.setDefaultUncaughtExceptionHandler( this); 这句代码都要在那个线程中执行一次。

参考:什么是FC?如何避免FC的发生,另外FC发生时如何捕获相应的uncaught exception?

Asynctask与Handler的比较

AsyncTask实现的原理,和适用的优缺点
AsyncTask,是Android提供的轻量级的异步类,可以直接继承AsyncTask,在类中实现异步操作,并提供接口反馈当前异步执行的程度(可以通过接口实现UI进度更新),最后反馈执行的结果给UI主线程.

使用的优点:
l 简单,快捷
l 过程可控

使用的缺点:
l 在使用多个异步操作和并需要进行Ui变更时,就变得复杂起来.
2 Handler异步实现的原理和适用的优缺点

在Handler 异步实现时,涉及到 Handler, Looper, Message,Thread四个对象,实现异步的流程是主线程启动Thread(子线程)àthread(子线程)运行并生成Message- àLooper获取Message并传递给HandleràHandler逐个获取Looper中的Message,并进行UI变更。

使用的优点:
l 结构清晰,功能定义明确
l 对于多个后台任务时,简单,清晰

子线程更新UI 的方法:

1)、handler和message机制:通过显示的抛出、捕获消息与ui进行交互;

2)、Activity.runOnUiThread(Runnable):如果当前线程为ui线程,则立即执行;否则,将参数中的线程操作放入到ui线程的事件队列中,等待执行。

3)、View.post(Runnable):将操作放入到message队列中,如果放入成功,该操作将会在ui线程中执行,并返回true,否则返回false

4)、View.postDelayed(Runnable, long)跟第三条基本一样,只不过添加了一个延迟时间。

5)、android1.5以后为我们提供了一个工具类来搞定这个问题AsyncTask.

原创粉丝点击