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  在单个后台异步处理时,显得代码过多,结构过于复杂(相对性)

 

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   
特别的:doInBackgroundpublishProgress --> 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_EXECUTORTHREAD_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)

posted on 2015-08-13 16:15  鸿钧老祖  阅读(358)  评论(0编辑  收藏  举报

导航