Android开发之十八-AsyncTask基本使用

简介

  • AsyncTask 是

轻量级的异步任务类,轻松地在 UI 线程控制后台操作和后台操作所返回结果,无需使用 Thread 和 Handler 这样的组合来进行切换。实际上 AsyncTask 是为我们所设计的关于 Thread 和 Handler 的帮助类。

  • AsyncTask 是经过 Android 封装、简化的异步任务实现方式,

内部实现也是由 Thread 和 Handler 来实现异步任务和切换线程的

不说多余的废话,先上代码

       

1AsyncTask task = new AsyncTask<Void, Integer, Void>() { 2            @Override 3            protected void onPreExecute() { 4                super.onPreExecute(); 5            } 6            protected void onProgressUpdate(Integer... values) { 7                super.onProgressUpdate(values); 8                if (isCancelled()) { 9                    return; 10                } 11            } 12  13            @Override 14            protected Void doInBackground(Void... voids) { 15                publishProgress(); 16                return null; 17            } 18  19            @Override 20            protected void onPostExecute(Void aVoid) { 21                super.onPostExecute(aVoid); 22            } 23  24            @Override 25            protected void onCancelled() { 26                super.onCancelled(); 27            } 28        }; 29        //采用默认的线程池执行异步任务 30        //task.execute(); 31        //采用自定义线程池执行异步任务 32        task.executeOnExecutor(Executors.newSingleThreadExecutor()); 33

先看3个三个泛型代表什么意思

Paramas

  • 在执行AsyncTask时需要传入的参数,可用于在后台任务中使用。

Progress

  • 后台执行耗时任务时,返回的任务进度

Result

  • 任务执行完毕后返回的结果

再看一下Asynctask中的几个方法

onPreExecute():

  • 这个方法是在执行异步

任务之前的时候执行,并且是 在UI Thread当中执行的,通常我们在这个方法里做一些UI控件的初始化的操作,例如弹出ProgressDialog

doInBackground(Params... params):

  • 在onPreExecute()方法执行完之后,会马上执行这个方法,这个方法就是来

处理异步任务的方法,Android操作系统会在后台的线程池当中 开启一个worker thread来执行我们的这个方法,所以这个方法是在worker thread当中执行的,这个方法 执行完之后就可以将我们的 执行结果发送给我们的后一个 onPostExecute 方法,因此,doInBackground是这四个方法中 唯一必须要重写的方法,在这个方法中,我们可以从网络当中获取数据等一些耗时操作

onProgressUpdate(Progess... values):

  • 这个方法也是

在UI Thread当中执行的,我们在异步任务执行的时候,有时候需要将执行的进度返回给我们的UI界面,例如下载一张网络图片,我们需要时刻 显示其下载的进度,就可以使用这个方法来更新我们的进度。这个方法在调用之前,我们需要在 doInBackground 方法中,调用一个 publishProgress(Progress) 的方法来将我们的进度时时刻刻传递给onProgressUpdate 方法从而来更新进度

onPostExecute(Result... result):

  • 当我们的异步任务

执行完之后,就会将结果返回给这个方法,这个方法也是 在UI Thread当中调用的,我们可以将返回的结果显示在UI控件上

onCancelled

当AsyncTask调用了 cancel(boolean)方法时,在doinbackground执行完成之后,程序会调用onCancelled方法,而不是回调onPostExecute方法

AsyncTask 的一般使用代码写法形式如下:

1private class MyTask extends AsyncTask<Params, Progress, Result> { ... } 2MyTask task = new MyTask(); 3task.execute(Params...) ; 4

=========================================================================================

Android中我们可以通过Theater+Handler来实现多线程通信,当子线程的耗时任务完成后通过Handler向主线程发送message,主线程收到message后开始更新UI,而为了使代码更加统一,我们会使用AsyncTask类。

 

