zoukankan      html  css  js  c++  java
  • 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)

  • 相关阅读:
    K近邻(K Nearest Neighbor-KNN)原理讲解及实现
    Bisecting KMeans (二分K均值)算法讲解及实现
    KMeans (K均值)算法讲解及实现
    NodeJs使用async让代码按顺序串行执行
    NodeJs递归删除非空文件夹
    NodeJs之配置文件管理
    NodeJs针对Express框架配置Mysql进行数据库操作
    在Express中使用Multiparty进行文件上传及POST、GET参数获取
    Linux操作命令
    SftpUtil FTP文件上传
  • 原文地址:https://www.cnblogs.com/hebao0514/p/4727633.html
Copyright © 2011-2022 走看看