Android(java)学习笔记92:Android线程形态之 AsyncTask (异步任务)
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 在单个后台异步处理时,显得代码过多,结构过于复杂(相对性)
- Params 启动任务执行的输入参数,比如HTTP请求的URL。
- Progress 后台任务执行的百分比。
- Result 后台执行任务最终返回的结果,比如String。
- doInBackground(Params… params) 后台线程池中执行,比较耗时的操作都可以放在这里。注意这里不能直接操作UI。此方法在后台线程执行,完成任务的主要工作,通常需要较长的时间。在执行过程中可以调用publicProgress(Progress…)来更新任务的进度,publishProgress方法会调用onProgressUpdate方法。另外此方法需要返回计算结果给onPostExecute方法。
- onPostExecute(Result result) 相当于Handler 处理UI的方式,在这里面可以使用在doInBackground 得到的结果处理操作UI。 此方法在主线程执行,异步任务执行结束之后,此方法会被调用,其中result参数是后台任务的返回值,即doInBackground的返回值。
- onProgressUpdate(Progress… values) 可以使用进度条增加用户体验度。 此方法在主线程执行,用于显示任务执行的进度,当后台任务的执行进度发生改变时,此方法会被调用。
- onPreExecute() 这里是最终用户调用Excute时的接口,在主线程中执行,当任务执行之前开始调用此方法,可以在这里显示进度对话框。
- onCancelled() 用户调用取消时,要做的操作
- AsyncTask的类必须在主线程中加载;
- AsyncTask的实例必须在UI thread中创建;
- execute方法必须在UI thread中调用;
- 不要手动的调用onPreExecute(), onPostExecute(Result),doInBackground(Params...), onProgressUpdate(Progress...)这几个方法;
- 该task只能被执行一次,否则多次调用时将会出现异常;
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>
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 }
- 与主线程交互
- 线程任务的调度
- 改善你的设计,少用异步处理
- 与主线程有交互时用AsyncTask,否则就用Thread
- 当有需要大量线程执行任务时,一定要创建线程池
- 对于想要立即开始执行的异步任务,要么直接使用Thread,要么单独创建线程池提供给AsyncTask
- 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)