什么是AsyncTask

  • AsyncTask是Android提供的轻量级异步类。
  • 为了降低异步通信的开发难度,提供了AsyncTask。
  • AsyncTask直接继承与Object类,位于android.os包中。
  • 使用AsyncTask可以忽略Looper、MessageQueue、Handler等复杂对象,更便捷地完成耗时操作。

AsyncTask的使用

  • 新建内部类继承AsyncTask
  • 定义AsyncTask的三种泛型参数
  • 重写doInBackground抽象方法
  • 重写onPreExecute方法
  • 重写onProgressUpdate方法
  • 重写onPostExecute方法
  • 在需要启动的地方调用execute方法
  • 需要实现的几个方法及作用

AsyncTask有四种重要的回调方法

  • onProgressUpdate
  • doInBackground
  • onPostExecute
  • onPreExecute 
  • 这四个方法会在AsyncTask的不同时期进行自动调用,我们只需要实现这几个方法的内部逻辑即可。这四个方法的一些参数和返回值都是基于泛型的,而且泛型的类型还不一样,所以在AsyncTask的使用中会遇到三种泛型参数:Params, Progress 和 Result,如下图所示: 

  

Params

  • 表示用于AsyncTask执行任务的参数的类型 

Progress

  • 表示在后台线程处理的过程中,可以阶段性地发布结果的数据类型 

Result

  • 表示任务全部完成后所返回的数据类型 

我们通过调用AsyncTask的execute()方法传入参数并执行任务,然后AsyncTask会依次调用以下四个方法:

onPreExecute 

doInBackground 

onProgressUpdate

 onPostExecute 

 

下面截取Android中AsyncTask使用详解中的例子 
布局文件

1<RelativeLayout 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:paddingLeft="@dimen/activity_horizontal_margin" 6    android:paddingRight="@dimen/activity_horizontal_margin" 7    android:paddingTop="@dimen/activity_vertical_margin" 8    android:paddingBottom="@dimen/activity_vertical_margin" 9    tools:context=".MainActivity"> 10 11    <Button android:id="@+id/btnDownload" 12        android:layout_width="match_parent" 13        android:layout_height="wrap_content" 14        android:onClick="onClick" 15        android:text="开始下载" /> 16 17    <TextView android:id="@+id/textView" 18        android:layout_below="@id/btnDownload" 19        android:layout_width="match_parent" 20        android:layout_height="wrap_content" /> 21 22</RelativeLayout> 23

java:

