您的位置:首页 > 其它

AsyncTask源码分析

2016-03-17 16:25 363 查看
在Android中,主线程是UI线程,当需要根据其他数据进行更新UI时,如果获取数据的操作比较耗时的话,会触发ANR,所以我们应该讲耗时的操作进行异步操作,尤其是请求网络数据的操作应该放在后台线程进行,避免ANR。

而AsyncTask是Android里很常用的异步任务请求方法,AsyncTaks基本用法都会用,网上也要好多教程,就不写例子了。

以前用的时候分析过源码,发现学问还是挺多的,今天总结一下,错误之处的请诸位指正。

分析一个AsyncTask的源码,需要了解的知识:线程池,future模式,无锁操作,handler、looper和Message之间的关系。

[b]1.先看一下AsyncTask的基本成员们:[/b]

public abstract class AsyncTask<Params, Progress, Result> {
private static final String LOG_TAG = "AsyncTask";

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);

/**
* An {@link Executor} that can be used to execute tasks in parallel.
*/
public static final Executor THREAD_POOL_EXECUTOR
= new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE,
TimeUnit.SECONDS, sPoolWorkQueue, sThreadFactory);

/**
* An {@link Executor} that executes tasks one at a time in serial
* order.  This serialization is global to a particular process.
*/
public static final Executor SERIAL_EXECUTOR = new SerialExecutor();

private static final int MESSAGE_POST_RESULT = 0x1;
private static final int MESSAGE_POST_PROGRESS = 0x2;

private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;
private static InternalHandler sHandler;

private final WorkerRunnable<Params, Result> mWorker;
private final FutureTask<Result> mFuture;

private volatile Status mStatus = Status.PENDING;

private final AtomicBoolean mCancelled = new AtomicBoolean();
private final AtomicBoolean mTaskInvoked = new AtomicBoolean();
//后续省略
}


首先我们看到了AsyncTask必备的3大参数Params,Progress,Result。后续我们会再见到它们。

CPU_COUNT是java虚拟机可用的处理器个数,可以通过Runtime类的availableProcessors()方法得到。

看到刚开始的几个静态常量CORE_POOL_SIZE和MAXMUM_POOL_SIZE,就知道AsyncTask实现了一个线程池的机制。

定义了一个线程工场,以便于自定义线程

任务阻塞队列是一个定义了一个大小为128的无界任务队列。

ThreadPoolExecutor定义了一个线程池,核心池大小是CORE_POOL_SIZE,线程池最大容量是MAXIMUM_POOL_SIZE,多余空闲线程的存活时间是KEEP_ALIVE,单位是秒,使用一个大小为128的LinkedBlockingQueue作为任务队列,用一个无锁整型计数的线程工场产生线程。
定义了两个常量MESSAGE_POST_RESULT和MESSAGE_POST_PROGRESS,用于与主线程通信的标签,表示当获取到与主线程交互的数据类型,是进度信息还是结果信息。

定义了两个无锁布尔值作为任务激活或者取消的标志位,无锁操作即CAS,使用于并发情况下数据的更新。

然后看一下这里自定义的SeiralExcuter,实现了Executor接口,用于串行执行任务,并且设置为默认的Executor.

随着Android版本的不同,AsyncTask处理任务默认是串行还是并行几经更迭,至今又改回串行,因为如果有一个UI的状态信息需要不同任务下反复更改,那么并行处理显然会出现更新顺序的问题,很麻烦,所以尽量使用串行的方法。

[b]2.执行任务的方式。[/b]

一个自定义WorkerRunnable,实现了Callable接口

private static abstract class WorkerRunnable<Params, Result> implements Callable<Result> {
Params[] mParams;
}


再看一下AsyncTask的初始化方法:

public AsyncTask() {
mWorker = new WorkerRunnable<Params, Result>() {
public Result call() throws Exception {
mTaskInvoked.set(true);

Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
//noinspection unchecked
Result result = doInBackground(mParams);
Binder.flushPendingCommands();
return postResult(result);
}
};

mFuture = new FutureTask<Result>(mWorker) {
@Override
protected void done() {
try {
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);
}
}
};
}


随后用这个WorkerRunnable实例构造了一个FutureTask实例,这个FutureTask则是JDK中对于future模式的一种实现。future模式是多线程开发中常用的一种设计模式,主要思想是异步调用。当Client想Server请求数据时,如果这个请求操作耗时比较长,那么Server会先立即返回一个FutureData,这个数据是一份虚拟的数据,此时Client就可以去做其他的事情了,过一段时间在调用get方法,如果这个时候Server已经准备好返回数据了,就会返回我们真正需要的数据RealData,否则Client会进入等待状态,直至返回RealData。简单的说,可以把FutureData理解成一份契约,可以用来装配RealData的契约。

这样Client就不用一直等待数据的返回,拿到FutureData后就可以先去做别的事情了,到需要的时候调用get方法获得RealData.

在重复一遍,WorkerRunnable实现了Callable接口,然后用这个实例去构造一个FuntureTask实例。

当准备好future模式后,我们需要决定让任务串行执行还是并行执行,Android官方认为大多数情况下尽量选择串行执行,直接调用execute方法即默认使用SeiralExcuter。

public final AsyncTask<Params, Progress, Result> execute(Params... params) {
return executeOnExecutor(sDefaultExecutor, params);
}


