谈谈AsyncTask

由于Android的特性,如果要执行耗时操作,必须在非ui线程执行.除了Thread 和 Runnable 的方式开启子线程外,Android扮演线程角色的有很多,这篇博客就来谈谈其中的一种————AsyncTask,一个执行异步任务的类,底层采用线程池实现的.

AsyncTask简介

AsyncTask is designed to be a helper class around {@link Thread} and {@link Handler}
and does not constitute a generic threading framework. AsyncTasks should ideally be
used for short operations (a few seconds at the most.) If you need to keep threads
running for long periods of time, it is highly recommended you use the various APIs
provided by the java.util.concurrent package such as {@link Executor},
{@link ThreadPoolExecutor} and {@link FutureTask}.

AsyncTask是一个抽象类,可以代替Handler和Thread在An来处理后台操作,通知ui刷新,Android的官方文档的介绍中写明,AsyncTask适合执行数秒的短时间耗时操作,长时间的耗时操作推荐使用线程池等其他的方式.

AsyncTask内部封装了两个线程池(SeriaExcutor和THREAD_POOL_EXCUTOR)和一个Handler(InternalHandler).

其中SerialExecutor线程池用于任务的排队,让需要执行的多个耗时任务,按顺序排列,THREAD_POOL_EXECUTOR线程池才真正地执行任务,InternalHandler用于从工作线程切换到主线程。

值得一提的是:THREAD_POOL_EXECUTOR的大小和CPU的核心数有关:

code

int CORE_POOL_SIZE=Math.max(2,Math.min(CPU_COUNT - 1, 4));
int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;
int KEEP_ALIVE_SECONDS = 30;

AsyncTask的使用

1.AsyncTask的泛型参数

public abstract class  AsyncTask<Params,Progress, Result> 

三个泛型参数的含义如下:

  • Params: 开始异步任务执行时传入的参数类型;
  • Progress: 异步任务执行过程中,返回进度值的类型;
  • Result: 异步任务执行完成后,返回的结果类型;

如果AsyncTask确定不需要传递具体参数,那么这三个泛型参数可以用Void来代替。

2.AsyncTask的核心方法

onPreExcute()

这个方法在后台任务开始执行之前调用,在主线程执行,用于进行一些界面上的初始化操作

doInBackground(Params…)

这个方法中的所有代码都会在子线程中运行,我们应该在这里去处理所有的耗时任务。

任务一旦完成就可以通过return语句来讲任务的执行结果返回,如果AsyncTask的第三个泛型参数是Void,就可以不返回任务执行结果.
注意: 因为是子线程,所以不能执行ui操作,如需返回进度,需要调用publishProgress(Progress…)方法来完成.

onProgressUpdate(Progress…)

当在doInBackground中调用了publishProgress()方法,该方法很快就会被调用
在这个方法中可以对UI进行操作,在主线程中进行,利用参数中的数值就可以对界面元素进行相应的更新。

onPostExecute(Result)

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

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

onPreExecute() –> doInBackground() –> publishProgress() –> onProgressUpdate() –> onPostExecute()

如果不需要执行更新进度则为:

onPreExecute() –> doInBackground() –> onPostExecute()

除了上面四个方法,AsyncTask还提供onCancelled()方法,这个方法同样在主线程执行,当异步任务取消时,onCancelled()会被调用,这时onPostExecute()则不会被调用,但需要注意:AsyncTask()中的cancel()方法并不是真正的去取消任务,只是设置这个任务为取消状态,我们需要再doInBackground()判断终止任务.类似于: 想要终止一个线程,调用interrupt()方法,只是进行标记为中断,需要再线程内部进行标记判断然后中断线程.

3.AsyncTask的简单使用

class DownloadTask extends AsyncTask<Void, Integer, Boolean> {  
@Override  
    protected void onPreExecute() {  
        progressDialog.show();  
    }  
    @Override  
    protected Boolean doInBackground(Void... params) {  
        try {  
            while (true) {  
                int downloadPercent = doDownload();  
                publishProgress(downloadPercent);  
                if (downloadPercent >= 100) {  
                    break;  
                }  
            }  
        } catch (Exception e) {  
            return false;  
        }  
        return true;  
    } 
    @Override  
    protected void onProgressUpdate(Integer... values) {  
        progressDialog.setMessage("当前下载进度:" + values[0] + "%");  
    }  
    @Override  
    protected void onPostExecute(Boolean result) {  
        progressDialog.dismiss();  
        if (result) {  
            Toast.makeText(context, "下载成功", Toast.LENGTH_SHORT).show();  
        } else {  
            Toast.makeText(context, "下载失败", Toast.LENGTH_SHORT).show();  
        }  
    }  
}

上面模拟了一个下载任务,当然,前面也说过,长时间的异步操作并不适合用AsyncTask.这里仅作举例.想要开启这个任务,很简单,只需要调用excute()方法即可.

new DownloadTask.excute();

注意事项

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

AsyncTask的优缺点

优点

  • 过程可控
  • api简洁

缺点

  • 多个异步操作同时进行ui刷新的时候,就变得很复杂
  • 最大并发数一般不超过5

使用AsyncTask可能造成的问题

生命周期

很多开发者会认为在一个Activity中创建的AsyncTask会随着Activity销毁而销毁,事实并非如此,AsyncTask会随着doInBackground()方法执行完毕才销毁,然后,cancel()被调用,那么onCancel会执行;否则执行postExecute方法会执行。如果在AsyncTask没有执行完毕,就销毁了Activity,AsyncTask可能会崩溃,因为它想要处理的view已经不存在了。所以,我们总是必须确保在销毁活动之前取消任务。总之,我们使用AsyncTask需要确保AsyncTask正确地取消。

另外,即使我们正确地调用了cancle() 也未必能真正地取消任务。因为如果在doInBackgroud里有一个不可中断的操作,比如BitmapFactory.decodeStream(),那么这个操作会继续下去。

内存泄漏

如果AsyncTask未声明成静态,则会持有外部类Activity的引用,当Activity销毁之后,AsyncTask还在执行,它将在内存中依旧保持这个引用,会造成内存泄漏

结果丢失

当屏幕旋转Activity销毁重新创建(未配置android:configChanges=”orientation|screenSize”的情况)之前运行的AsyncTask会持有之前Activity的引用,这时调用onPostExecute()再去更新界面将不再生效。

并行还是串行

当想要串行执行时,直接执行execute()方法,如果需要并行执行,则要执行executeOnExecutor(Executor)。

-------------看啥呢?没了-------------