AsyncTask 使用详解

一. AsyncTask 是什么

AsyncTask是Android提供的执行异步操作的类,它通过对ThreadHandler进行封装,可以让我们在后台线程中执行耗时操作,然后将结果发送给UI主线程,从而在主线程中进行UI更新等操作。

二. 为什么要引入 AsyncTask

其实在 Android 中我们经常通过 Thread + Handler实现多线程通信,Handler实现异步任务时需要为每一个任务创建一个新的线程,最常见的使用场景就是是:在后台线程中进行耗时操作,当后台线程任务完成后通过Handler向UI主线程发送消息,从而更新UI;
正式因为Android中这样类似的场景很多, 并且Thread + Handler 不够方便,如果有多个线程的时候这种处理方式会更加麻烦, 会使你的代码很复杂很难理解, 实现复杂的操作并需要频繁地更新UI时这会变得更糟糕, 代码的可维护性也差.

而如果使用AsyncTask,就不用关注ThreadHandler的内部实现,只需关注于业务逻辑, 这样可以使开发者处理业务逻辑更加方便,开发效率更高。

其实使用Thread + Handler也有自己优点, 即对于整个过程的控制比较精细;

其实除了Hanlder 和 AsyncTask之外, Android还提供了一些其他线程中访问UI线程的方法:

Activity.runOnUiThread( Runnable ) 
View.post( Runnable ) 
View.postDelayed( Runnable, long ) 

但其实内部实现都是 Thread + Handler的方式.

三. AsyncTask 详解

AnsycTask执行异步任务时,内部会创建一个线程池来管理要运行的任务,当调用了AsyncTask.execute()后,AsyncTask会把任务交给线程池,由线程池来管理创建 Thread 和运行 Therad, 最后由handler处理和UI的操作.

1. AsyncTask线程池可以同时执行多少个异步线程

Android 3.0 之前规定线程池的核心线程数(corePoolSize)为5个,线程池(maximumPoolSize)总大小为128,还有一个缓冲队列(sWorkQueue)可以放10个任务,所以AsyncTask最多可以添加138个任务, 当我们去添加第139个任务时,程序就会崩溃。

2. AsyncTask任务是串行还是并行?

从Android 1.5 开始,AsyncTask被引入用来帮助开发者更简单地管理线程异步任务;
从Android 1.6 到 Android 2.3 期间, AsyncTask是并行的,即有5个核心线程的线程池ThreadPoolExecutor负责调度任务;
从Android 3.0开始,AsyncTask 被改成了串行,默认的Executor被指定为SERIAL_EXECUTOR; 如需要并行,可以通过设置executeOnExecutor(Executor)来实现多个AsyncTask并行。

所以如果你在使用AsyncTask忽视它版本不一致带来串行和并行的问题, 你的APP可能会达不到预想的效果。
但由于目前我们开发APP时几乎不会去适配Android 4.0 以下的系统, 所以对我们来说AsyncTask默认总是串行;

四. AsyncTask API简介

AsyncTask是一个抽象类, 来看看他的定义:

public abstract class AsyncTask<Params, Progress, Result> {

从上述定义可以看出要使用AsyncTask有两种方式: 一是继承AsyncTask创建一个子类, 二是new 一个匿名的AsyncTask; 但是不管那种方式都需要传入三个泛型参数,这三个参数的用途如下:
1. Params

表示启动任务的输入参数, 可用于在后台任务中使用, 比如URL请求;

2. Progress

表示后台任务执行的进度的单位, 如果需要在界面上显示当前的进度,则使用这里指定的泛型作为进度单位。

3. Result

表示后台计算结果的类型, 当任务执行完毕后,如果需要对结果进行返回,可以使用该泛型参数作为返回值类型。

主要的几个方法如下:

//在调用execute(Params... params)后立即执行, 运行在主线程
protected void onPreExecute() {}

//在调用onPreExecute()完成后立即执行,运行在后台线程, 用于执行后台耗时的操作
protected abstract Result doInBackground(Params... params);

//当后台耗时操作结束时,该方法将会被调用,计算结果将做为参数传递到此方法中,该方法运行在主线程, 所以主线程可以直接拿到结果并且将结果显示到UI组件上
protected void onPostExecute(Result result) {}

//在doInBackground方法内部调用publishProgress方法可以直接将进度信息传给onProgressUpdate方法,最终更新到UI组件; 该方法运行在主线程
protected void onProgressUpdate(Progress... values) {}

//执行一个异步任务,在主线程中调用,用来触发异步任务的执行
public static void execute(Runnable runnable) {
    sDefaultExecutor.execute(runnable);
}

AsyncTask的需要注意的几点:

