AysnTask详解
一、AsynTask简介
AysnTask是android的轻异步操作,和handler一样是后台处理耗时操作,并且更新UI的一种方式。它的函数原型:
public final AsyncTask<Params, Progress, Result> execute(Params... params)
它的三个泛型参数分别代表:传入的参数、更新UI的参数、最后在主线程运行的参数的类型。这些参数当用不到时候可以用java.lang.Void代替,下面详细介绍一下参数:
AysnTask类主要覆写下面几个函数:
这些函数的运行顺序、参数、作用是:
onPreExecute():无参数,它是第一个运行的函数,是为后台运行做准备的,一般用来对UI做一些标记或者回去Ui的参数,如获取progressBar的max;它运行在主线程中。
doInBackground(Params ... params ) :它的参数为定义AsyncTask时传入的第一个参数类型,也是execute传入的参数的类型。返回的类型是定义AsyncTask时传入的第三个参数类型,即是后台运行结果传入到主线程中的数据,是onPostExecute方法的参数。该方法运行在后台进程中。
onProgressUpdate(progerss...progress):它的参数是定义AsyncTask时传入的第二个参数类型;它是用来更新UI的函数,运行在主线程中。想运行次函数必须在doInbackground()函数里调用publishProgress()函数,该方法里调用了此函数。
onPostExecute(Result... result):它的参数是doInBackground返回的值。运行在主线程中,可以修改UI。
onCancelled(result:Result) 它的参数是doInBackground,当在主程序中调用asynTask.cancel(true)时,会调用此函数,此函数将停止后台进程doInBackground()的执行,并取代onPostExecute,而执行此函数和oncancelled(),此方法和execute()一样只能执行一次,虽然在主线程中但是在此修改UI是无效的。比如可以在下载时候调用这个函数来执行取消取消下载后的操作。
oncancelled:执行完onCancelled(result:Result) 后就紧接着执行,释放掉asynTask自己。在函数里,它会执行Tread的interrupt方法结束后台线程。注意,这个inteerrupt方法只有在线程运行的时候才可以终止,当处于sleep或者wait的时候就会曝InterruptedException异常,即后台的线程未被停止。读者想了解更多可以参考:http://blog.csdn.net/srzhz/article/details/6804756
调用AsynTask的函数是execute() ,只能执行一次,且当activity被销毁时候,再创建(比如横竖屏切换)时候就会新建activity 执行execute(),你会发现新的execute()会等到之前的执行完了执行,而之前的acitivity被销毁了,若有对UI的操作就会出错,原因在下一节AsynTask使用注意事项中讲到。
取消释放AysnTask的方法是:cancel(true),也只能执行一次。
下面给出一个下载的demo,其中执行的过程未实现只是更新progressBar:
public class MainActivity extends AppCompatActivity { private TextView text; private ProgressBar progressBar; private MyAsynTask task; boolean isDownload = true; private Button bt,bt2; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); text = (TextView) findViewById(R.id.text); bt = (Button) findViewById(R.id.bt); bt2 = (Button) findViewById(R.id.bt2); progressBar = (ProgressBar) findViewById(R.id.progressBar); task = new MyAsynTask(); task.execute(); } public void pause(View view) { //起初把暂停的代码放入cancle中,发现cancle只能执行一次,就每次执行时创建new task发现可以,且task的progress没有初始化为0. MyAsynTask task = new MyAsynTask(); task.cancel(true); } public void cancel(View view) { task.cancel(true); text.setText("down load has been canceled !"); bt2.setEnabled(false); bt.setEnabled(false); } class MyAsynTask extends AsyncTask<String , Integer , String> { int progress = 0; int maxProgress; @Override protected void onPreExecute() { System.out.println("onPreExecute"); maxProgress = progressBar.getMax(); isDownload = true; super.onPreExecute(); } @Override protected String doInBackground(String... params) { System.out.println("doInBackground"); while(progress<100) { if (isDownload) { try { Thread.sleep(1000); progress++; } catch (InterruptedException e) { e.printStackTrace(); } publishProgress(); } else { synchronized (MyAsynTask.class) { try { MyAsynTask.class.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } } } return null; } //运行在主线程中 @Override protected void onProgressUpdate(Integer... values) { System.out.println("onProgressUpdate"); text.setText("Has download "+ progress +"%"); progressBar.setProgress(progress); super.onProgressUpdate(values); } @Override protected void onCancelled(String s) { System.out.println("onCancelled string "+ isDownload); //这里设置UI无效 // text.setText("down load has been canceled !"); // bt2.setEnabled(false); // bt.setEnabled(false); //不要放在这放在点击函数里 // if(isDownload) // { // isDownload = false; // bt.setText("start"); // } // else // { // synchronized (MyAsynTask.class) // { // isDownload = true; // bt.setText("pause"); // MyAsynTask.class.notifyAll(); // } // } super.onCancelled(s); } @Override protected void onCancelled() { System.out.println("onCancelled"); } //运行在主线程中 @Override protected void onPostExecute(String s) { super.onPostExecute(s); } }
从实验中发现,当调用了pause(View view)方法中调用cancel(true)后,重新new MyAsynTask时,任然后可以继续下载,且原来task的progress未被重置为0。分析原因:
1、当我第一次调用cancel的时候是新建的asyntask占用了第一次创建的锁从而挂起了下载进程doBackground,再释放了自己。
2、当我第二次点击时候,创建了新的asyntask,但是isDownload为false直接挂起了来到cancle里将之前的挂起的下载进程唤醒了,新建的asyntask任然在wait(),然而又cancel释放掉了自己。
这样不断创建对象释放自己会影响程序的性能。
二、AsynTask使用注意事项
1、导致内存泄漏
AsynTask并非随activity的销毁而销毁,activity被销毁后,AsyncTask会一直执行, 直到doInBackground()方法执行完毕。即使销毁activity的时候调用了cancel方法,后台进程由于阻塞任然未停止(上面讲oncancel时候讲到过)。它会一直保留着创建了AsyncTask的Activity的引用,即使activity被销毁了,其内存任然无法回收最终导致内存泄漏。
2、运行出错
同上面的一样,activity销毁了,而doInBackground()执行完了后执行postExecute时候或者updateProgress跟新UI的时候activity已经不存在了程序报错,从而runtimeException。(若只是activity重建了比如横竖屏切换不会崩溃,只是更新UI失效)
3、更新界面延迟
屏幕旋转或Activity在后台被系统杀掉等情况会导致Activity的重新创建,之前运行的AsyncTask会持有一个之前Activity的引用,跟新UI时候你会发现必须等到之前的doBackground方法执行完毕后才开始执行重新创建的activity里的asynTask的dobackground方法,再执行UI的更新,原因看源码:
通过源码发现,调用excute()执行后台操作时候有2种执行方法,即对应的2种执行器:ThreadPoolExecutor 和 SerialExecutor即串行和并行2种执行器,意思大家通过字面可以理解了。而通过
private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;
可以看出穿行的是默认的执行器。下面外面分析下串行执行器:
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); } } }
我们可以发现
THREAD_POOL_EXECUTOR.execute(mActive);
里面是用了个同步锁(锁是执行器),执行器内部有个线程工作队列,sPoolWorkQueue当我们调用excute时候就会把doInBackground加载到队列里,只有前面的进程执行完了才会释放执行器,因此外面之前创建的asyntask还未执行完因此会继续执行完了外面的新加载的activity里的AsynTask才开始跟新。当然可以用并行执行器来执行。
task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
4、并行、串行的选择
在Android 1.6之前的版本,AsyncTask是串行的,在1.6至2.3的版本,改成了并行的。在2.3之后的版本又做了修改,可以支持并行和串行,当想要串行执行时,直接执行execute()方法,如果需要并行执行,则要执行executeOnExecutor(Executor)。当然选择并行执行率高,但对与线程就不好管理容易浪费资源。