AsyncTask全解析
AsyncTask全解析
1、AsyncTask介绍
回顾上一节的问题:更新UI的方式有哪些?答案有:
- Activity.runOnUiThread(Runnable)
- View.post(Runnable),View.postDelay(Runnable, long)
- Handler
- AsyncTask
- Rxjava
- LiveData
本文的主角AsyncTask,本人现在用得比较少,更多用的是Rxjava。同时面试的时候知道AsyncTask是什么?怎么用?有什么不足之处或者需要注意什么?懂了这三个问题一般就可以了。至于看源码,可当作进阶,个人建议还是有必要学习一下的。那么AssyncTask究竟是什么呢?
AsyncTask是一个轻量级的异步任务类,可以在线程池里执行比较耗时的后台任务,然后把执行的进度和最后的结果传递给主进程,并在主进程中更新UI。使用容易,而且好理解。
1.1 AsyncTask的使用
同样从使用入手,看看代码。以下代码以往文件里写数据为例:
private class FileTestTask extends AsyncTask<Void, Long, Boolean> { //1
@Override
protected void onPreExecute() {
//初始化一些数据
initProcessInfo();
}
@Override
protected Long doInBackground(Void... integers) {
//创建目录
...
while (mTotalWriteSize <= mInitFreeDataValue) {
...
mTotalWriteSize += writeSize / 1024 / 1024; // M
//2:反馈当前任务进度
publishProgress(mTotalWriteSize);
}
return true;
}
@Override
protected void onProgressUpdate(Long... progresses) { //3
if (progresses != null && progresses.length >= 1) {
updateProcessInfo(progresses[0]); //更新进度条
}
}
@Override
protected void onPostExecute(Boolean result) { //4
if (result) {
updateContentInfo(R.string.text_show_result_test_over);
} else {
updateContentInfo(R.string.text_show_result_fail);
}
}
}
//主线程中使用自定义的FileTestTask
if (mFileTestTask == null) {
mFileTestTask = new FileTestTask();
mFileTestTask.execute();
}
1.2 核心方法
onPreExecute():字面上就是“提前执行”,会在后台任务开始前调用,并在主线程中执行,因此常用于初始化一些数据和界面。
doInBackground(Params…):字面上是“在背后处理”,就是在后台处理所有耗时的任务,这些任务都会在子线程中处理。有两个需要注意的点:
- 不能在这个函数进行UI操作,因为该函数是在子线程运行的。如果需要更新当前任务完成进度条,需要运行publishProgress(Progress…),如上面的注释2,而最终是调用onProgressUpdate()去更新进度条;
- 执行完之后可以通过return将执行的结果返回,返回的结果会在onPostExecute()里执行,如注释4,我这边只是更新了一个文字内容。
onProgressUpdate(Progress…):如上一段所述,调用publishProgress(Progress…)后会把参数(如当前任务处理的进度)传递给onProgressUpdate()并被调用,因为该方法在主进程进行,因此可以进行UI修改。
onPostExecute():如上所述,doInBackground()会把执行结果返回给 onPostExecute()并被调用,该方法也在主线程运行,可以做一些结尾的动作,如提示任务完成等。
上面几个核心方法的调用顺序为: onPreExecute() –> doInBackground() –> publishProgress() –> onProgressUpdate() –> onPostExecute()。
如果不需要更新进度则为onPreExecute() –> doInBackground() –> onPostExecute()。
1.3 三个泛型类参数
在1.1小节案例代码里的注释1 可以看到AsyncTask<Void, Long, Boolean>,其原型是 AsyncTask<Params, Progress, Result>,这三个泛型类参数含义如下:
- Params:开始异步任务执行时传入的参数类型,对应doInBackground(),本案例是Void,代表不需要传入参数;
- Progress:异步任务执行过程中,返回下载进度值的类型,对应onProgressUpdate(),本案例是Long,代表目前已经写入的字节数;
- Result:异步任务执行完成后,返回的结果类型,对应onPostExecute(),本案例返回的是Boolean类型,true代表任务完成;
2、AsyncTask使用心得
2.1 AsyncTask的使用注意事项
从第1小节,我们了解了AsyncTask的使用方法和步骤,现在再强调几个注意点,不仅面试官可能会问到,平时自己工作中也要多注意:
- AsyncTask的实例需要在UI线程也就是主线程中创建,调用execute()开始执行任务时,也要在主线程执行;
- 不要手动调用onPreExecute(),doInBackground(Params… params),onProgressUpdate(Progress… values),onPostExecute(Result result)这几个方法;
- 只有onPreExecute(),onProgressUpdate(Progress… values),onPostExecute(Result result)可以执行UI相关操作,doInBackground(Params… params)不能更改UI界面;
- 一个任务实例只能执行一次execute(),执行多次会报异常。
2.2 AsyncTask的不足之处
- 生命周期
AsyncTask不与任何组件绑定生命周期,因此会出现组件生命周期结束了,但AsyncTask还存在,所以需要在onDestroy()调用 cancel(boolean),手动结束AsyncTask;
- 内存泄漏
和之前说的Handler造成内存泄露的原因是一样的。如果AsyncTask被声明为Activity的非静态的内部类,如本文使用代码案例一样,那么AsyncTask会保留一个所在Activity的引用。如果Activiy被销毁,此时因此AsyncTask保留了它的引用,导致Activity无法被回收,引起内存泄露。
3.) 结果丢失
如果屏幕旋转或Activity在后台被系统杀掉等情况会导致Activity重新创建,如果之前运行的AsyncTask是非静态的内部类,此时就会持有之前旧的Activity的引用,而旧的Activity已经销毁了,这时调用onPostExecute()再去更新界面将不再生效,无法更新到新的Activity到界面。
3、 源码分析(进阶)
如果看完前面2节,去面试已经没问题了,对AsyncTask的源码分析属于进阶,如果有时间,建议看看源码,因为整个第3节的源码代码关联性很强,为了方便说明,所有代码块的关键代码行的注释码都是连贯的。
回顾下AsyncTak的使用方法:我们只要执行new FileTestTask().execute(),就会自动执行onPreExecute(),doInBackground(Params… params),onProgressUpdate(Progress… values),onPostExecute(Result result)这几个方法。这是怎么做到的呢?
带着这个思路,按顺序,我们先看看在初始化一个AsyncTask时,调用函数都做了什么。
public AsyncTask() {
mWorker = new WorkerRunnable<Params, Result>() {//1
public Result call() throws Exception {
mTaskInvoked.set(true);
Result result = null;
try {
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
//noinspection unchecked
result = doInBackground(mParams); //2
Binder.flushPendingCommands();
} catch (Throwable tr) {
mCancelled.set(true);
throw tr;
} finally {
postResult(result); //3
}
return result;
}
};
mFuture = new FutureTask<Result>(mWorker) { //4
@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);
}
}
};
}
我们可以看到这仅仅是初始化了两个变量,mWorker和mFuture,并在初始化mFuture的时候将mWorker作为参数传入,如注释1和注释4。此时仅仅完成初始化,什么都没做。不过记住这个注释1的call()函数,里面有注释2和注释3两个关键函数,后面是如何调用到的呢?接着执行execute()就开始执行任务了,来看看execute()的源码:
public final AsyncTask<Params, Progress, Result> execute(Params... params) {
return executeOnExecutor(sDefaultExecutor, params); //5
}
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(); //6
mWorker.mParams = params;
exec.execute(mFuture); //7
return this;
}
注释5,execute()调用executeOnExecutor(sDefaultExecutor, params),终于在注释6,看到了熟悉的函数onPreExecute()!执行完onPreExecute(),即完成了一些初始化工作后,调用注释7:exec.execute(mFuture)开始执行耗时任务,并把上面注释4初始化的mFuture实例传入。
进一步看看注释7这个exec是什么?看下代码调用,就知道exec是注释5的sDefaultExecutor,因此注释7的exec.execute(mFuture)实际上是执行sDefaultExecutor.execute(mFuture)。再追述源码可以看到sDefaultExecutor是SerialExecutor类,具体源码如下:
public static final Executor SERIAL_EXECUTOR = new SerialExecutor();
private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;
private static class SerialExecutor implements Executor {
final ArrayDeque<Runnable> mTasks = new ArrayDeque<Runnable>(); //8
Runnable mActive; //9
public synchronized void execute(final Runnable r) { //10
mTasks.offer(new Runnable() { //11
public void run() {
try {
r.run(); //12
} finally {
scheduleNext(); //13
}
}
});
if (mActive == null) {
scheduleNext();
}
}
protected synchronized void scheduleNext() {
if ((mActive = mTasks.poll()) != null) { //14
THREAD_POOL_EXECUTOR.execute(mActive); //15
}
}
}
可以看出这个SerialExecutor是静态内部类,因此每个AsyncTask都共有。SerialExecutor维护了一个队列,也就是注释8的mTasks。在注释10通过加锁保证AsyncTask的任务是串行的。好好看注释10这个execute()函数,主要做了两件事:
- 在注释11向队列mTask加入一个新的任务,也就是注释7传入的mFuture对象;
- 调用注释 13的scheduleNext()方法,进而调用注释14的THREAD_POOL_EXECUTOR.execute(mActive)来执行队列头部任务。
所以,SerialExecutor只是保证任务是串行的,真正执行任务还是交给THREAD_POOL_EXECUTOR。继续追代码,看看THREAD_POOL_EXECUTOR是什么?
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE_SECONDS, TimeUnit.SECONDS,
sPoolWorkQueue, sThreadFactory);
threadPoolExecutor.allowCoreThreadTimeOut(true);
THREAD_POOL_EXECUTOR = threadPoolExecutor; //16
注释16可以看到THREAD_POOL_EXECUTOR就是一个线程池,注释15调用了线程池的execute()方法,执行具体的耗时任务,而注释15的mActive是从注释14的mTasks队列获取的Runnable,这个Runnable又是注释11加入队列的,注释11这个Runnable就是注释7传入的mFuture,接着这个mFuture又是注释4传入的mWorker构造的。通过倒推,就知道注释15执行的具体的耗时任务其实就是注释1中的call()函数。终于,调用到了注释1,先执行完doInBackground()方法处理后台耗时任务,又执行postResult()方法返回处理结果,一切都和我们想的一样!
最后看看怎么结束任务吧。
private Result postResult(Result result) {
@SuppressWarnings("unchecked")
Message message = getHandler().obtainMessage(MESSAGE_POST_RESULT,
new AsyncTaskResult<Result>(this, result)); //17
message.sendToTarget();
return result;
}
没错,就是Handler机制。AsyncTask内部又一个Handler对象,这也是为什么2.1小节说AsyncTask需要在主线程创建,因为Handler需要在主线程创建:
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]); //18
break;
case MESSAGE_POST_PROGRESS:
result.mTask.onProgressUpdate(result.mData); //19
break;
}
}
}
代码非常清晰,如果收到MESSAGE_POST_PROGRESS消息,在1.1小节注释2的地方发送,就执行注释19,又是我们熟悉的onProgressUpdate(),如果收到MESSAGE_POST_RESULT消息,就执行注释18的finish()。
private void finish(Result result) {
if (isCancelled()) {
onCancelled(result); //20
} else {
onPostExecute(result); //21
}
mStatus = Status.FINISHED;
}
如果按2.2小节说的调用cancel(boolean),则会回调注释20,否则执行注释21:onPostExecute(result)。
到此结束源码分析。相信文章开头4个问题大家都理解了。
4、总结
AsyncTask中有两个线程池(SerialExecutor和THREAD_POOL_EXECUTOR)和一个Handler(InternalHandler),其中线程池SerialExecutor用于任务的排队,而线程池THREAD_POOL_EXECUTOR用于真正地执行任务,InternalHandler用于将执行环境从线程池切换到主线程。