1、 AsyncTask和Handler的优缺点比较:
1)AsyncTask实现的原理和适用的优缺点
AsyncTask是Android提供的轻量级的异步类,可以直接继承AsyncTask(AsyncTask是抽象类),在类中实现异步操作,并提供接口反馈当前异步执行的程度(可以通过接口实现UI进度更新),最后反馈执行的结果给UI主线程.
AsyncTask使用的优点:
l 简单,快捷
ll 过程可控
AsyncTask使用的缺点:
l 在使用多个异步操作和并需要进行Ui变更时,就变得复杂起来.
2)Handler异步实现的原理和适用的优缺点
在Handler 异步实现时,涉及到 Handler, Looper, Message,Thread四个对象,实现异步的流程是主线程启动Thread(子线程),thread(子线程)运行并生成Message-Looper获取Message并传递给Handler,Handler逐个获取Looper中的Message,并进行UI变更。
Handler使用的优点:
l 结构清晰,功能定义明确
ll 对于多个后台任务时,简单,清晰
Handler使用的缺点:
l 在单个后台异步处理时,显得代码过多,结构过于复杂(相对性)
2、AsyncTask介绍
Android的AsyncTask比Handler更轻量级一些,适用于简单的异步处理。
首先明确Android之所以有Handler和AsyncTask,都是为了不阻塞主线程(UI线程),且UI的更新只能在主线程中完成,因此异步处理是不可避免的。
AsyncTask其本质:是一个由5个核心线程组成的,最大队列数为128的线程池。我们在使用的过程中,通常会重写doInBackground(Params…) 方法,比较耗时的操作都可以放在这里。这个方法在子线程,是不能直接操作UI的。此方法在后台线程执行,完成任务的主要工作,通常需要较长的时间。在执行过程中可以调用onPostExecute(Result)方法,相当于Handler 处理UI的方式,在这里面可以使用在doInBackground 得到的结果处理操作UI。 此方法在主线程执行,任务执行的结果作为此方法的参数返回。
AsyncTask定义了三种泛型类型 Params,Progress和Result。
- Params 启动任务执行的输入参数,比如HTTP请求的URL。
- Progress 后台任务执行的百分比。
- Result 后台执行任务最终返回的结果,比如String。
使用过AsyncTask 的同学都知道一个异步加载数据最少要重写以下这两个方法:
- doInBackground(Params… params) 后台线程池中执行,比较耗时的操作都可以放在这里。注意这里不能直接操作UI。此方法在后台线程执行,完成任务的主要工作,通常需要较长的时间。在执行过程中可以调用publicProgress(Progress…)来更新任务的进度,publishProgress方法会调用onProgressUpdate方法。另外此方法需要返回计算结果给onPostExecute方法。
- onPostExecute(Result result) 相当于Handler 处理UI的方式,在这里面可以使用在doInBackground 得到的结果处理操作UI。 此方法在主线程执行,异步任务执行结束之后,此方法会被调用,其中result参数是后台任务的返回值,即doInBackground的返回值。
有必要的话你还得重写以下这三个方法,但不是必须的:
- onProgressUpdate(Progress… values) 可以使用进度条增加用户体验度。 此方法在主线程执行,用于显示任务执行的进度,当后台任务的执行进度发生改变时,此方法会被调用。
- onPreExecute() 这里是最终用户调用Excute时的接口,在主线程中执行,当任务执行之前开始调用此方法,可以在这里显示进度对话框。
- onCancelled() 用户调用取消时,要做的操作
上面几个方法中,onPreExecute 先执行,接着是doInBackground,最后才是onPostExecute。
执行顺序 :onPreExecute –> doInBackground–> onPostExecute
特别的:doInBackground(publishProgress –> onProgressUpdate)
AsyncTask还提供了onCallcelled方法,同样onCallcelled方法在主线程中执行,当异步任务被取消时,onCallcelled方法会被调用,这个时候onPostExecute则不会被调用.
使用AsyncTask类,以下是几条必须遵守的准则:
- AsyncTask的类必须在主线程中加载;
- AsyncTask的实例必须在UI thread中创建;
- execute方法必须在UI thread中调用;
- 不要手动的调用onPreExecute(), onPostExecute(Result),doInBackground(Params…), onProgressUpdate(Progress…)这几个方法;
- 该task只能被执行一次,否则多次调用时将会出现异常;
一个超简单的理解 AsyncTask 的例子:
main.xml:
1 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 2 xmlns:tools="http://schemas.android.com/tools" 3 android:layout_width="match_parent" 4 android:layout_height="match_parent" 5 android:orientation="vertical" > 6 7 <Button 8 android:id="@+id/startButton" 9 android:layout_width="fill_parent" 10 android:layout_height="wrap_content" 11 android:text="开始异步任务" /> 12 13 <Button 14 android:id="@+id/cancelButton" 15 android:layout_width="fill_parent" 16 android:layout_height="wrap_content" 17 android:text="取消异步任务" /> 18 19 <ProgressBar 20 android:id="@+id/progressBar" 21 style="?android:attr/progressBarStyleHorizontal" 22 android:layout_width="fill_parent" 23 android:layout_height="wrap_content" 24 android:max="100" 25 android:progress="0" /> 26 27 <ScrollView 28 android:id="@+id/scrollView" 29 android:layout_width="fill_parent" 30 android:layout_height="wrap_content" > 31 32 <TextView 33 android:id="@+id/textView" 34 android:layout_width="fill_parent" 35 android:layout_height="wrap_content" 36 android:text="test test" /> 37 </ScrollView> 38 39 </LinearLayout>
MainActivity.java,如下:
1 package com.example.asynctasktest; 2 import java.io.ByteArrayOutputStream; 3 import java.io.InputStream; 4 import org.apache.http.HttpEntity; 5 import org.apache.http.HttpResponse; 6 import org.apache.http.HttpStatus; 7 import org.apache.http.client.HttpClient; 8 import org.apache.http.client.methods.HttpGet; 9 import org.apache.http.impl.client.DefaultHttpClient; 10 import android.app.Activity; 11 import android.os.AsyncTask; 12 import android.os.Bundle; 13 import android.view.View; 14 import android.view.View.OnClickListener; 15 import android.widget.Button; 16 import android.widget.ProgressBar; 17 import android.widget.TextView; 18 import android.widget.Toast; 19 public class MainActivity extends Activity { 20 private Button satrtButton; 21 private Button cancelButton; 22 private ProgressBar progressBar; 23 private TextView textView; 24 private DownLoaderAsyncTask downLoaderAsyncTask; 25 @Override 26 public void onCreate(Bundle savedInstanceState) { 27 super.onCreate(savedInstanceState); 28 setContentView(R.layout.main); 29 initView(); 30 } 31 public void initView() { 32 satrtButton=(Button) findViewById(R.id.startButton); 33 cancelButton=(Button) findViewById(R.id.cancelButton); 34 satrtButton.setOnClickListener(new ButtonOnClickListener()); 35 cancelButton.setOnClickListener(new ButtonOnClickListener()); 36 progressBar=(ProgressBar) findViewById(R.id.progressBar); 37 textView=(TextView) findViewById(R.id.textView); 38 } 39 private class ButtonOnClickListener implements OnClickListener{ 40 public void onClick(View v) { 41 switch (v.getId()) { 42 case R.id.startButton: 43 //注意: 44 //1 每次需new一个实例,新建的任务只能执行一次,否则会出现异常 45 //2 异步任务的实例必须在UI线程中创建 46 //3 execute()方法必须在UI线程中调用。 47 downLoaderAsyncTask=new DownLoaderAsyncTask(); 48 downLoaderAsyncTask.execute("http://www.baidu.com"); 49 break; 50 case R.id.cancelButton: 51 //取消一个正在执行的任务,onCancelled()方法将会被调用 52 downLoaderAsyncTask.cancel(true); 53 break; 54 default: 55 break; 56 } 57 } 58 59 } 60 //构造函数AsyncTask<Params, Progress, Result>参数说明: 61 //Params 启动任务执行的输入参数 62 //Progress 后台任务执行的进度 63 //Result 后台计算结果的类型 64 private class DownLoaderAsyncTask extends AsyncTask<String, Integer, String>{ 65 //onPreExecute()方法用于在执行异步任务前,主线程做一些准备工作 66 @Override 67 protected void onPreExecute() { 68 super.onPreExecute(); 69 textView.setText("调用onPreExecute()方法--->准备开始执行异步任务"); 70 System.out.println("调用onPreExecute()方法--->准备开始执行异步任务"); 71 } 72 73 //doInBackground()方法用于在执行异步任务,不可以更改主线程中UI 74 @Override 75 protected String doInBackground(String... params) { 76 System.out.println("调用doInBackground()方法--->开始执行异步任务"); 77 try { 78 HttpClient client = new DefaultHttpClient(); 79 HttpGet get = new HttpGet(params[0]); 80 HttpResponse response = client.execute(get); 81 if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) { 82 HttpEntity entity = response.getEntity(); 83 InputStream is = entity.getContent(); 84 long total = entity.getContentLength(); 85 ByteArrayOutputStream bos = new ByteArrayOutputStream(); 86 byte[] buffer = new byte[1024]; 87 int count = 0; 88 int length = -1; 89 while ((length = is.read(buffer)) != -1) { 90 bos.write(buffer, 0, length); 91 count += length; 92 //publishProgress()为AsyncTask类中的方法 93 //常在doInBackground()中调用此方法 94 //用于通知主线程,后台任务的执行情况. 95 //此时会触发AsyncTask中的onProgressUpdate()方法 96 publishProgress((int) ((count / (float) total) * 100)); 97 //为了演示进度,休眠1000毫秒 98 Thread.sleep(1000); 99 } 100 return new String(bos.toByteArray(), "UTF-8"); 101 } 102 } catch (Exception e) { 103 return null; 104 } 105 return null; 106 } 107 108 //onPostExecute()方法用于异步任务执行完成后,在主线程中执行的操作 109 @Override 110 protected void onPostExecute(String result) { 111 super.onPostExecute(result); 112 Toast.makeText(getApplicationContext(), "调用onPostExecute()方法--->异步任务执行完毕", 0).show(); 113 //textView显示网络请求结果 114 textView.setText(result); 115 System.out.println("调用onPostExecute()方法--->异步任务执行完毕"); 116 } 117 118 //onProgressUpdate()方法用于更新异步执行中,在主线程中处理异步任务的执行信息 119 @Override 120 protected void onProgressUpdate(Integer... values) { 121 super.onProgressUpdate(values); 122 //更改进度条 123 progressBar.setProgress(values[0]); 124 //更改TextView 125 textView.setText("已经加载"+values[0]+"%"); 126 } 127 128 //onCancelled()方法用于异步任务被取消时,在主线程中执行相关的操作 129 @Override 130 protected void onCancelled() { 131 super.onCancelled(); 132 //更改进度条进度为0 133 progressBar.setProgress(0); 134 //更改TextView 135 textView.setText("调用onCancelled()方法--->异步任务被取消"); 136 System.out.println("调用onCancelled()方法--->异步任务被取消"); 137 } 138 } 139 }
3、AsyncTask的工作原理(通过查看源码)
参见《Android开发艺术探索》:P395 ~ P402
4、
深入解析AsyncTask与Thread的区别
(1)AsyncTask的内幕
AsyncTask主要有二个部分:一个是与主线程的交互,另一个就是线程的管理调度。虽然可能多个AsyncTask的子类的实例,但是AsyncTask的内部Handler和ThreadPoolExecutor都是进程范围内共享的,其都是static的,也即属于类的,类的属性的作用范围是CLASSPATH,因为一个进程一个VM,所以是AsyncTask控制着进程范围内所有的子类实例。
- 与主线程交互
与主线程交互是通过Handler来进行的,因为本文主要探讨AsyncTask在任务调度方面的,所以对于这部分不做细致介绍,感兴趣的朋友可以去看AsyncTask的源码 。
- 线程任务的调度
内部会创建一个进程作用域的线程池来管理要运行的任务,也就就是说当你调用了AsyncTask#execute()后,AsyncTask会把任务交给线程池,由线程池来管理创建Thread和运行Therad。对于内部的线程池不同版本的Android的实现方式是不一样的:
1)Android 3.0 以前的版本,也即SDK/API 11和以前的版本
内部的线程池限制是5个,也就是说同时只能有5个线程运行
,超过的线程只能等待,等待前面的线程某个执行完了才被调度和运行。换句话说,如果一个进程中的AsyncTask实例个数超过5个,那么假如前5个都运行很长时间的话,那么第6个只能等待机会了。这是AsyncTask的一个限制,而且对于3.0以前的版本无法解决。如果你的应用需要大量的后台线程去执行任务,那么你只能放弃使用AsyncTask,自己创建线程池来管理Thread,或者干脆不用线程池直接使用Thread也无妨。不得不说,虽然AsyncTask较Thread使用起来比较方便,但是它最多只能同时运行5个线程,这也大大局限了它的实力,你必须要小心的设计你的应用,错开使用AsyncTask的时间,尽力做到分时,或者保证数量不会大于5个,否则就可能遇到上面提到的问题。要不然就只能使用JavaSE中的API
了。
2)Android 3.0 以后,也即SDK/API 11和 以后的版本
可能是Google意识到了AsyncTask的局限性了,从Android 3.0开始对AsyncTask的API做出了一些调整:#execute()提交的任务,按先后顺序每次只运行一个也就是说它是按提交的次序,每次只启动一个线程执行一个任务,完成之后再执行第二个任务,也就是相当于只有一个后台线程在执行所提交的任务 。
新增了接口#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()是一样的。
(2)
AsyncTask的使用注意事项
曾建议使用AsyncTask而不是使用Thread,但是AsyncTask似乎又有它的限制,这就要根据具体的需求情况而选择合适的工具,下面是一些建议:
- 改善你的设计,少用异步处理
线程的开销是非常大的,同时异步处理也容易出错,难调试,难维护,所以改善你的设计,尽可能的少用异步。对于一般性的数据库查询,少量的I/O操作是没有必要启动线程的。
- 与主线程有交互时用AsyncTask,否则就用Thread
AsyncTask被设计出来的目的就是为了满足Android的特殊需求:非主线程不能操作(UI)组件,所以AsyncTask扩展Thread增强了与主线程的交互的能力。如果你的应用没有与主线程交互,那么就直接使用Thread就好了。
- 当有需要大量线程执行任务时,一定要创建线程池
线程的开销是非常大的,特别是创建一个新线程,否则就不必设计线程池之类的工具了。当需要大量线程执行任务时,一定要创建线程池,无论是使用AsyncTask还是Thread,因为使用AsyncTask它内部的线程池有数量限制,可能无法满足需求;使用Thread更是要线程池来管理,避免虚拟机创建大量的线程。比如从网络上批量下载图片,你不想一个一个的下,或者5个5个的下载,那么就创建一个CorePoolSize为10或者20的线程池,每次10个或者20个这样的下载,即满足了速度,又不至于耗费无用的性能开销去无限制的创建线程。
- 对于想要立即开始执行的异步任务,要么直接使用Thread,要么单独创建线程池提供给AsyncTask
默认的AsyncTask不一定会立即执行你的任务,除非你提供给他一个单独的线程池。如果不与主线程交互,直接创建一个Thread就可以了,虽然创建线程开销比较大,但如果这不是批量操作就没有问题。
- Android的开发没有想像中那样简单,要多花心思和时间在代码上和测试上面,以确信程序是优质的。
5. 坑爹的AsyncTask内存泄漏:
AsyncTask底层虽然是封装了线程和handler,但是不可避免的出现了内存泄露的问题。
(1)AsyncTask的内存泄漏:
private TextView mTextview; new AsyncTask<...> { @Override protected void onPostExecute(Objecto) { mTextview.setText("text"); } }.execute();
乍一看好像没什么问题,但这段代码会导致内存泄露,线程有可能会超出当前Activity的生命周期之后仍然在run,因为这个时候线程已经不受控制了。Activity生命周期已经结束,需要被系统回收掉,但是AsyncTask还在持有TextView的引用,这样就导致了内存泄露。
既然你说上面的代码有问题,那我们把上面的代码改一改,如下:
private TextView mTextview; new AsyncTask<...> { @Override protected void onPostExecute(Objecto) { //mTextview.setText("text"); } }.execute();
我直接注释掉,不做UI操作了,这样总不会有问题了吧。真的吗?
仔细看,这里是个内部类,由于Java内部类的特点,AsyncTask内部类会持有外部类的隐式引用。即使从代码上看我在AsyncTask里没有持有外部的任何引用,但是写在Activity里,对context仍然会有个强引用,这样如果线程超过Activity生命周期,Activity还是无法回收造成内存泄露。
(2)AsyncTask内存泄漏的解决方法:
在Activity生命周期结束前,去cancel AsyncTask,因为Activity都要销毁了,这个时候再跑线程,绘UI显然已经没什么意义了。
也就是在Activity的onDestory方法中调用AsyncTask.cancel(true)