1public class MainActivity extends Activity implements Button.OnClickListener { 2 3    TextView textView = null; 4    Button btnDownload = null; 5 6    @Override 7    protected void onCreate(Bundle savedInstanceState) { 8        super.onCreate(savedInstanceState); 9        setContentView(R.layout.activity_main); 10        textView = (TextView)findViewById(R.id.textView); 11        btnDownload = (Button)findViewById(R.id.btnDownload); 12        Log.i("iSpring", "MainActivity -> onCreate, Thread name: " + Thread.currentThread().getName()); 13    } 14 15    @Override 16    public void onClick(View v) { 17        //要下载的文件地址,第一个参数:Params泛型是String类型 18        String[] urls = { 19                "http://blog.csdn.net/iispring/article/details/47115879", 20                "http://blog.csdn.net/iispring/article/details/47180325", 21                "http://blog.csdn.net/iispring/article/details/47300819", 22                "http://blog.csdn.net/iispring/article/details/47320407", 23                "http://blog.csdn.net/iispring/article/details/47622705" 24        }; 25 26 //创建一个新的下载对象 27        DownloadTask downloadTask = new DownloadTask(); 28 //执行该对象 29        downloadTask.execute(urls); 30    } 31 32    //public abstract class AsyncTask<Params, Progress, Result> 33    //在此例中,Params泛型是String类型,Progress泛型是Object类型,Result泛型是Long类型 34    private class DownloadTask extends AsyncTask<String, Object, Long> { 35        @Override 36        protected void onPreExecute() { 37            Log.i("iSpring", "DownloadTask -> onPreExecute, Thread name: " + Thread.currentThread().getName()); 38            super.onPreExecute(); 39            btnDownload.setEnabled(false); 40            textView.setText("开始下载..."); 41        } 42 43        @Override 44        protected Long doInBackground(String... params) { 45            Log.i("iSpring", "DownloadTask -> doInBackground, Thread name: " + Thread.currentThread().getName()); 46            //totalByte表示所有下载的文件的总字节数 47            long totalByte = 0; 48            //params是一个String数组 49            for(String url: params){ 50                //遍历Url数组,依次下载对应的文件 51                Object[] result = downloadSingleFile(url); 52                int byteCount = (int)result[0]; 53                totalByte += byteCount; 54                //在下载完一个文件之后,我们就把阶段性的处理结果发布出去 55 //Progress泛型是Object类型 56 //需要在 doInBackground 方法中调用一个 publishProgress(Progress) 的方法来 57 //将我们的进度时时刻刻传递给 onProgressUpdate 方法从而来更新进度,onProgressUpdate会自动调用 58                publishProgress(result); 59                //如果AsyncTask被调用了cancel()方法,那么任务取消,跳出for循环 60                if(isCancelled()){ 61                    break; 62                } 63            } 64            //将总共下载的字节数作为结果返回 65 //Result泛型是Long类型 onPostExecute被调用 66            return totalByte; 67        } 68 69        //下载文件后返回一个Object数组:下载文件的字节数以及下载的博客的名字 70        private Object[] downloadSingleFile(String str){ 71            Object[] result = new Object[2]; 72            int byteCount = 0; 73            String blogName = ""; 74            HttpURLConnection conn = null; 75            try{ 76                URL url = new URL(str); 77                conn = (HttpURLConnection)url.openConnection(); 78                InputStream is = conn.getInputStream(); 79                ByteArrayOutputStream baos = new ByteArrayOutputStream(); 80                byte[] buf = new byte[1024]; 81                int length = -1; 82                while ((length = is.read(buf)) != -1) { 83                    baos.write(buf, 0, length); 84                    byteCount += length; 85                } 86                String respone = new String(baos.toByteArray(), "utf-8"); 87                int startIndex = respone.indexOf("<title>"); 88                if(startIndex > 0){ 89                    startIndex += 7; 90                    int endIndex = respone.indexOf("</title>"); 91                    if(endIndex > startIndex){ 92                        //解析出博客中的标题 93                        blogName = respone.substring(startIndex, endIndex); 94                    } 95                } 96            }catch(MalformedURLException e){ 97                e.printStackTrace(); 98            }catch(IOException e){ 99                e.printStackTrace(); 100            }finally { 101                if(conn != null){ 102                    conn.disconnect(); 103                } 104            } 105            result[0] = byteCount; 106            result[1] = blogName; 107            return result; 108        } 109 110        @Override 111        protected void onProgressUpdate(Object... values) { 112            Log.i("iSpring", "DownloadTask -> onProgressUpdate, Thread name: " + Thread.currentThread().getName()); 113            super.onProgressUpdate(values); 114            int byteCount = (int)values[0]; 115            String blogName = (String)values[1]; 116            String text = textView.getText().toString(); 117            text += "\n博客《" + blogName + "》下载完成,共" + byteCount + "字节"; 118            textView.setText(text); 119        } 120 121        @Override 122        protected void onPostExecute(Long aLong) { 123            Log.i("iSpring", "DownloadTask -> onPostExecute, Thread name: " + Thread.currentThread().getName()); 124            super.onPostExecute(aLong); 125            String text = textView.getText().toString(); 126            text += "\n全部下载完成,总共下载了" + aLong + "个字节"; 127            textView.setText(text); 128            btnDownload.setEnabled(true); 129        } 130 131        @Override 132        protected void onCancelled() { 133            Log.i("iSpring", "DownloadTask -> onCancelled, Thread name: " + Thread.currentThread().getName()); 134            super.onCancelled(); 135            textView.setText("取消下载"); 136            btnDownload.setEnabled(true); 137        } 138    } 139} 140

案例-Demo倒计时
java:

1public class Main2Activity extends AppCompatActivity { 2    private TextView tv1; 3    private Button button; 4    int time = 10; 5 6    @Override 7    protected void onCreate(Bundle savedInstanceState) { 8        super.onCreate(savedInstanceState); 9        setContentView(R.layout.activity_main2); 10        bindID(); 11        button.setOnClickListener(new View.OnClickListener() { 12            @Override 13            public void onClick(View v) { 14                new DownloadTask().execute(); 15            } 16        }); 17    } 18 19    private void bindID() { 20        button = findViewById(R.id.btn); 21        tv1 = findViewById(R.id.tv); 22 23    } 24 25 26    class DownloadTask extends AsyncTask<String, String, String> { 27 28        @Override 29        protected String doInBackground(String... strings) { 30            while (time > 0) { 31                publishProgress(time + ""); 32                time--; 33                try { 34                    Thread.sleep(1000); 35                } catch (InterruptedException e) { 36                    e.printStackTrace(); 37                } 38            } 39            return "下载完成"; 40 41        } 42 43        @Override 44        protected void onProgressUpdate(String... values) { 45            super.onProgressUpdate(values); 46            tv1.setText(values[0]); 47        } 48 49        @Override 50        protected void onPostExecute(String s) { 51            super.onPostExecute(s); 52            tv1.setText(s); 53        } 54    } 55 56} 57

xml:

1<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 2    xmlns:app="http://schemas.android.com/apk/res-auto" 3    xmlns:tools="http://schemas.android.com/tools" 4    android:layout_width="match_parent" 5    android:layout_height="match_parent" 6    tools:context=".Main2Activity" 7    android:orientation="vertical"> 8 9    <Button 10        android:id="@+id/btn" 11        android:layout_width="match_parent" 12        android:layout_height="50dp" 13        android:text="button"/> 14    <TextView 15        android:id="@+id/tv" 16        android:layout_width="match_parent" 17        android:layout_height="match_parent" /> 18 19</LinearLayout> 20

下载一张图片

目标1 是实现下载一张网络图片,看下 AsyncTask 是如何来操作的。

1package com.gypsophila.testdemo.asynctask; 2 3import android.graphics.Bitmap; 4import android.graphics.BitmapFactory; 5import android.os.AsyncTask; 6import android.os.Bundle; 7import android.support.annotation.Nullable; 8import android.support.v7.app.AppCompatActivity; 9import android.view.View; 10import android.widget.Button; 11import android.widget.ImageView; 12import android.widget.TextView; 13 14import com.gypsophila.testdemo.R; 15 16import java.io.BufferedInputStream; 17import java.io.IOException; 18import java.io.InputStream; 19import java.net.URL; 20import java.net.URLConnection; 21 22/** 23 * Description: 24 * Author:AstroGypsophila 25 * GitHub:https://github.com/AstroGypsophila 26 */ 27public class AsynctaskActivity extends AppCompatActivity implements View.OnClickListener { 28 29 private TextView mResponseTv; 30 private Button mDownloadBtn; 31 // 图片地址可能是会失效,替换自己找的图片即可 32 private static String IMAGE_URL = "https://noavatar.csdn.net/7/F/C/1_astro_gypsophila.jpg"; 33 private ImageView mImageView; 34 private ImageAsyncTask imageTask; 35 36 @Override 37 protected void onCreate(@Nullable Bundle savedInstanceState) { 38 super.onCreate(savedInstanceState); 39 setContentView(R.layout.activity_asynctask); 40 mResponseTv = (TextView) findViewById(R.id.response); 41 mDownloadBtn = (Button) findViewById(R.id.btn_download); 42 mImageView = (ImageView) findViewById(R.id.image); 43 mDownloadBtn.setOnClickListener(this); 44 } 45 46 @Override 47 public void onClick(View view) { 48 int id = view.getId(); 49 if (id == R.id.btn_download) { 50 //点击下载,执行后台任务 51 imageTask = new ImageAsyncTask(); 52 imageTask.execute(IMAGE_URL); 53 } 54 55 } 56 57 //String是图片地址参数; 58 //Void是进度结果类型,这里用不到 59 //Bitmap是最终返回结果,这里希望下载到一张图片 60 class ImageAsyncTask extends AsyncTask<String, Void, Bitmap> { 61 62 @Override 63 protected void onPreExecute() { 64 super.onPreExecute(); 65 //页面提示 66 mResponseTv.setText("下载中..."); 67 } 68 69 @Override 70 protected Bitmap doInBackground(String... params) { 71 Bitmap bitmap = null; 72 String url = params[0]; 73 URLConnection connection; 74 InputStream is; 75 try { 76 //睡眠2秒,制造耗时操作效果 77 Thread.sleep(2000); 78 connection = new URL(url).openConnection(); 79 is = connection.getInputStream(); 80 BufferedInputStream bis = new BufferedInputStream(is); 81 bitmap = BitmapFactory.decodeStream(bis); 82 bis.close(); 83 is.close(); 84 85 } catch (IOException e) { 86 e.printStackTrace(); 87 } catch (InterruptedException e) { 88 e.printStackTrace(); 89 } 90 91 return bitmap; 92 } 93 94 @Override 95 protected void onPostExecute(Bitmap bitmap) { 96 super.onPostExecute(bitmap); 97 if (bitmap != null) { 98 mImageView.setImageBitmap(bitmap); 99 mResponseTv.setText("下载完成"); 100 } 101 } 102 } 103} 104 105

以上代码,仅仅写了简单的布局,以及实例化了 AsyncTask 子类,由于 AsyncTask 是抽象类,且至少需要实现抽象方法 doInBackground(Params… params)。
在进行下载图片前,onPreExecute() 内做一些提示性或者初始化的操作;然后在真正下载图片代码则是在处于 doInBackground(String… params) 中,它是在子线程中被调用的;最后在onPostExecute(Bitmap) 中拿到图片,更新 UI。

效果查看:

目标1 中代码并没有都使用上所有三个参数和四个步骤,为符合情景现在稍作修改,将它们都使用上。

下载多张图片

目标2 是总共下载三张图片,过程就每下载一张图片就直接先返回更新当前的 ImageView 。

修改的代码如下:

1... 2//下载三张图片 3 4private static String IMAGE_URL = "https://noavatar.csdn.net/7/F/C/1_astro_gypsophila.jpg"; 5private static String IMAGE_URL_TWO = "http://tva1.sinaimg.cn/crop.0.5.309.309.180/69252704jw8erra828plgj208l0ekaaj.jpg"; 6private static String IMAGE_URL_THREE = "https://github.com/AstroGypsophila/TryGank/raw/master/TryGankLib/Common/ResModule/src/main/res/mipmap-xxxhdpi/ic_launcher.png"; 7... 8 9 10... 11 @Override 12 public void onClick(View view) { 13 int id = view.getId(); 14 if (id == R.id.btn_download) { 15 //点击下载,执行后台任务 16 imageTask = new ImageAsyncTask(); 17 imageTask.execute(IMAGE_URL, IMAGE_URL_TWO, IMAGE_URL_THREE); 18 } 19 } 20... 21 22 23//String 是图片地址参数; 24//Bitmap 是整个异步任务过程中,返回的进度单位,这里是图片 25//Integer 是最终返回结果,返回结果为3,说明都下载成功, 26class ImageAsyncTask extends AsyncTask<String, Bitmap, Integer> { 27 28 @Override 29 protected void onPreExecute() { 30 super.onPreExecute(); 31 //页面提示 32 mResponseTv.setText("下载中..."); 33 } 34 35 @Override 36 protected Integer doInBackground(String... params) { 37 //记录成功下载的图片个数 38 int downloadSuccess = 0; 39 try { 40 for (int i = 0; i < params.length; i++) { 41 //睡眠2秒,制造耗时操作效果 42 Thread.sleep(2000); 43 //循环取出可变参数中图片地址 44 String url = params[i]; 45 //Bitmap下载 46 Bitmap bitmap = null; 47 URLConnection connection; 48 InputStream is; 49 connection = new URL(url).openConnection(); 50 is = connection.getInputStream(); 51 BufferedInputStream bis = new BufferedInputStream(is); 52 bitmap = BitmapFactory.decodeStream(bis); 53 // 54 bis.close(); 55 is.close(); 56 //将每一次下载好的图片,作为阶段性结果发回给 onProgressUpdate() 57 publishProgress(bitmap); 58 //成功下载一张则累加 59 downloadSuccess++; 60 } 61 62 } catch (IOException e) { 63 e.printStackTrace(); 64 } catch (InterruptedException e) { 65 e.printStackTrace(); 66 } 67 68 return downloadSuccess; 69 } 70 71 @Override 72 protected void onProgressUpdate(Bitmap... values) { 73 super.onProgressUpdate(values); 74 //接收到的值-图片,可以直接进行更新UI,因为运行在主线程上 75 mImageView.setImageBitmap(values[0]); 76 } 77 78 @Override 79 protected void onPostExecute(Integer integer) { 80 super.onPostExecute(integer); 81 if (integer == 3) { 82 mResponseTv.setText("全部成功下载"); 83 } 84 } 85} 86 87 88

以上代码,在下载三张图片的过程中,每次下载完成都会返回一张图片来设置给 ImageView 。特别说明下 publishProgress(Progress) 和 onProgressUpdate(Progress… values) 里面的参数是你期望在后台任务执行过程中,想返回的什么类型,就在这边体现。
看了很多人举了加载进度条的例子,这边返回的是 Integer 数值,并且又因为泛型参数名字为 Progress,担心初学者会误认为只指任务执行的进度数值。其实不是,它应该是你期望在后台任务执行过程中想要返回的参数类型或者是进度单位。

效果图:

取消异步任务

目标3 在下载三张图片过程中,取消这一异步任务,并且给与我们响应。
修改的代码如下:

点击取消下载按钮,设置取消标志

1... 2@Override 3public void onClick(View view) { 4 int id = view.getId(); 5 if (id == R.id.btn_download) { 6 //点击下载,执行后台任务 7 imageTask = new ImageAsyncTask(); 8 imageTask.execute(IMAGE_URL, IMAGE_URL_TWO, IMAGE_URL_THREE); 9 } else if (id == R.id.btn_cancel) { 10 if (imageTask != null && imageTask.getStatus() == AsyncTask.Status.RUNNING) { 11 //仅仅将 AsyncTask 的状态标记为 cancel 状态,并不是真正取消 12 imageTask.cancel(true); 13 } 14 } 15 16} 17... 18 19

真正的取消,是判断是否取消的标志,然后不继续执行接下来代码

1@Override 2protected Integer doInBackground(String... params) { 3 //记录成功下载的图片个数 4 int downloadSuccess = 0; 5 try { 6 for (int i = 0; i < params.length; i++) { 7 8 //睡眠2秒,制造耗时操作效果 9 Thread.sleep(2000); 10 //若为取消状态,则跳出循环 11 if (isCancelled()) { 12 break; 13 } 14 ... 15 } 16 17 } catch (IOException e) { 18 e.printStackTrace(); 19 } catch (InterruptedException e) { 20 e.printStackTrace(); 21 } 22 23 return downloadSuccess; 24} 25 26@Override 27protected void onPostExecute(Integer integer) { 28 super.onPostExecute(integer); 29 if (integer == 3) { 30 mResponseTv.setText("全部成功下载"); 31 } 32 //注意!取消下载后,这边并不会响应返回 33 //因为 AsyncTask 源码中判断当前是取消状态时,调用的是onCancelled(),而不是onPostExecute() 34 35} 36 37

新增重写 onCancelled()方法

1@Override 2protected void onCancelled(Integer integer) { 3 super.onCancelled(integer); 4 //取消后的响应在这边写 5 mResponseTv.setText("取消下载"); 6} 7 8

小结和注意规则
网络请求或者其他不确定的耗时操作,当然要开启子线程来执行,涉及到异步任务使用 AsyncTask 能较为简易地满足需求。其实 AsyncTask 并不适合处理特别耗时的后台任务,对于特别耗时任务建议使用线程池,但普通的异步任务,有它就大胆放心使用吧。
有了 AsyncTask,我们不必使用 Thread 和Handler 组合来进行线程中发送消息和处理消息的方式来处理异步任务,但是我们需要注意以下 AsyncTask 使用过程中的线程规则。

AsyncTask 的类必须在 UI 线程中加载。这个过程在 JELLY_BEAN(Android 4.1) 及以上版本被系统自动完成。
AsyncTask 的实例必须在 UI 线程中创建。
execute 方法必须在 UI 线程中调用。
AsyncTask 的 onPreExecute(), onPostExecute(Result), doInBackground(Params…), onProgressUpdate(Progress…) 不能手动调用。
一个 AsyncTask 实例只能执行一次,即只能调用 execute 方法一次,否则将抛出运行时异常。
一点拓展是:
在 Android 1.6 以前,AsyncTask 是串行处理任务,在 Android 1.6 开始是用线程池里处理并发任务。然后从 Android 3.0 开始又默认是一个线程串行处理任务,为的是避免 AsyncTask 带来并发错误,从这个版本及以后,我们可以通过 executeOnExecutor 方法来灵活传入配置的线程池处理并发,否则默认是以串行方式。

到此 AsyncTask 的基本用法暂告一段落,但是你以为就结束了吗?来来来,继续吃我一篇 Android AsyncTask 源码详细解析,掌握工作原理和细节。

本文若有描述不清或者错误之处,还请指出,多谢。

=================================================================

在Android中,由于主线程的诸多限制,像网络请求等一些耗时的操作我们必须在子线程中运行。我们往往会通过new Thread来开启一个子线程,待子线程操作完成以后通过Handler切换到主线程中运行。这么以来我们无法管理我们所创建的子线程,并且无限制的创建子线程,它们相互之间竞争,很有可能由于占用过多资源而导致死机或者OOM。所以在Java中为我们提供了线程池来管理我们所创建的线程。

而AsyncTask就是这么个环境下产生的。它本质上是一个静态的线程池(封装了THREAD_POOL_EXECUTOR异步线程池和SERIAL_EXECUTOR同步线程池和Handler),AsyncTask派生出的子类可以实现不同的异步任务,这些任务都是提交到静态的线程池中执行。线程池中的工作线程执行doInBackground(mParams)方法执行异步的任务。当任务状态改变后,工作线程向UI线程发送消息,AsyncTask内部的InternalHandler响应这些消息,并调用相关的回调函数。

下面是三种泛型:

Params表示用于AsyncTask执行任务的参数的类型
Progress表示在后台线程处理的过程中,可以阶段性地发布结果的数据类型
Result表示任务全部完成后所返回的数据类型

.AsyncTask的核心方法:

** onPreExecute()**

  • 这个方法会在后台任务开始执行之间调用,在主线程执行。用于进行一些界面上的初始化操作,比如显示一个进度条对话框等。

doInBackground(Params...)

  • 这个方法中的所有代码都会在子线程中运行,我们应该在这里去处理所有的耗时任务。
  • 任务一旦完成就可以通过return语句来将任务的执行结果进行返回,如果AsyncTask的第三个泛型参数指定的是Void,就可以不返回任务执行结果。注意,在这个方法中是不可以进行UI操作的,如果需要更新UI元素,比如说反馈当前任务的执行进度,可以调用publishProgress(Progress...)方法来完成。

onProgressUpdate(Progress...)

  • 当在后台任务中调用了publishProgress(Progress...)方法后,这个方法就很快会被调用,方法中携带的参数就是在后台任务中传递过来的。在这个方法中可以对UI进行操作,在主线程中进行,利用参数中的数值就可以对界面元素进行相应的更新。

onPostExecute(Result)

  • 当doInBackground(Params...)执行完毕并通过return语句进行返回时,这个方法就很快会被调用。返回的数据会作为参数传递到此方法中,可以利用返回的数据来进行一些UI操作,在主线程中进行,比如说提醒任务执行的结果,以及关闭掉进度条对话框等。

上面几个方法的调用顺序:

  • onPreExecute() --> doInBackground() --> publishProgress() --> onProgressUpdate() --> onPostExecute()
  • 如果不需要执行更新进度则为onPreExecute() --> doInBackground() --> onPostExecute(),
  • 除了上面四个方法,AsyncTask还提供了onCancelled()方法,它同样在主线程中执行,当异步任务取消时,onCancelled()会被调用,这个时候onPostExecute()则不会被调用,但是要注意的是,AsyncTask中的cancel()方法并不是真正去取消任务,只是设置这个任务为取消状态,我们需要在doInBackground()判断终止任务。就好比想要终止一个线程,调用interrupt()方法,只是进行标记为中断,需要在线程内部进行标记判断然后中断线程。

使用AsyncTask的注意事项:

  • ①异步任务的实例必须在UI线程中创建,即AsyncTask对象必须在UI线程中创建。
  • ②execute(Params... params)方法必须在UI线程中调用。
  • ③不要手动调用onPreExecute(),doInBackground(Params...params),onProgressUpdate(Progress... values),onPostExecute(Result result)这几个方法。
  • ④不能在doInBackground(Params... params)中更改UI组件的信息。
  • ⑤一个任务实例只能执行一次,如果执行第二次将会抛出异常。

 

AsyncTask使用不当的后果

1.)生命周期

  • AsyncTask不与任何组件绑定生命周期,所以在Activity/或者Fragment中创建执行AsyncTask时,最好在Activity/Fragment的onDestory()调用 cancel(boolean);

2.) 内存泄漏

  • 如果AsyncTask被声明为Activity的非静态的内部类,那么AsyncTask会保留一个对创建了AsyncTask的Activity的引用。如果Activity已经被销毁,AsyncTask的后台线程还在执行,它将继续在内存里保留这个引用,导致Activity无法被回收,引起内存泄露。

3.) 结果丢失

  • 屏幕旋转或Activity在后台被系统杀掉等情况会导致Activity的重新创建,之前运行的AsyncTask(非静态的内部类)会持有一个之前Activity的引用,这个引用已经无效,这时调用onPostExecute()再去更新界面将不再生效。

AsyncTask里面的两个线程池:

  • 1.     AsyncTask里面有THREAD_POOL_EXECUTOR和SERIAL_EXECUTOR两种方式来异步执行任务;THREAD_POOL_EXECUTOR是异步的,而SERIAL_EXECUTOR任务是顺序执行的。
  • 2.    THREAD_POOL_EXECUTOR如果添加的任务过多,没有及时处理的话,会导致程序崩溃,它的队列size是128;它的调度规则是核心池大小,队列大小,以及最大线程数和异常处理Handler来决定的。
  • 3.    SERIAL_EXECUTOR本质是在THREAD_POOL_EXECUTOR的基础上添加一个mTasks的集合来保证任务的顺序执行

 

代码交流 2021