Android AsyncTask异步基础介绍,多实例下并行分析

目录

前言

一、AsyncTask基础

二、使用步骤

三、注意事项

1、 关于 生命周期

2、 关于 内存泄漏

3、 线程任务执行结果 丢失

四、案例:AsyncTask下载文件

五、多实例并行



Async                                                   |     Task

async [æˈsɪŋk] abbr. 异步,非同步          |     英  [tɑːsk]  美  [tæsk]n. (困难的)任务,工作;

 AsyncTask,称为异步任务,属于原生类。它对Thread和Handler进行了封装,方便在子线程中执行耗时任务,然后将结果发送给主线程进行UI更新等操作。使用AsyncTask就可以不用关注Thread和Handler,因为AsyncTask内部会对其进行管理,我们就只需要关注我们的业务逻辑即可。

建议先看一遍——Android Handler机制使用,源码分析

一、AsyncTask基础

源码类如下:

//抽象类
public abstract class AsyncTask {
    //线程池类型
    public static final Executor SERIAL_EXECUTOR = null;
    public static final Executor THREAD_POOL_EXECUTOR = null;

    public AsyncTask() {}

    protected abstract Result doInBackground(Params... var1);

    protected void onPreExecute() {}

    protected void onPostExecute(Result result) {}

    protected void onProgressUpdate(Progress... values) {}
    
    protected final void publishProgress(Progress... values) {    }

    public final boolean isCancelled() {}

    public final boolean cancel(boolean mayInterruptIfRunning) {}

    protected void onCancelled() {    }

    ...//省略
}

AsyncTask类中参数为3种泛型类型,控制AsyncTask子类执行线程任务时各个阶段的返回类型。如下所示:

  • Params   :开始异步任务执行时传入的参数类型,对应excute()中传递的参数
  • Progress:异步任务执行过程中,返回下载进度值的类型
  • Result     :异步任务执行完成后,返回的结果类型,与doInBackground()的返回值类型保持一致

其中,Params用于AsyncTask调用execute()方法时传入后,依次执行下面其他方法。

二、使用步骤

Android AsyncTask异步基础介绍,多实例下并行分析_第1张图片

第一步:

创建AsyncTask子类,继承AsyncTask类,为3个泛型参数指定类型;

若不使用,可用java.lang.Void类型代替

  private class MyTask extends AsyncTask {
    //线程池对象
    public static final Executor SERIAL_EXECUTOR = null;
    public static final Executor THREAD_POOL_EXECUTOR = null;
      //执行线程任务前的操作
      @Override
      protected void onPreExecute() {}

      //接收输入参数、执行任务中的耗时操作、返回 线程任务执行的结果
      @Override
      protected String doInBackground(String... params) {

            ...// 自定义的线程任务
            // 可调用publishProgress()显示进度, 之后将执行onProgressUpdate()
             publishProgress(count);
         }
      // 在主线程 显示线程任务执行的进度
      @Override
      protected void onProgressUpdate(Integer... progresses) {}

      // 接收线程任务执行结果、将执行结果显示到UI组件
      @Override
      protected void onPostExecute(String result) { }

      // 将异步任务设置为:取消状态
      @Override
      protected void onCancelled() {}
  }

第二步:

创建AsyncTask子类的实例对象,AsyncTask子类的实例必须在UI线程中创建

 MyTask mTask = new MyTask();

第三步:

手动调用execute(Params... params) 从而执行异步线程任务,同样也必须在UI线程中调用。另外,同一个AsyncTask实例对象只能执行1次,若执行第2次将会抛出异常。

  mTask.execute();

 

三、注意事项

在使用AsyncTask时有一些问题需要注意的:

1、 关于 生命周期

  • 结论
    AsyncTask不与任何组件绑定生命周期
  • 使用建议
    Activity或 Fragment中使用 AsyncTask时,最好在Activity或 FragmentonDestory()调用 cancel(boolean)

2、 关于 内存泄漏

  • 结论
    AsyncTask被声明为Activity的非静态内部类,当Activity需销毁时,会因AsyncTask保留对Activity的引用 而导致Activity无法被回收,最终引起内存泄露
  • 使用建议
    AsyncTask应被声明为Activity的静态内部类

3、 线程任务执行结果 丢失

  • 结论
    Activity重新创建时(屏幕旋转 / Activity被意外销毁时后恢复),之前运行的AsyncTask(非静态的内部类)持有的之前Activity引用已无效,故复写的onPostExecute()将不生效,即无法更新UI操作
  • 使用建议
    Activity恢复时的对应方法 重启 任务线程

 