如果一定要并行执行,则调用executeOnExecutor方法,并且设置Executor为前文定义的线程池THREAD_POOL_EXECTOR:

execute方法也是调用executeOnExecutor方法,只是将executor直接设置为默认的SeiralExecutor。

再强调一遍,必须在UI线程中调用执行方法。

[b]3.执行的过程[/b]

直接看一下executeOnExector方法:

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;
}


可以看到,在执行前先检查任务状态,如果正在执行或者已经完成,则抛出异常。处于挂起状态则设置为RUNNING状态,随后执行onPreExecute方法,此方法需要重写,用于执行任务前对UI的动作。

随后将执行任务所需参数赋给我们自定义的WorkerRunnable实例mWorker构造future实例mFuture,执行futureTask,处理任务。最后返回this,是为了保持UI线程对这个AsyncTask的引用。

到这里,我们回头看一下AsyncTask初始化的方法了。 (上文已引用,此处隐藏)

public AsyncTask() {
mWorker = new WorkerRunnable<Params, Result>() {
public Result call() throws Exception {
mTaskInvoked.set(true);

Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
//noinspection unchecked
Result result = doInBackground(mParams);
Binder.flushPendingCommands();
return postResult(result);
}
};

mFuture = new FutureTask<Result>(mWorker) {
@Override
protected void done() {
try {
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);
}
}
};
}


View Code
WorkerRunnable中的Call方法是继承自Callable的方法,这个方法会返回需要构造的实际数据。任务线程被激活后就将其优先级设置为后台线程,将我们任务需要的Param参数传递给doInBackground方法,去在后台执行耗时任务,整个耗时任务包括网络请求和信息处理的过程,在执行该方法的时候,可以激活publishProcess方法来进行主线程进度更新的显示,当处理完数据后返回Result。

在mFuture中重写了done方法,即当mFuture的mWorker处理完数据后,mFuture调用了get方法和postResultIfNotInvoked方法,get方法如下:

public final Result get(long timeout, TimeUnit unit) throws InterruptedException,
ExecutionException, TimeoutException {
return mFuture.get(timeout, unit);
}


调用future模式的get方法,获取到了我们想要的结果数据,之后通过postResultIfNotInvoked方法,将数据发送出去。

private void postResultIfNotInvoked(Result result) {
final boolean wasTaskInvoked = mTaskInvoked.get();
if (!wasTaskInvoked) {
postResult(result);
}
}

private Result postResult(Result result) {
@SuppressWarnings("unchecked")
Message message = getHandler().obtainMessage(MESSAGE_POST_RESULT,
new AsyncTaskResult<Result>(this, result));
message.sendToTarget();
return result;
}


终于到这一步了,可以看到数据处理完毕后,我们把在FutureTask中处理后的数据最终传给了一个message,用来与UI线程进行通信,有了Message,Handler还远么?通过getHandler方法得到一个Handler,这个Handler是自定义的InternalHandler

private static class InternalHandler extends Handler {
public InternalHandler() {
super(Looper.getMainLooper());
}
@SuppressWarnings({"unchecked", "RawUseOfParameterizedType"})
@Override
public void handleMessage(Message msg) {
AsyncTaskResult<?> result = (AsyncTaskResult<?>) msg.obj;
switch (msg.what) {
case MESSAGE_POST_RESULT:
// There is only one result
result.mTask.finish(result.mData[0]);
break;
case MESSAGE_POST_PROGRESS:
result.mTask.onProgressUpdate(result.mData);
break;
}
}
}
private void finish(Result result) {
if (isCancelled()) {
onCancelled(result);
} else {
onPostExecute(result);
}
mStatus = Status.FINISHED;
}


此处Looper.getMainLooper () 获得UI线程的Lopper,准备更新主线程的UI啦!

根据请求的标签信息判定所需行为,当需要更新进度信息时,将数据分发给onProgressUpdate方法去主线程显示进度的更新,当需要传递结果时调用finish方法,此时如果请求已被取消则不发送,如果没被取消则发送至UI线程进行UI信息的更新。

至此,AsyncTask的基本流程就走了一遍了。

总结一下:

利用FutureTask进行异步请求,通常情况下串行处理请求,如果有并行需求则使用线程池应对并发请求,线程池的配置取决于当前虚拟机所能使用的CPU数量。

三大参数:Params任务后台执行所需的参数类型,Progress是如果需要前台进度更新的更新值类型,Result则是任务返回值的类型。

使用execute方法激发异步任务执行,在发出请求之前,使用onPreExecute可以在执行任务前对UI进行标记,随后调用了doInBackground方法,进行后台耗时任务的操作,在这个过程中可以调用publishProgress方法将信息放入message,通过InternalHandler传递,调用onProgressUpdate方法将进度更新信息发送至UI线程,展示进度情况,处理完毕后,InternalHandler将结果数据的message通过onPostExecute发送至UI线程,UI线程根据结果数据进行UI的更新。

所以,在使用AsyncTask时,我们需要重写上面标红的四个方法。最少得重写一个doInBackground方法,一般情况下还得重写一个onPostExecute方法。

这种大的流程框架已经被写好(注意executrOnExecutor方法是final的),我么只需要自己定制流程一部分具体实现的方式,也就是设计模式之中的模板方法模式。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: