标签:
之前我们介绍了Handler的一些基本用法,也解读了Handler的源码。通过Handler我们可以简便的切换到主线程进行UI操作。而AsyncTask的出现使我们不用去关心线程管理和切换的一些细节,我们可以更轻松的去操作UI。
AsyncTask,见名之意,异步任务。允许我们在后台做一些耗时操作,然后切换到主线程更新,而且这一过程变得非常简便。一提到异步任务,我们的第一反应就是多线程。假如我们现在需要去下载一张图片,然后在界面上显示,如果没有AsyncTask,我们的异步操作可能就会这么写:
new Thread(new Runnable() {
@Override
public void run() {
Bitmap bitmap=loadBitmap(url);//loadBitmap是封装好的工具类,用来下载图片,返回bitmap
mHandler.post(new Runnable() {
@Override
public void run() {
mImageView.setImageBitmap(bitmap);
}
});
}
}).start();
那么,AsyncTask怎么写呢?
//AsyncTask下载并展示图片
new MyAsyncTask(mImageView).execute(url);
//MyAsyncTask的实现
public class MyAsyncTask extends AsyncTask<String,Void,Bitmap>{
private ImageView mImageView;
private ProgressDialog mProgressDialog;
public MyAsyncTask(ImageView imageView){
this.mImageView=imageView;
}
@Override
protected void onPreExecute() {
super.onPreExecute();
mProgressDialog=new ProgressDialog(mImageView.getContext());
mProgressDialog.setMessage("正在下载");
mProgressDialog.show();
}
@Override
protected Bitmap doInBackground(String... params) {
retrun loadBitmap(params[0])
}
@Override
protected void onPostExecute(Bitmap bitmap) {
super.onPostExecute(bitmap);
if(bitmap!=null){
mImageView.setImageBitmap(bitmap);
}
}
}
可以看出,AsyncTask的结构要清晰很多。有准备操作的方法onPreExecute
,有专门执行后台操作的方法doInBackground
,也有更新UI的方法onPostExecute
,甚至还有个更新进度的方法onProgressUpdate
,每个方法各司其职。
你可能会想,除了结构清晰,也没看出有其他什么优势啊。那么,我们现在来想象一个场景——列表滑动。假如我们在ListView中去请求图片,直接new线程显然不太合适,快速滑动会开启大量的线程,线程阻塞不说,还会消耗大量系统资源。而AsyncTask由于内部管理着一个线程池,就可以解决这些问题。
AsyncTask不仅使用起来非常简便,此外,工作流程也非常清晰。
doInBackground
编写耗时任务。在了解过AsyncTask的一些用法后,我们不难会产生一些疑问。
execute
方法的可变参数怎么用?execute
方法为什么只能调用一次?execute
方法为什么只能在主线程调用?让我们带着疑问,去源码里探索吧。
一看名字就知道,线程池,JAVA线程管理的一个api。通过下面的构造方法就可以建立一个线程池。
/**
* @param corePoolSize 核心线程数,即使空闲也一直存在,除非给核心线程也设了超时时间。
* @param maximumPoolSize 线程池的最大线程数
* @param keepAliveTime 除核心线程外的其他线程等待超时时间,超时就会杀死。
* @param unit 时间单位
* @param workQueue 工作队列,多余的任务就会放在这个队列中,等待被执行。
* @param threadFactory 线程工厂,用来创建线程
* @param handler 当工作队列满后,再添加任务时的处理策略,处理策略有如下几种,DiscardOldestPolicy:挤掉最旧的任务。DiscardPolicy:废弃当前被添加的任务,CallerRunsPolicy:直接在添加任务的那个线程执行,AbortPolicy:直接抛异常。默认的处理策略时AbortPolicy。
*/
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
我们常用的Executors.newFixedThreadPool(int nThreads)
等创建线程池的方法,其内部也是调用了这个构造方法。
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
Queue队列,继承自集合(Collection),通常遵循着先进先出原则(FIFO)(除了优先级队列和后进先出队列)对应的api如下。
操作 | 抛出异常(已满或已空) | 返回空值(已满或已空) |
---|---|---|
添加元素 | add(e) | offer(e) |
取出一个元素 | remove() | poll() |
查看一个元素 | element() | peek() |
我们一般采用offer,poll,peek方法来操作元素,以免抛出异常。
Deque双端队列,继承自Queue,支持双端移除和插入。因继承自Queue,所以可以直接当作Queue队列来用,此时遵守FIFO原则,当然,可以显示指明操作队头还是队尾offerFirst(e),pollFirst(),offerLast(e),pollLast()..
Deque的api对照如下
Queue方法 | 同等的Deque方法 |
---|---|
add(e) | addLast(e) |
remove() | removeFirst() |
element() | getFirst() |
offer(e) | offerLast(e) |
poll() | pollFirst() |
peek() | peekFirst() |
void run()
,该方法没有返回值。public interface Runnable {
public void run();
}
V call()
,该方法含有返回值。public interface Callable<V> {
V call() throws Exception;
}
public interface Future<V> {
boolean cancel(boolean mayInterruptIfRunning);
boolean isCancelled();
boolean isDone();
V get() throws InterruptedException, ExecutionException;
V get(long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException;
}
//创建一个Callable对象,在实现方法中写耗时操作
Callable callable= new Callable<Bitmap>() {
@Override
public Bitmap call() throws Exception {
return loadBitmap(url);
}
};
//使用FutureTask包装Callable,使其可控制
FutureTask<Bitmap> futureTask=new FutureTask<Bitmap>(callable);
//提交到线程中
new Thread(futureTask).start();
//阻塞直到获取结果,如果不想阻塞,就重写done方法,在里面get()
Bitmap bitmap= futureTask.get();
doInBackground
方法)这一步比较简单,就是在doInBackground
里面写一些耗时操作,比如网络访问。这里不作演示。
execute
方法)AsyncTask的使用比较简便,一般都是以new MyAsyncTask(..).execute(url)
的形式调用。那么,execute
到底做了什么呢?在介绍之前,我们先来看一下AsyncTask的构造方法。
public AsyncTask() {
mWorker = new WorkerRunnable<Params, Result>() {
public Result call() throws Exception {
mTaskInvoked.set(true);//设置任务已被调用的标识
//...
//省略了部分代码
Result result = doInBackground(mParams);//耗时操作
//...
//省略了部分代码
return postResult(result);
}
};
mFuture = new FutureTask<Result>(mWorker) {
@Override
protected void done() {
//...
//省略了部分代码
}
};
}
看完上面的代码,可能有点手足无措。不要慌张,慢慢来。
先来看看mWorker
,里面的东西是不是非常眼熟,跟Callable
的写法简直太像了。事实上,的确继承自Callable
。而doInBackground
只是Callable
实现方法中的一部分。执行完过后调用了postResult
来传递数据,postResult
做了什么暂且不做研究,后面再做介绍。WorkerRunnable的代码如下,仅仅是定义了一个变量用于保存参数。
private static abstract class WorkerRunnable<Params, Result> implements Callable<Result> {
Params[] mParams;
}
再来看看mFuture
,是不是也非常眼熟,包装了一下mWorker
使其可控制。
看完上面两段源码后,有没有发现跟我上面举的FutureTask
的例子有点像,那么,接下来要干嘛相信你已经很清楚了,没错,就是提交到线程或者线程池中去执行这段代码。只要被执行就能获得结果完成使命了。
真是万事俱备,只欠线程池啊。
execute
方法现在回过来看一下execute
方法,看它到底做了什么事。
public final AsyncTask<Params, Progress, Result> execute(Params... params) {
return executeOnExecutor(sDefaultExecutor, params);
}
可以看到内部经过了一次包装,且传入了一个默认的Executor
,我们来看一下executeOnExecutor
的源码。
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;
}
我们可以发现在这个方法中直接调用了onPreExecute
,而onPreExecute
是用来干嘛的呢?做一些提交之前的操作,比如显示一个进度框等UI操作,这也就暗示了,如果要在onPreExecute
中做UI操作,则必须在主线程中调用execute
方法。此外我们也能隐隐约约知道为什么execute
只能提交一次,因为,一旦提交过任务,就会将状态设置为Status.RUNNING
,完成后就变为Status.FINISHED
,按照源码逻辑可知,再也回不到Status.PENDING
状态,所以不能再提交第二次,否则会抛异常。那么,为什么这么设计呢?是因为FutureTask
的缘故,FutureTask
的计算是不能被重新调用的(除非调用的是runAndReset()
)。
Status
是一个枚举类,如下。
public enum Status {
PENDING,//还未调用
RUNNING,//调用中
FINISHED,//完成
}
继续阅读源码,发现了这一行exec.execute(mFuture);
莫非就在这里提交给了线程池去执行?满怀期待的找到sDefaultExecutor
的源码实现一看:
private static class SerialExecutor implements Executor {
final ArrayDeque<Runnable> mTasks = new ArrayDeque<Runnable>();
Runnable mActive;
public synchronized void execute(final Runnable r) {
//将Runable对象添加到双端队列里
mTasks.offer(new Runnable() {
public void run() {
try {
r.run();
} finally {
//执行完过后从队列取出下一个任务添加到线程池里执行。
scheduleNext();
}
}
});
//第一次执行,走这里
if (mActive == null) {
scheduleNext();
}
}
protected synchronized void scheduleNext() {
//取出一个任务添加到线程池里执行。
if ((mActive = mTasks.poll()) != null) {
THREAD_POOL_EXECUTOR.execute(mActive);
}
}
}
What?这个线程池不是用来执行任务的?心中一万只草泥马奔腾而过。没办法,只能继续阅读源码。发现了ArrayDeque
双端队列,一看到队列我们的第一反应就是要排队。可是排队干嘛呢?干嘛不直接提交到线程池里面去执行。从源码可以看出使用了ArrayDeque
双端队列来存放Runnable
对象,你可能会疑问,不是FutureTask
吗,怎么变成Runnable
了,那是因为FutureTask
也实现了Runnable
接口,莫急,接下来就是排队的核心代码,为了使看起来更直观,修改成如下形式:
new Runnable() {
public void run() {
try {
r.run();
} finally {
//取出一个任务添加到线程池里执行。
if ((mActive = mTasks.poll()) != null) {
THREAD_POOL_EXECUTOR.execute(mActive);
}
}
}
}
从上面的源码可以看出,使用了Runnable
包装了原来的FutureTask
的run
方法,巧妙的使用finally
来保证任务的串行执行。
看到这里恍然大悟SerialExecutor
原来是一个用来排队的线程池。THREAD_POOL_EXECUTOR
才是我们一直在苦苦寻找的用于执行任务的线程池。exec.execute(mFuture)
先把任务保存到排队线程池的队列中,然后串行提交到执行任务的线程池。也就是说,每执行完一个任务后,才会从队列中取出下一个任务到线程池中。
看到这里终于可以松一口气了,算是看到了提交到线程池的代码,第二步任务终于完成了。
THREAD_POOL_EXECUTOR.execute(..);
)线程池的内部工作逻辑这里就不深究了,我们只需知道目前为止我们已经成功将任务提交到线程池中,只需等待处理,便可获得结果。我们来看看线程池本尊是怎么实现的。源码如下。
//线程配置
private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
private static final int CORE_POOL_SIZE = CPU_COUNT + 1;
private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;
private static final int KEEP_ALIVE = 1;
//线程工厂
private static final ThreadFactory sThreadFactory = new ThreadFactory() {
private final AtomicInteger mCount = new AtomicInteger(1);
public Thread newThread(Runnable r) {
return new Thread(r, "AsyncTask #" + mCount.getAndIncrement());
}
};
//工作队列
private static final BlockingQueue<Runnable> sPoolWorkQueue =
new LinkedBlockingQueue<Runnable>(128);
/**
*线程池的构造方法
*/
public static final Executor THREAD_POOL_EXECUTOR
= new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE,
TimeUnit.SECONDS, sPoolWorkQueue, sThreadFactory);
线程池的实现是一些很常规的代码,没有什么可介绍的,唯一需要关心的问题就是线程池开多大合适。核心线程数为N+1,最大线程数为2N+1。
onPostExecute
)经过线程执行完毕,终于可以获取结果呢。还记得AsyncTask的构造方法吗?不记得也不要紧,我们再看一遍。
public AsyncTask() {
mWorker = new WorkerRunnable<Params, Result>() {
public Result call() throws Exception {
mTaskInvoked.set(true);//设置任务已被调用
//...
//省略了部分代码
Result result = doInBackground(mParams);//耗时操作
//...
//省略了部分代码
return postResult(result);
}
};
//...
//省略了部分代码
}
由源码可知,在处理完毕后会调用postResult
,postResult
的源码如下
private Result postResult(Result result) {
@SuppressWarnings("unchecked")
Message message = getHandler().obtainMessage(MESSAGE_POST_RESULT,
new AsyncTaskResult<Result>(this, result));
message.sendToTarget();
return result;
}
AsyncTaskResult
是一个包装类,里面保存了结果数据和AsyncTask对象。
private static class AsyncTaskResult<Data> {
final AsyncTask mTask;
final Data[] mData;
AsycTaskResult(AsyncTask task, Data... data) {
mTask = task;
mData = data;
}
}
此外,似乎又看到了熟悉的身影Message
和Handler
。看到这里终于明白了,内部通过Handler来进行切换线程,然后更新UI。如果我没猜错,在Handler的handleMessage
方法内,一定直接或者间接的调用了onPostExecute
。
为了验证我的猜想是否正确,顺藤摸瓜找到了Handler的实现。
private static Handler getHandler() {
synchronized (AsyncTask.class) {
if (sHandler == null) {
sHandler = new InternalHandler();
}
return sHandler;
}
}
//Handler的实现
private static class InternalHandler extends Handler {
public InternalHandler() {
super(Looper.getMainLooper());
}
@Override
public void handleMessage(Message msg) {
AsyncTaskResult<?> result = (AsyncTaskResult<?>) msg.obj;
switch (msg.what) {
case MESSAGE_POST_RESULT://这个是结果
result.mTask.finish(result.mData[0]);
break;
case MESSAGE_POST_PROGRESS://这个是进度
result.mTask.onProgressUpdate(result.mData);
break;
}
}
}
找到我们刚刚发出的消息类型MESSAGE_POST_RESULT
。发现没有直接调用onPostExecute
而是调用了AsyncTask的finish方法,继续追溯源码。
private void finish(Result result) {
if (isCancelled()) {
onCancelled(result);
} else {
onPostExecute(result);
}
mStatus = Status.FINISHED;
}
看完源码才恍然大悟。原来有两种类型啊,一种是正常执行完毕的,回调onPostExecute
,还有一种是被取消了,回调onCancelled
。那么问题来了,怎么取消一个任务?
只需调用AsyncTask的cancel(boolean)
方法即可。cancel会调用FutureTask的cancel方法去中断任务。
//取消一个任务
public final boolean cancel(boolean mayInterruptIfRunning) {
mCancelled.set(true);//设置一个取消标识
return mFuture.cancel(mayInterruptIfRunning);//取消一个任务
}
//获取取消的状态
public final boolean isCancelled() {
return mCancelled.get();//获取取消标识
}
从源码可以看出,取消任务后是不会再走onPostExecute
的,只走onCancelled
方法。
看到这里。算是把AsyncTask的源码给看的差不多了。
doInBackground
中进行调用。然后由Handler传递到主线程中,重写onProgressUpdate
接受即可。protected final void publishProgress(Progress... values) {
if (!isCancelled()) {
getHandler().obtainMessage(MESSAGE_POST_PROGRESS,
new AsyncTaskResult<Progress>(this, values)).sendToTarget();
}
}
private static class InternalHandler extends Handler {
public InternalHandler() {
super(Looper.getMainLooper());
}
@Override
public void handleMessage(Message msg) {
AsyncTaskResult<?> result = (AsyncTaskResult<?>) msg.obj;
switch (msg.what) {
//省略了部分源码
case MESSAGE_POST_PROGRESS:
result.mTask.onProgressUpdate(result.mData);
break;
}
}
}
AsyncTask的execute
方法的可变参数怎么用?
从源码来看,execute
的可变参数全部传入进Result result = doInBackground(mParams);
,也就是说,自己可以结合场景巧妙使用。可以传多个url,批量下载小文件等。
AsyncTask是串行执行还是并行执行?
从以上源码分析(Android5.0)得出,是串行执行。但是官方api文档指出。Android1.6之前和Android3.0之后是串行执行。在这两个版本之间采用的是并行执行。于是找了份2.3的源码,果真如此,可以发现没有了排队线程池的踪影。源码如下:
//execute的源码如下
public final AsyncTask<Params, Progress, Result> execute(Params... params) {
if (mStatus != Status.PENDING) {
//..
//省略了部分源码
}
mStatus = Status.RUNNING;
onPreExecute();
mWorker.mParams = params;
sExecutor.execute(mFuture);
return this;
}
//sExecutor的实现如下,可以看出直接就是一个任务线程池,没有用于排队的线程池。
private static final ThreadPoolExecutor sExecutor = new ThreadPoolExecutor(CORE_POOL_SIZE,
MAXIMUM_POOL_SIZE, KEEP_ALIVE, TimeUnit.SECONDS, sWorkQueue, sThreadFactory);
executeOnExecutor(Executor exec, Params... params)
,如上所示,直接传入一个线程池进行执行。经过解读了AsyncTask的源码,从侧面可以看出,Handler不可动摇的地位,毋容置疑。
如果没有看过Handler源码的,可以阅读这篇 Handler消息机制 源码解读加深理解。
标签:
原文地址:http://blog.csdn.net/maplejaw_/article/details/51441312