四、案例:AsyncTask下载文件

Layout布局文件:



    

界面上有一个“开始下载”的按钮,点击该按钮即可下载。对应的Java代码:

public class MainActivity extends Activity implements Button.OnClickListener {
    TextView textView = null;
    Button btnDownload = null;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        textView = (TextView)findViewById(R.id.textView);
        btnDownload = (Button)findViewById(R.id.btnDownload);
        Log.i("iSpring", "MainActivity -> onCreate, Thread name: " + Thread.currentThread().getName());
    }

    @Override
    public void onClick(View v) {
        //要下载的文件地址
        String[] urls = {
                "http://blog.csdn.net/iispring/article/details/47115879",
                "http://blog.csdn.net/iispring/article/details/47180325",
                "http://blog.csdn.net/iispring/article/details/47300819",
                "http://blog.csdn.net/iispring/article/details/47320407",
                "http://blog.csdn.net/iispring/article/details/47622705"
        };
        DownloadTask downloadTask = new DownloadTask();
        downloadTask.execute(urls);
    }

    //在此例中,Params泛型是String类型,Progress泛型是Object类型,Result泛型是Long类型
    private class DownloadTask extends AsyncTask {
        @Override
        protected void onPreExecute() {
            Log.i("iSpring", "DownloadTask -> onPreExecute, Thread name: " + Thread.currentThread().getName());
            super.onPreExecute();
            btnDownload.setEnabled(false);
            textView.setText("开始下载...");
        }

        @Override
        protected Long doInBackground(String... params) {
            Log.i("iSpring", "DownloadTask -> doInBackground, Thread name: " + Thread.currentThread().getName());
            //totalByte表示所有下载的文件的总字节数
            long totalByte = 0;
            //params是一个String数组
            for(String url: params){
                //遍历Url数组,依次下载对应的文件
                Object[] result = downloadSingleFile(url);
                int byteCount = (int)result[0];
                totalByte += byteCount;
                //在下载完一个文件之后,我们就把阶段性的处理结果发布出去
                publishProgress(result);
                //如果AsyncTask被调用了cancel()方法,那么任务取消,跳出for循环
                if(isCancelled()){
                    break;
                }
            }
            //将总共下载的字节数作为结果返回
            return totalByte;
        }

        //下载文件后返回一个Object数组:下载文件的字节数以及下载的博客的名字
        private Object[] downloadSingleFile(String str){
            Object[] result = new Object[2];
            int byteCount = 0;
            String blogName = "";
            HttpURLConnection conn = null;
            try{
                URL url = new URL(str);
                conn = (HttpURLConnection)url.openConnection();
                InputStream is = conn.getInputStream();
                ByteArrayOutputStream baos = new ByteArrayOutputStream();
                byte[] buf = new byte[1024];
                int length = -1;
                while ((length = is.read(buf)) != -1) {
                    baos.write(buf, 0, length);
                    byteCount += length;
                }
                String respone = new String(baos.toByteArray(), "utf-8");
                int startIndex = respone.indexOf("");
                if(startIndex > 0){
                    startIndex += 7;
                    int endIndex = respone.indexOf("");
                    if(endIndex > startIndex){
                        //解析出博客中的标题
                        blogName = respone.substring(startIndex, endIndex);
                    }
                }
            }catch(MalformedURLException e){
                e.printStackTrace();
            }catch(IOException e){
                e.printStackTrace();
            }finally {
                if(conn != null){
                    conn.disconnect();
                }
            }
            result[0] = byteCount;
            result[1] = blogName;
            return result;
        }

        @Override
        protected void onProgressUpdate(Object... values) {
            Log.i("iSpring", "DownloadTask -> onProgressUpdate, Thread name: " + Thread.currentThread().getName());
            super.onProgressUpdate(values);
            int byteCount = (int)values[0];
            String blogName = (String)values[1];
            String text = textView.getText().toString();
            text += "\n博客《" + blogName + "》下载完成,共" + byteCount + "字节";
            textView.setText(text);
        }

        @Override
        protected void onPostExecute(Long aLong) {
            Log.i("iSpring", "DownloadTask -> onPostExecute, Thread name: " + Thread.currentThread().getName());
            super.onPostExecute(aLong);
            String text = textView.getText().toString();
            text += "\n全部下载完成,总共下载了" + aLong + "个字节";
            textView.setText(text);
            btnDownload.setEnabled(true);
        }

        @Override
        protected void onCancelled() {
            Log.i("iSpring", "DownloadTask -> onCancelled, Thread name: " + Thread.currentThread().getName());
            super.onCancelled();
            textView.setText("取消下载");
            btnDownload.setEnabled(true);
        }
    }
}

