前些天面试被问到一些关于AsyncTask的问题,下面是我整理的一些关于这个的知识。
要了解一个类的使用首先可以通过类前面的介绍来有个大概的了解,然后再有针对的去看自己感兴趣的地方。那么下面进入正题^^
AsyncTask enables proper and easy use of the UI thread. This class allows you
* to perform background operations and publish results on the UI thread without
* having to manipulate threads and/or handlers
这句话相信接触过这个类的同学一看就都明白了,这里我请允许我再重复的描述下: 在ui线程中可以合适和简单的使用AsyncTask。AsyncTask允许我们可以在后台做一些操作(这里指比较耗时的操作)并且将结果发布到UI线程,而不需要人为的操作线程和使用handler.
AsyncTask 是定位是一个助手,非常适合处理一些几秒之内可以完成的短的操作,下面是原文AsyncTasks should ideally be used for short operations (a few seconds at the most.如果你的操作耗时比较长,建议使用Java提供的另外的并发工具类,如:Executor,ThreadPoolExecutor,FutureTask等。
AsyncTask是一个抽象类,所以必须以子类的形式使用。
下面粘贴一个简单的使用
private class DownloadFilesTask extends AsyncTask<URL, Integer, Long> {
protected Long doInBackground(URL... urls) {
int count = urls.length;
long totalSize = 0;
for (int i = 0; i < count; i++) {
totalSize += Downloader.downloadFile(urls[i]);
publishProgress((int) ((i / (float) count) * 100));
// Escape early if cancel() is called
if (isCancelled()) break;
}
return totalSize;
}
protected void onProgressUpdate(Integer... progress) {
setProgressPercent(progress[0]);
}
protected void onPostExecute(Long result) {
showDialog("Downloaded " + result + " bytes");
}
}
从上面我们可以看到子类重写了onPreExecute,doInBackground,onProgressUpdate,onPostExecute等方法。下面简单的记录下这几个方法的功能以及所在线程,就进入分析阶段了。
- onPreExecute()是第一执行的方法,主要可以做一些初始化的操作,所在线程 Main
- doInBackground(),是主要的方法,每个子类都要实现,可以用来做些比较耗时的操作,所在线程 Work 线程
- onProgressUpdate() 主要做一些更新进度的操作,可以重写也可以不重写,看需求,所在线程 Main
- onPostExecute(),一般都会重写,主要是根据后台操作获得数据更新UI,所在线程 Main
执行 new DownloadFilesTask().execute(url1, url2, url3);
取消 cancel(boolean)
操作需要借助isCancelled()方法,一般是在doInBackground方法中添加判断,用法见上面的例子。其中源码中是这样描述的:
To ensure that a task is cancelled as quickly as possible, you should always
* check the return value of {@link #isCancelled()} periodically from
* {@link #doInBackground(Object[])}, if possible (inside a loop for instance.)
讲了一大堆没有的东西,下面进入分析阶段。
首先我们从使用的入口方法,着手。介绍中有说,AsyncTask默认后台其实顺序执行的,但是也支持并行执行。下面我们一个一个分析。
public final AsyncTask<Params, Progress, Result> execute(Params... params) {
return executeOnExecutor(sDefaultExecutor, params);
}
相信这个方法大家都很熟悉,有没有发现这个方法中调用的另一个方法中有一个sDefaultExecutor,让我们跟踪下代码看看它的实现细节
private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;
/**
* 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 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 {
r.run();
} finally {
scheduleNext();
}
}
});
if (mActive == null) {
scheduleNext();
}
}
protected synchronized void scheduleNext() {
if ((mActive = mTasks.poll()) != null) {
THREAD_POOL_EXECUTOR.execute(mActive);
}
}
}
从上面的代码中可以看到,最终实现是SerialExecutor,下面我们接着看下它的实现细节,
可以看到 内部创建了一个ArrayDeque mTasks ,不熟悉这个类,没关系,我们简单看下这个类的描述。
Resizable-array implementation of the {@link Deque} interface.
可以看到它实现了deque接口,接着看deque描述
A linear collection that supports element insertion and removal at
both ends.
意思是是一种支持首尾操作的集合,其实它是对Queue的一个包装。这里用到的隐含的装饰者模式。
那么回到之前的地方,我们接着看execute方法,可以看到该方法加了同步锁,这是由于刚刚介绍的ArrayDeque不是一个线程安全的集合。
mTasks.offer,我们点进去看下介绍,
/**
* Inserts the specified element at the end of this deque.
*
* <p>This method is equivalent to {@link #offerLast}.
*
* @param e the element to add
* @return {@code true} (as specified by {@link Queue#offer})
* @throws NullPointerException if the specified element is null
*/
public boolean offer(E e) {
return offerLast(e);
}
可以看到就是插入一个数据到队列的尾部。
这里我们接着回到入口方法处,然后接着跟踪方法的执行
/**
* Executes the task with the specified parameters. The task returns
* itself (this) so that the caller can keep a reference to it.
*
* <p>This method is typically used with {@link #THREAD_POOL_EXECUTOR} to
* allow multiple tasks to run in parallel on a pool of threads managed by
* AsyncTask, however you can also use your own {@link Executor} for custom
* behavior.
*
* <p><em>Warning:</em> Allowing multiple tasks to run in parallel from
* a thread pool is generally <em>not</em> what one wants, because the order
* of their operation is not defined. For example, if these tasks are used
* to modify any state in common (such as writing a file due to a button click),
* there are no guarantees on the order of the modifications.
* Without careful work it is possible in rare cases for the newer version
* of the data to be over-written by an older one, leading to obscure data
* loss and stability issues. Such changes are best
* executed in serial; to guarantee such work is serialized regardless of
* platform version you can use this function with {@link #SERIAL_EXECUTOR}.
*
* <p>This method must be invoked on the UI thread.
*
* @param exec The executor to use. {@link #THREAD_POOL_EXECUTOR} is available as a
* convenient process-wide thread pool for tasks that are loosely coupled.
* @param params The parameters of the task.
*
* @return This instance of AsyncTask.
*
* @throws IllegalStateException If {@link #getStatus()} returns either
* {@link AsyncTask.Status#RUNNING} or {@link AsyncTask.Status#FINISHED}.
*
* @see #execute(Object[])
*/
@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;
}
上面的几句先先不用管,只是一些对状态的检验和修改,
onPreExecute();这个方法不就是使用时可以实现的方法吗,这也说明这个方法是确实是第一个执行的方法,而且运行线程在方法调用处所在的线程中,由于AsyncTask明确规定调用必须在UI线程,所以这里其实是UI线程。
mWorker.mParams = params; 可以看到我们传入的参数赋值给了一个叫mWorker的mParams,这里可以简单的看下这个mWorker
private static abstract class WorkerRunnable<Params, Result> implements Callable<Result> {
Params[] mParams;
}
可以看到实现了Callable接口,这里我们需要了解下Callable的特点,那么我们接着跟进去
从类头描述可以知道这个是一个可以返回值的Task
A task that returns a result and may throw an exception.
继续回到刚刚的方法处,
exec.execute(mFuture);可以看到我们穿了一个mFuture对象进去,我们跟踪下这个对象,最后在构造方法中找到
/**
* Creates a new asynchronous task. This constructor must be invoked on the UI thread.
*
* @hide
*/
public AsyncTask(@Nullable Looper callbackLooper) {
mHandler = callbackLooper == null || callbackLooper == Looper.getMainLooper()
? getMainHandler()
: new Handler(callbackLooper);
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
result = doInBackground(mParams);
Binder.flushPendingCommands();
} catch (Throwable tr) {
mCancelled.set(true);
throw tr;
} finally {
postResult(result);
}
return 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);
}
}
};
}
这里我们先对FutureTask有个了解:
A cancellable asynchronous computation. This class provides a base
implementation of {@link Future}, with methods to start and cancel
a computation, query to see if the computation is complete, and
retrieve the result of the computation. The result can only be
retrieved when the computation has completed; the {@code get}
methods will block if the computation has not yet completed. Once
the computation has completed, the computation cannot be restarted
or cancelled (unless the computation is invoked using
{@link #runAndReset}).
一个可以被取消的异步计算。提供了对Future的基本实现,可以开始,取消一个计算,并且可以查询计算是否完成,并进而获取到计算结果。
好,带着上面的认识,我们回到SerialExecutor类的实现处看,
public synchronized void execute(final Runnable r) {
mTasks.offer(new Runnable() {
public void run() {
try {
r.run();
} finally {
scheduleNext();
}
}
});
if (mActive == null) {
scheduleNext();
}
}
可以看到,这里的r就是我们穿进去的mFuture,由上面我们对FeatureTask的认识,我们知道在一个线程中任务的执行是从上到下的顺序执行的,那么,这里r.run(),行完,才会走finally中的操作,虽然一进来会走if (mActive == null) {
scheduleNext();
}但是,刚进来时mTaks是空的,再来看
protected synchronized void scheduleNext() {
if ((mActive = mTasks.poll()) != null) {
THREAD_POOL_EXECUTOR.execute(mActive);
}
}
可以看到,第一次进来mActive是获取null,因而这个保证了任务的顺序执行。
了解了顺序执行,那我们在来了解下并行的执行是怎么实现的,虽然Android中这种用法很少,但是万一呢,
其实并的原理很简单,只是换了一个Executor,我们看另一个入口方法,其实刚刚有粘贴代码,为了方便查看,这里请允许我再粘贴一次。
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,这里其实AsyncTask内部为我们提供了另外一个Executor,就是THREAD_POOL_EXECUTOR,我们可以直接将他传进去,就可以试下并行的处理效果。下面是它的实现。
/**
* An {@link Executor} that can be used to execute tasks in parallel.
*/
public static final Executor THREAD_POOL_EXECUTOR;
static {
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE_SECONDS, TimeUnit.SECONDS,
sPoolWorkQueue, sThreadFactory);
threadPoolExecutor.allowCoreThreadTimeOut(true);
THREAD_POOL_EXECUTOR = threadPoolExecutor;
}
可以看到THREAD_POOL_EXECUTOR是个静态的常量,因此可以直接使用。
其内部线程间的切换其实使用的是handler ,仔细看我们之前看过的代码,不难发现,有个postResult的调用
private Result postResult(Result result) {
@SuppressWarnings("unchecked")
Message message = getHandler().obtainMessage(MESSAGE_POST_RESULT,
new AsyncTaskResult<Result>(this, result));
message.sendToTarget();
return result;
}
private Handler getHandler() {
return mHandler;
}
构造方法中有下面一句,
mHandler = callbackLooper == null || callbackLooper == Looper.getMainLooper()
? getMainHandler()
: new Handler(callbackLooper);
private static Handler getMainHandler() {
synchronized (AsyncTask.class) {
if (sHandler == null) {
sHandler = new InternalHandler(Looper.getMainLooper());
}
return sHandler;
}
}
这里相信读者应该可以轻松的自己追踪,就不再赘述了。