前言
做过安卓开发的没有人不知道AsyncTask类,它是一个处理异步任务极为强大的类,Andorid是个单线程模型的系统,但是为了提高UI的流畅度,也支持多线程操作,但前提是更新UI的线程必须是主线程,在子线程中无法更新UI,会抛出异常,而AsyncTask就是为此而生的。
AsyncTask内部封装的其实就是Thread+Handler,相信在没有AsyncTask之前,很多人都是new个子线程,在子线程中去处理相关的耗时操作,然后通过handler去发送消息到主线程来处理,而AsyncTask的出现,将这一过程变得更加容易。
演变过程:
在1.5中初始引入的时候, AsyncTask 执行( AsyncTask.execute() )起来是顺序的,当同时执行多个 AsyncTask的时候,他们会按照顺序一个一个执行。前面一个执行完才会执行后面一个。这样当同时执行多个比较耗时的任务的时候 可能不是您期望的结果,具体情况就像是execute的task不会被立即执行,要等待前面的task执行完毕后才可以执行。
在android 1.6(Donut) 到 2.3.2(Gingerbread)中,AsyncTask的执行顺序修改为并行执行了。如果同时执行多个任务,则这些任务会并行执行。 当任务访问同一个资源的时候 会出现并发问题.
而在Android 3.0(Honeycomb)以后的版本中,AsyncTask又修改为了顺序执行,并且新添加了一个函数 executeOnExecutor(Executor),如果您需要并行执行,则只需要调用该函数,并把参数设置为并行执行即可。即创建一个单独的线程池(Executors.newCachedThreadPool())。或者最简单的方法法就是使用executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, Params… params),这样起码不用等到前面的都结束了再执行了。
源码分析:
源码分析就不多讲了,想了解的看这篇博客:
http://blog.csdn.net/liyuchong2537631/article/details/49760177
流程原理分析(重点):
这里我重点去总结相关的流程,AsyncTask内部到底是如何实现在doInBackground()中执行耗时操作,如何在onPostExecute、onProgress等当中得到返回的值。
在了解之前,我们必须要准备一些相关的知识,首先关于Callable和Future的相关知识,Callable可以理解为产生结果,Future可以理解为拿到结果。allable接口类似于Runnable,从名字就可以看出来了,但是Runnable不会返回结果,并且无法抛出返回结果的异常,而Callable功能更强大一些,被线程执行后,可以返回值,这个返回值可以被Future拿到,也就是说,Future可以拿到异步执行任务的返回值,下面来看一个简单的例子:
public static void main(String[] args) {
Task work = new Task();
FutureTask<Integer> future = new FutureTask<Integer>(work){
//异步任务执行完成,回调
@Override
protected void done() {
try {
System.out.println("done:"+get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
};
//线程池(使用了预定义的配置)
ExecutorService executor = Executors.newCachedThreadPool();
executor.execute(future);
try {
Thread.sleep(1000);
} catch (InterruptedException e1) {
e1.printStackTrace();
}
//取消异步任务
future.cancel(true);
try {
//阻塞,等待异步任务执行完毕
System.out.println(future.get()); //获取异步任务的返回值
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
//异步任务
static class Task implements Callable<Integer>{
//返回异步任务的执行结果
@Override
public Integer call() throws Exception {
int i = 0;
for (; i < 10; i++) {
try {
System.out.println(Thread.currentThread().getName() + "_"+i);
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
return i;
}
}
上面的代码主要是利用Callable来设置异步任务,通过FutureTask获取线程执行的返回结果(注意:当FutureTask处于未启动或已启动状态时,如果此时我们执行FutureTask.get()方法将导致调用线程阻塞;当FutureTask处于已完成状态时,执行FutureTask.get()方法将导致调用线程立即返回结果或者抛出异常。)。
之所以介绍Callable和FutureTask,是因为AsyncTask内部原理就是利用Callable和FutureTask进行设计的。
public AsyncTask() {
mWorker = new WorkerRunnable<Params, Result>() {
public Result call() throws Exception {
mTaskInvoked.set(true);
Result result = null;
try {
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
//noinspection unchecked
//可以看到doInBackground是在子线程中执行的
result = doInBackground(mParams);
Binder.flushPendingCommands();
} catch (Throwable tr) {
mCancelled.set(true);
throw tr;
} finally {
//postResult中的方法是利用handler和message,将结果发送给
//InternalHandler处理
postResult(result);
}
return result;
}
};
//由于上面的WorkerRunnable是实现了Callable类的,所以可以利用FutureTask运行
mFuture = new FutureTask<Result>(mWorker) {
@Override
protected void done() {
//在done方法中拿到子线程执行后的返回值result
try {
//这个方法里实际调用的还是postResult(result)方法
//在InternalHandler的handleMessage方法中,调用了finish()和
//onProgressUpdate()这两个方法是运行在主线程的,因为
//InternalHandler的构造方法中获取了主线程的looper,所以它是与
//主线程关联的
postResultIfNotInvoked(get());
} catch (InterruptedException e) {
android.util.Log.w(LOG_TAG, e);
} catch (ExecutionException e) {
throw new RuntimeException("An error occurred while executing doInBackground()",
e.getCause());
} catch (CancellationException e) {
postResultIfNotInvoked(null);
}
}
};
}
上面这段代码可以算的上是整个AsyncTask的开始。接下来就看最重要的execute方法:
private static class SerialExecutor implements Executor {
final ArrayDeque<Runnable> mTasks = new ArrayDeque<Runnable>();
Runnable mActive;
//核心方法
public synchronized void execute(final Runnable r) {
mTasks.offer(new Runnable() {
public void run() {
try {
//执行AsyncTask任务
r.run();
} finally {
scheduleNext();
}
}
});
if (mActive == null) {
scheduleNext();
}
}
protected synchronized void scheduleNext() {
if ((mActive = mTasks.poll()) != null) {
THREAD_POOL_EXECUTOR.execute(mActive);
}
}
}
AsyncTask中的execute方法实际上调用的是executeOnExecutor方法,里面传入一个Executor,上面提到了在AsyncTask3.0之后改为了顺序执行,要调用并发执行的方法需要调用executeOnExecutor方法:
@MainThread
public final AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec,
Params... params) {
if (mStatus != Status.PENDING) {
switch (mStatus) {
case RUNNING:
throw new IllegalStateException("Cannot execute task:"
+ " the task is already running.");
case FINISHED:
throw new IllegalStateException("Cannot execute task:"
+ " the task has already been executed "
+ "(a task can be executed only once)");
}
}
mStatus = Status.RUNNING;
onPreExecute();
mWorker.mParams = params;
exec.execute(mFuture);
return this;
}
上面的方法里面需要传入一个执行器Executor,当调用execute()的时候内部其实默认是使用了上面提到的SerialExecutor执行器,这个执行器是顺序执行的,而调用executeOnExecutor()是并发执行的,但是需要我们自己外部去定义一个线程池。
大概的执行流程如下:
Executor.execute(mFuture) -> SerialExecutor.myTasks(队列)
-> (线程池)THREAD_POOL_EXECUTOR.execute
虽然AsyncTask看起来如此强大,但是它依然存在着缺陷:
一、线程池容量不够抛出异常
首先我们得先看下,它的内部是用的什么线程池
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE_SECONDS, TimeUnit.SECONDS,
sPoolWorkQueue, sThreadFactory);
CORE_POOL_SIZE 核心线程数
MAXIMUM_POOL_SIZE 最大线程数量
KEEP_ALIVE 1s闲置回收
TimeUnit.SECONDS 时间单位
sPoolWorkQueue 异步任务队列
sThreadFactory 线程工厂
AsyncTask内部异步任务队列默认是128个,核心线程数和最大线程数是根据CPU计算出来的,这种情况下,当调用executeOnExecutor并发执行任务时,可能会抛出异常,其中有这样的机制:
1、如果当前线程池中的数量小于corePoolSize,创建并添加的任务。
2、如果当前线程池中的数量等于corePoolSize,缓冲队列 workQueue未满,那么任务被放入缓冲队列、等待任务调度执行。
3、如果当前线程池中的数量大于corePoolSize,缓冲队列workQueue已满,并且线程池中的数量小于maximumPoolSize,新提交任务会创建新线程执行任务。
4、如果当前线程池中的数量大于corePoolSize,缓冲队列workQueue已满,并且线程池中的数量等于maximumPoolSize,新提交任务由Handler处理(抛出异常)。
当线程池中的线程大于corePoolSize时,多余线程空闲时间超过keepAliveTime时,会关闭这部分线程。
面对这样的问题该如何解决呢?
AsyncTask对应的线程池ThreadPoolExecutor都是进程范围内共享的,都是static的,所以AsyncTask控制着进程范围内所有的子类的实例,由于这个限制的存在,当使用默认线程池时,如果线程数超过线程池的最大容量,线程池就会爆掉(3.0后默认是串行执行,不会出现这个问题)。但是当并发执行的时候就可能抛出异常,针对这种情况,可以尝试自定义线程池,配合AsyncTask使用。
二、内存泄露
内存泄露根据对AsyncTask生命周期的分析可以发现,如果AsyncTask被声明为Activity的非静态的内部类,那么AsyncTask会保留一个对创建了AsyncTask的Activity的引用。如果Activity已经被销毁,AsyncTask的后台线程还在执行,它将继续在内存里保留这个引用,导致Activity无法被回收,引起内存泄露。
所以还是那句话,在Activity销毁之前cancel掉AsyncTask。即中断AsyncTask的执行,因为此时已经没有必要让AsyncTask继续执行了,结果丢失,屏幕旋转或Activity在后台被系统杀掉等情况会导致Activity的重新创建,之前运行的AsyncTask会持有一个之前Activity的引用,这个引用已经无效,这时调用onPostExecute()再去更新界面将不再生效。
解决方式:第一,在Activity生命周期结束前,去cancel AsyncTask,因为Activity都要销毁了,这个时候再跑线程,绘UI显然已经没什么意义了。第二,如果一定要写成内部类的形式,对context采用WeakRefrence,在使用之前判断是否为空。
三、一个线程,一个异步任务
这个问题将会引入一个新的知识点:HandlerThread,这个将会在后面的博客中总结。