大厂Android高频问题:谈谈AsyncTask原理?
前言
AsyncTask原理可以说是Android开发面试高频的一道问题,但总有小伙伴在回答这道问题总不能让面试满意, 本篇就搞清楚面试官问你对AsyncTask原理时,他最想听到的和其实想问的应该是哪些?一个是你是否了解AsyncTask的使用,第二个是有没有研究过AsyncTask的源码,下面我们通过以下几点来剖析这道问题!
- AsyncTask的使用
- AsyncTask的源码解析
- AsyncTask的原理总结
AsyncTask的使用
AsyncTask是一个Android中封装的轻量级异步类,主要可以帮我们实现工作线程与UI线程之间的通信,即在工作线程中执行耗时操作,并将结果传递到主线程,然后在主线程中进行相关的UI操作。使用一般分三步:
- 实现AsyncTask子类,按需重写相应的方法
- 创建AsyncTask子类的实例对象
- 手动调用AsyncTask的execute方法,触发AsyncTask的执行
/**
* 创建AsyncTask:一般通过继承AsyncTask抽象类
* 可指定3个泛型参数的类型,泛型参数分别是Params, Progress, Result
* 按需重载内部提供的方法
*/
private static class MyAsyncTask extends AsyncTask<Integer, Integer, String> {
/**
* 执行后台任务前的操作,用于初始化操作,例如数据重置、显示加载进度条等
*/
@Override
protected void onPreExecute() {
super.onPreExecute();
}
/**
* 真正执行后台耗时任务,例如下载服务器资源
*
* @param params 后台任务的输入参数,类型与AsyncTask第一个泛型一致
* @return
*/
@Override
protected String doInBackground(Integer[] params) {
return null;
}
/**
* 更新后台任务执行进度,回调到UI线程,通过手动调用 publishProgress 方法触发更新
*
* @param progress 执行进度,类型与AsyncTask第二个泛型一致
*/
@Override
protected void onProgressUpdate(Integer[] progress) {
super.onProgressUpdate(progress);
}
/**
* 后台任务执行结果,回调到UI线程
*
* @param result 执行结果,类型与AsyncTask第三个泛型一致
*/
@Override
protected void onPostExecute(String result) {
super.onPostExecute(result);
}
/**
* 后台任务被取消,回调到UI线程
*/
@Override
protected void onCancelled() {
super.onCancelled();
}
}
/**
* AsyncTask的使用
*/
private void testAsyncTask() {
//创建出MyAsyncTask实例
MyAsyncTask myAsyncTask = new MyAsyncTask();
//触发执行异步任务,该方法必须调用于UI线程
myAsyncTask.execute();
}
上面的代码列出了AsyncTask的几个重要方法,注释已经标的比较清楚,整体上的执行顺序如下图。
核心方法 | 调用时机 | 执行线程 |
---|---|---|
onPreExecute | 后台任务执行前,自动调用 | UI线程 |
doInBackground | 后台任务执行时,自动调用 | 工作线程 |
onProgressUpdate | 当手动调用publishProgress方法时 | UI线程 |
onPostExecute | 后台任务执行结束时,自动调用 | UI线程 |
onCanceled | 后台任务被取消时,自动调用 | UI线程 |
除了上面四个方法,AsyncTask还提供了onCancelled()
方法,它同样在主线程中执行,当异步任务取消时,onCancelled()
会被调用,这个时候onPostExecute()
则不会被调用,但是要注意的是,AsyncTask中的cancel()
方法并不是真正去取消任务,只是设置这个任务为取消状态,我们需要在doInBackground()
判断终止任务。就好比想要终止一个线程,调用interrupt()
方法,只是进行标记为中断,需要在线程内部进行标记判断然后中断线程。
使用AsyncTask的注意事项
- 异步任务的实例必须在UI线程中创建,即AsyncTask对象必须在UI线程中创建。
execute(Params... params)
方法必须在UI线程中调用。- 不要手动调用
onPreExecute()
,doInBackground(Params... params)
,onProgressUpdate(Progress... values)
,onPostExecute(Result result)
这几个方法。 - 不能在
doInBackground(Params... params)
中更改UI组件的信息。 - 一个任务实例只能执行一次,如果执行第二次将会抛出异常。
AsyncTask的源码解析
熟悉了AsyncTask的使用,并不能回答得上面试官的这个问题。 既然问的是原理,必然需要进入到源码层,才能一探究竟。
AsyncTask的构造方法
public AsyncTask(@Nullable Looper callbackLooper) {
//创建主线程handler
mHandler = callbackLooper == null || callbackLooper == Looper.getMainLooper()
? getMainHandler()
: new Handler(callbackLooper);
//初始化mWorker变量
//WorkerRunnable其实就是Callable<Result>接口,所以这边实现了call方法
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变量
//mFuture是一个FutureTask对象,通过这个对象便可以取消后台任务以及获取后台任务的执行结果
//在构造方法中将上面的mWorker对象作为参数传入,当mFuture被执行时,便会调用mWorker的call方法
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);
}
}
};
}
构造方法中做了两件事,第一件事是创建了一个主线程的handler,这个主线程的handler很重要,它是AsyncTask线程切换的重要角色。第二件事是初始化了mWorker和mFuture这两个成员变量。并在初始化mFuture的时候将mWorker作为参数传入。mWorker是一个Callable对象,mFuture是一个FutureTask对象,这两个变量会暂时保存在内存中,稍后才会用到它们。 FutureTask实现了Runnable接口。
mWorker中的call()方法执行了耗时操作,即result = doInBackground(mParams);
,然后把执行得到的结果通过postResult(result);
,传递给内部的Handler跳转到主线程中。在这里这是实例化了两个变量,并没有开启执行任务。
AsyncTask的execute方法
public final AsyncTask<Params, Progress, Result> execute(Params... params) {
//execute最终会调用executeOnExecutor方法
return executeOnExecutor(sDefaultExecutor, params);
}
execute
方法调用了executeOnExecutor
方法并传递参数sDefaultExecutor和params。
再看executeOnExecutor
方法:
public final AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec,
Params... params) {
//状态检查。
//不能对正在执行任务的AsyncTask对象或已经执行完任务的AsyncTask对象调用execute方法
//这其实是AysncTask的一个缺点:一个AsyncTask对象只能调用一次execute方法
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)");
}
}
//将状态置为RUNNING
mStatus = Status.RUNNING;
//注意!!!这里调用了onPreExecute方法,验证了我们前面所说的这个方法会执行在后台任务前面
onPreExecute();
//参数值赋值给mWorker.mParams
mWorker.mParams = params;
//关键点!!!
//这里的exec方法传过来的sDefaultExecutor,意思就是调用sDefaultExecutor的execute方法
//参数便是刚刚构造方法中提到的成员变量mFuture
exec.execute(mFuture);
return this;
}
executeOnExecutor
方法首先判断状态,若处于可执行态,则将状态置为RUNNING。然后调用了onPreExecute
方法,交给用户进行执行任务前的准备工作。核心部分在于 exec.execute(mFuture)
。exec即sDefaultExecutor。
sDefaultExecutor串行线程池
查看sDefaultExecutor定义:
//sDefaultExecutor是一个线程池,默认赋值SERIAL_EXECUTOR,是static的,全局唯一
private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;
//SERIAL_EXECUTOR是AsyncTask中线程池的默认实现
public static final Executor SERIAL_EXECUTOR = new SerialExecutor();
再看一下SerialExecutor类
//线程池的具体实现
private static class SerialExecutor implements Executor {
//mTasks是基于数组实现的双向链表,作为SerialExecutor的任务缓存队列
final ArrayDeque<Runnable> mTasks = new ArrayDeque<Runnable>();
//代表当前正在执行的任务,一开始肯定为空
Runnable mActive;
//通过synchronized关键字,可以保证线程安全,即多个AsyncTask会按序入队列
//这里的r就是上面传进来的mFuture
public synchronized void execute(final Runnable r) {
//通过offer方法,往缓存队列中添加一个任务
mTasks.offer(new Runnable() {
public void run() {
try {
r.run();
} finally {
scheduleNext();
}
}
});
//如果mActive为空,执行scheduleNext方法
if (mActive == null) {
scheduleNext();
}
}
protected synchronized void scheduleNext() {
//如果缓存队列mTasks不为空,则调用THREAD_POOL_EXECUTOR.execute
if ((mActive = mTasks.poll()) != null) {
//看名字就知道,又是一个线程池
THREAD_POOL_EXECUTOR.execute(mActive);
}
}
}
sDefaultExecutor
是一个串行线程池,作用在于任务的排队执行。从SerialExecutor
的源码可以看出,mFuture
是插入到mTasks
任务队列的对象。当mTasks
中没有正在活动的AsyncTask
任务,则调用scheduleNext
方法执行下一个任务。若一个AsyncTask
任务执行完毕,则继续执行下一个AsyncTask
任务,直至所有任务执行完毕。通过分析可以发现真正去执行后台任务的是线程池THREAD_POOL_EXECUTOR
。
在这个方法中,有两个主要步骤:
- 向队列中加入一个新的任务,即之前实例化后的mFuture对象。
- 调用
scheduleNext()
方法,调用THREAD_POOL_EXECUTOR执行队列头部的任务。
线程池THREAD_POOL_EXECUTOR
//真正执行任务的线程池
public static final Executor THREAD_POOL_EXECUTOR;
//核心线程数,最多4个,最少2个(注:各个版本的SDK有变化)
private static final int CORE_POOL_SIZE = Math.max(2, Math.min(CPU_COUNT - 1, 4));
//最大线程数为2n+1,n为CPU的数量
private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;
//非核心线程可存活时间为30s
private static final int KEEP_ALIVE_SECONDS = 30;
//阻塞队列为LinkedBlockingQueue,最大容量为128
private static final BlockingQueue<Runnable> sPoolWorkQueue =
new LinkedBlockingQueue<Runnable>(128);
//通过静态代码块进行线程池初始化,并赋值给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;
}
分析到这里,思路基本明朗了,我们来稍微捋一下。AsyncTask调用execute方法,先是经过了SerialExecutor这个线程池,并将自己的成员变量mFuture传入,但是SerialExecutor仅仅只是将mFuture包装成Runnable任务添加至缓存队列,这里有个注意点,入队时使用了synchronized关键字,保证了多个AsyncTask会依次入队。
接着SerialExecutor又从缓存队列里拿出队头任务,交给了另一个线程池THREADPOOLEXECUTOR去真正执行。至此,线程已经切换至工作线程。当真正执行缓存队列里的任务时,会调用mFuture的run方法,mFuture又会调到其构造方法中传入的mWorker的call方法
mWorker = new WorkerRunnable<Params, Result>() {
public Result call() throws Exception {
//mTaskInvoked置为true,表示当前任务已被调用过
mTaskInvoked.set(true);
Result result = null;
try {
//设置线程的优先级为后台
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
//重点!!!
//执行doInBackground方法,执行我们自己定义的后台任务,并将结果存入result中
result = doInBackground(mParams);
Binder.flushPendingCommands();
} catch (Throwable tr) {
mCancelled.set(true);
throw tr;
} finally {
//最终执行postResult方法
postResult(result);
}
return result;
}
};
postResult
private void finish(Result result) {
if (isCancelled()) {
//如果任务被取消了,回调onCancelled方法
onCancelled(result);
} else {
//否则,回调onPostExecute方法
onPostExecute(result);
}
mStatus = Status.FINISHED;
}
该方法向Handler对象发送了一个消息,下面具体看AsyncTask中实例化的Hanlder对象的源码
InternalHandler
/**
* mHandler的默认实现
*/
private static class InternalHandler extends Handler {
public InternalHandler(Looper looper) {
super(looper);
}
@Override
public void handleMessage(Message msg) {
AsyncTaskResult<?> result = (AsyncTaskResult<?>) msg.obj;
switch (msg.what) {
case MESSAGE_POST_RESULT:
//当接收到MESSAGE_POST_RESULT,会调用AsyncTask的finish方法
result.mTask.finish(result.mData[0]);
break;
case MESSAGE_POST_PROGRESS:
//当接收到MESSAGE_POST_PROGRESS,回调onProgressUpdate方法
result.mTask.onProgressUpdate(result.mData);
break;
}
}
}
InternalHandler接收到MESSAGE_POST_RESULT时调用result.mTask.finish(result.mData[0])
;即执行完了doInBackground()方法并传递结果,那么就调用finish()方法。
private void finish(Result result) {
if (isCancelled()) {
//如果任务被取消了,回调onCancelled方法
onCancelled(result);
} else {
//否则,回调onPostExecute方法
onPostExecute(result);
}
mStatus = Status.FINISHED;
}
这里需要确信一点,所有操作都已经处于主线程。mHandler只处理两种消息,一个是结果消息,一个是进度消息。进度消息很简单,直接回调我们AsyncTask 的onProgressUpdate方法。
当接收到结果消息时,会调用finish方法,里面通过isCancelled判断AsyncTask任务是否被取消,若取消了则回调onCancelled方法,否则回调onPostExecute方法。最终,把mStatus设为FINISHED,表示当前这个AsyncTask已经执行完毕。
AsyncTask的原理总结
AsyncTask是基于两个线程池 + 一个主线程的Handler实现了线程间通信。
- 第一个线程池SerialExecutor,它内部维护了一个双向队列,充当的是任务队列以及任务调度的角色,通过synchronized关键字保证多个AsyncTask可以按序排队,并每次从队头取出任务交出执行。
- 第二个线程池THREADPOOLEXECUTOR,它是后台任务的真正执行者,只要有任务过来,它便可以按照线程池的操作流程去执行。主线程的Handler是内部类InternalHandler,它负责将工作线程任务执行的结果与进度通过消息传递到主线程,从而在主线程更新UI。
拓展
常见问题
- 为什么AsyncTask在主线程创建执行?
因为AsyncTask需要在主线程创建InternalHandler,以便onProgressUpdate
, onPostExecute
, onCancelled
可以正常更新UI。
- 为什么AsyncTask不适合特别耗时任务?
AsyncTask实际上是一个线程池。如果有线程长时间占用,且没有空闲,则其他线程只能处于等待状态,会造成阻塞。
- AsyncTask内存泄漏问题
如果AsyncTask被声明为Activity的非静态的内部类,那么AsyncTask会保留一个对创建了AsyncTask的Activity的引用。如果Activity已经被销毁,AsyncTask的后台线程还在执行,它将继续在内存里保留这个引用,导致Activity无法被回收,引起内存泄露。
AsyncTask使用不当的后果
- 生命周期: AsyncTask不与任何组件绑定生命周期,所以在Activity/或者Fragment中创建执行AsyncTask时,最好在Activity/Fragment的onDestory()调用 cancel(boolean);
- 内存泄漏
- 结果丢失: 屏幕旋转或Activity在后台被系统杀掉等情况会导致Activity的重新创建,之前运行的AsyncTask(非静态的内部类)会持有一个之前Activity的引用,这个引用已经无效,这时调用onPostExecute()再去更新界面将不再生效。
看完三件事❤️
如果你觉得这篇内容对你还蛮有帮助,我想邀请你帮我三个小忙:
- 点赞,转发,有你们的 『点赞和评论』,才是我创造的动力。
- 关注公众号 『 小新聊Android 』,不定期分享原创知识
- 同时可以期待后续文章ing🚀
转载自:https://juejin.cn/post/7026699617668448286