控制台输出如下所示: 

Android AsyncTask异步基础介绍,多实例下并行分析_第2张图片

Android AsyncTask异步基础介绍,多实例下并行分析_第3张图片

 

五、多实例并行

我们上面提到,对于某个AsyncTask实例,只能执行一次execute方法,如果我们想并行地执行多个任务,难道要实例化多个AsyncTask实例对象,然后分别调用各个实例的execute方法吗?为了探究效果,我们将代码更改如下所示:

public void onClick(View v) {
        //要下载的文件地址
        String[] urls = {
                "http://blog.csdn.net/iispring/article/details/47115879",
                "http://blog.csdn.net/iispring/article/details/47180325",
                "http://blog.csdn.net/iispring/article/details/47300819",
                "http://blog.csdn.net/iispring/article/details/47320407",
                "http://blog.csdn.net/iispring/article/details/47622705"
        };

        DownloadTask downloadTask1 = new DownloadTask();
        downloadTask1.execute(urls);//第一个实例执行

        DownloadTask downloadTask2 = new DownloadTask();
        downloadTask2.execute(urls);//第二个实例执行
    }

运行后界面如下: 
Android AsyncTask异步基础介绍,多实例下并行分析_第4张图片

控制台输出如下所示: 
Android AsyncTask异步基础介绍,多实例下并行分析_第5张图片

分析:

通过观察控制台的输出结果,可以发现downloadTask1的doInBackground方法是运行在线程“AsyncTask #1”中的。downloadTask2的doInBackground方法是运行在线程”AsyncTask #2”中的。

我们对比上面的GIF图发现,在downloadTask1按照顺序下载完五篇文章之后,downloadTask2才开始按照顺序下载五篇文章。分析日志发现downloadTask1的doInBackground方法执行后,下载了五个文件,并触发五次onProgressUpdate,而之后才去执行downloadTask2的doInBackground方法。

综上所述,AsyncTask创建了多个实例并同时执行各自execute方法,这些实例的execute方法不是并行的,而是串行的同步的。即在第一个实例的doInBackground完成任务后,第二个实例的doInBackgroud方法才会开始执行。

原因是,AsyncTask为downloadTask1开辟了名为”AsyncTask #1”的工作线程,在其完成了任务之后就销毁了,然后又为downloadTask2开辟了名为”AsyncTask #2”的工作线程,所以两个线程可能并不是同时存在。

解决方案:

从Android 3.0开始AsyncTask增加了executeOnExecutor方法,用该方法可以并行处理任务。方法如下:

public final AsyncTask 
                executeOnExecutor(Executor exec, Params... params)

第一个参数是一个Executor线程池对象,为了让AsyncTask并行处理任务。通常情况下我们此处传入AsyncTask.THREAD_POOL_EXECUTOR即可。

第二个参数params表示的是要执行的任务的参数。

示例代码如下:

public void onClick(View v) {
        if(Build.VERSION.SDK_INT >= 11){
            String[] urls = {
                    "http://blog.csdn.net/iispring/article/details/47115879",
                    "http://blog.csdn.net/iispring/article/details/47180325",
                    "http://blog.csdn.net/iispring/article/details/47300819",
                    "http://blog.csdn.net/iispring/article/details/47320407",
                    "http://blog.csdn.net/iispring/article/details/47622705"
            };

            DownloadTask downloadTask1 = new DownloadTask();
            downloadTask1.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, urls);//看这里!

            DownloadTask downloadTask2 = new DownloadTask();
            downloadTask2.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, urls);//看这里!
        }
    }

点击下载按钮后,运行完的界面如下: 
Android AsyncTask异步基础介绍,多实例下并行分析_第6张图片

控制台输出如下: 
Android AsyncTask异步基础介绍,多实例下并行分析_第7张图片

小结:

通过控制台的输出结果我们可以看到,在downloadTask1执行了doInBackground方法后,downloadTask2也立即执行了doInBackground方法。并且通过程序运行完的UI界面可以看到在一个DownloadTask实例下载了一篇文章之后,另一个DownloadTask实例也立即下载了一篇文章,两个DownloadTask实例交叉按顺序下载文件,可以看出这两个AsyncTask的实例是并行执行的。

 

你可能感兴趣的:(Android AsyncTask异步基础介绍,多实例下并行分析)