  1. AsyncTask异步任务的必须在UI主线程中创建;
  2. execute(Params… params)方法必须在UI主线程中调用;
  3. 一个AsyncTask 任务只能执行一次,执行第二次将会抛出异常。

四. 简单实例

我们通过来模拟一个耗时任务, 更加清晰明了的学会怎么使用AsyncTask;

先在activity 内部继承AsyncTask创建DownloadAsyncTask, 在doInBackground方法中通过for循环的方式模式耗时任务, 代码如下:

public static class DownloadAsyncTask extends AsyncTask<Void, Integer, Void> {

        @Override
        protected void onPreExecute() {
            super.onPreExecute();
            if (mDialog != null) {
                mDialog.setMax(100);
                mDialog.show();
            }
        }

        @Override
        protected void onPostExecute(Void result) {
            super.onPostExecute(result);
            if (mDialog != null) {
                mDialog.dismiss();
            }
        }

        @Override
        protected void onProgressUpdate(Integer... values) {
            super.onProgressUpdate(values);
            if (mDialog != null) {
                mDialog.setProgress(values[0]);
            }
        }

        @Override
        protected Void doInBackground(Void... params) {
            //耗时的任务
            for (int i = 0; i < 101; i++) {
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                publishProgress(i);
            }
            return null;
        }
    }

使用上面的DownloadAsyncTask:

public class MainActivity extends AppCompatActivity {
    private static ProgressDialog mDialog;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mDialog = new ProgressDialog(this);
        mDialog.setMax(100);
       mDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
        mDialog.setCancelable(false);

        Button btnDownload = (Button) findViewById(R.id.btn_download);
        btnDownload.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //执行AsyncTask后台任务
                DownloadAsyncTask task = new DownloadAsyncTask();
                task.execute();
            }
        });
    }
}

五. AsyncTask容易出错的地方

1. 内存泄露

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

2. 取消任务

Activity销毁后, Activity中的AsyncTask并不会随着Activity的销毁而销毁
AsyncTask会一直执行doInBackground()方法直到方法执行结束, 此时如果不去做处理就会出现问题, 具体如下:

当App屏幕旋转时,当前Activity会被销毁和重建, 当Activity重启时,AsyncTask中对该Activity的引用已经无效了,因此onPostExecute()就不会起作用,若AsynTask正在执行,折会报 view not attached to window manager 异常

两种解决办法:
1 .最简单暴力的方式,在Activity销毁之前,取消AsyncTask的运行,以此来保证程序的稳定;

2 . 在 Activity 的onDestory()方法中调用Asyntask.cancal方法,让二者的生命周期同步;

3 . 在重建Activity的时候,会回调Activity.onRetainNonConfigurationInstance()重新传递一个新的Activity引用给AsyncTask,完成引用的更新;

3. 结果丢失

在屏幕旋转, Activity在内存紧张时被回收等情况下造成Activity重新创建时AsyncTask数据丢失的问题, 当Activity销毁并创新创建后,还在运行的AsyncTask会持有之前的Activity实例, 导致onPostExecute()回调没有意义。

猜你喜欢

转载自blog.csdn.net/u010784887/article/details/78883427