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
了。  
         
          《Android(java)学习笔记92:Android线程形态之 AsyncTask (异步任务)》
  
  
2)Android 3.0 以后,也即SDK/API 11和 以后的版本  
  可能是Google意识到了AsyncTask的局限性了,从Android 3.0开始对AsyncTask的API做出了一些调整:#execute()提交的任务按先后顺序每次只运行一个也就是说它是按提交的次序,每次只启动一个线程执行一个任务,完成之后再执行第二个任务,也就是相当于只有一个后台线程在执行所提交的任务 。         
       《Android(java)学习笔记92:Android线程形态之 AsyncTask (异步任务)》
 
         
    新增了接口#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()效果是一样的。
                                                               
     《Android(java)学习笔记92:Android线程形态之 AsyncTask (异步任务)》
  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)

    原文作者:java线程池
    原文地址: http://www.cnblogs.com/hebao0514/p/4727633.html
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