AsyncTask
AsyncTask可以非常方便、简单的在UI线程中使用,它帮助我们在不使用Thread和Handler的情况下,在后台执行一个后台(异步)任务,并且把执行结果通知到UI线程中。也就是说,使用AsyncTask可以极大的简化我们进行后台任务的操作,使用它,我们不必关心工作线程是如何启动的,也不必关心,工作线程和UI线程之间的通信问题,这些都被AsyncTask通过内部封装实现了,我们只需要按接口规范使用它即可。
AsyncTask适用于短时的后台(异步)任务(最多几秒钟),它并不适合时间较长的后台任务,如果我们想在后台线程执行长时间的异步任务,可以使用java.util.concurrent并发工具包中的Executor、ThreadPoolExecutor和FutureTask等。
AsyncTask的声明和执行
AsyncTask执行后台任务,是由后台线程执行具体的任务,最后把结果发布到UI线程。
一个后台任务的声明由3种泛型类型:Params、Progress和Result,以及4个过程:onPreExecute、doInBackground、onProgressUpdate和onPostExecute构成。
我们来看使用AsyncTask有哪些要求?
- 必须创建AsyncTask的子类,以它的子类形式来使用。
- 必须在子类中至少实现doInBackground()回调方法,通常我们还会实现onPostExecute()方法。
- AsyncTask类必须在UI线程中被加载(Android 4.1之后自动执行)。
- AsyncTask实例对象,必须在UI线程中创建。
- 必须在UI线程中调用execute()来运行任务。
3种泛型
Params:doInBackground方法的参数类型,它会在任务执行之前发送给后台任务。
Progress:onProgressUpdate方法的参数类型,它是在任务执行期间发布的进度类型。
Result:onPostExecute方法的参数类型,它是后台任务执行结束后的结果类型。
示例:
private class DownloadFilesTask extends AsyncTask<String, Integer, Long> {}
示例中的Params、Progress和Result分别是String、Integer和Long。
4个步骤
AsyncTask后台任务执行过程,将会经历4个步骤:
onPreExecute():UI线程中执行。它发生在后台任务开始执行之前,我们可以通过它来进行任务的配置,比如开始展示进度条等。
doInBackground(Params…):后台线程中执行。它在onPreExecute完成之后立即执行,它会执行较长时间的后台运算任务。运算结果必须由该步骤return,发布给onPostExecute方法。我们也可以在该过程中,调用一次或多次publishProgress(Progress…)方法来发布执行进度信息,进度信息最终会发布到UI线程中,成为onProgressUpdate(Progress…)的参数。
onProgressUpdate(Progress…):UI线程中执行。它是在调用publishProgress(Progress…)之后执行的,它通常用来展示后台任务的执行进度。
onPostExecute(Result):UI线程中执行。它是在后台任务执行结束之后调用的,由doInBackground(Params…)方法return的结果作为参数。
任务的取消
AsyncTask后台任务执行中的任意时刻,都可以调用cancel(boolean)来进行取消操作。
cancel方法需要关注以下几点:
- 执行cancel方法之后,调用isCancelled()将返回true。
- 执行cancel方法之后,onCancelled(java.lang.Object)方法将会在doInBackground(java.lang.Object[]) return之后执行(代替了onPostExecute方法)。
- 我们应该尽可能及时的检测到后台任务的取消。可以在doInBackground方法中,使用isCancelled()来定时执行检测工作,一旦发现任务取消,立即取消现有操作,执行return。
AsyncTask演示Demo
示例代码,创建了一个DownloadFilesTask类,继承于AsyncTask,用于执行后台任务。
private class DownloadFilesTask extends AsyncTask<String, Integer, Long> {//3个泛型分别为:String, Integer, Long
protected Long doInBackground(String... urls) {//参数为字符串列表
int count = urls.length;
long time = 0;
for (int i = 0; i < count; i++) {
try {
Thread.sleep(1000);//示例任务,执行线程休眠1秒,模拟耗时任务
} catch (InterruptedException e) {
e.printStackTrace();
}
time += 1000;
publishProgress((int) (((i + 1) / (float) count) * 100));//更新进度
if (isCancelled()) break;//检测是否执行过程中取消了任务
}
return time;
}
protected void onProgressUpdate(Integer... progress) { //更新进度。这里的参数是Integer对象
mTextView.setText("进度:" + progress[0]);
}
protected void onPostExecute(Long result) { //执行结果回调
mTextView.setText("time : " + result + " 毫秒");
}
}
异步任务准备好了,我们创建一个DownloadFilesTask类的实例,调用execute()方法即可执行:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
new DownloadFilesTask().execute("url1", "url2", "url3", "url3");
}
例子很简单,但是也完整的展示了AsyncTask的基本用法。
接下来我们来分析AsyncTask的实现原理。
AsyncTask源码解析
我们接下来分析AsyncTask的源码,看下它的实现原理。
在Demo中,我们调用AsyncTask的实例对象的execute方法来实现调用的,那么我们就从execute方法作为入口点来分析。
AsyncTask的execute方法:
@MainThread
public final AsyncTask<Params, Progress, Result> execute(Params... params) {
return executeOnExecutor(sDefaultExecutor, params);
}
这里调用了executeOnExecutor方法,参数sDefaultExecutor是一个线程池,作为默认线程池。
executeOnExecutor方法:
@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;
}
逻辑解析
- 该方法在UI线程中执行。
- 属性mStatus用来判断当前任务的状态,当任务正在运行或已经结束了,会抛出Error。
- 设置mStatus的状态为Status.RUNNING。
- 调用onPreExecute()方法,该方法通常在使用AsyncTask时,需要实现。
- 把参数params保存在mWorker.mParams中。
- 调用线程池,执行mFuture。
线程池执行的部分我们后面来分析,先来看这里有2个重要的属性mWorker和mFuture,它们起到了非常重要的作用,我们来看。
mWorker、mFuture
属性mWorker和mFuture是在AsyncTask的构造方法中初始化的,它们是后台线程控制逻辑的核心所在。
我们来看AsyncTask的构造方法:
public AsyncTask() {//这里是在UI线程中执行的
this((Looper) null);
}
public AsyncTask(@Nullable Handler handler) {
this(handler != null ? handler.getLooper() : null);
}
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);//这里设置了线程的优先级为THREAD_PRIORITY_BACKGROUND
//noinspection unchecked
result = doInBackground(mParams);//这里调用了doInBackground方法
Binder.flushPendingCommands();
} catch (Throwable tr) {
mCancelled.set(true);
throw tr;
} finally {
postResult(result); //post结果给UI线程
}
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);
}
}
};
}
private static abstract class WorkerRunnable<Params, Result> implements Callable<Result> {
Params[] mParams;
}
逻辑解析:
- 首先初始化Handler对象,因为AsyncTask涉及到后台线程和UI线程之间的通信,它内部实现使用了Handler通信机制。
- mWorker属性赋值,这里的mWorker是一个WorkerRunnable对象,它是一个抽象类,继承自Callable接口,用于执行异步任务并获取线程执行结果。WorkerRunnable对象的call()方法是在后台线程中执行的。这里可以看到在call()方法中,调用了doInBackground方法。
- call()方法中设置了线程的优先级为THREAD_PRIORITY_BACKGROUND(后台线程优先级)。
- mFuture属性赋值,这里的mFuture是一个FutureTask对象,FutureTask可以用于线程任务的控制等逻辑,异步任务执行完成后,会回调done()方法。
- done()方法中,调用postResultIfNotInvoked方法,把任务结果返回给UI线程,这里执行的是非正常情况下的返回,正常执行结束会在mWorker的call方法中通过调用postResult(result)将结果返回给UI线程。
Handler处理
我们来看,后台任务处理完成后,会在WorkerRunnable的call方法中,调用postResult(result)将结果返回给UI线程,但如果后台任务执行之前被终止,则会把结果传递给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;
}
postResult方法,创建一个message对象,把执行结果封装成一个AsyncTaskResult对象,通过Handler机制,将后台执行结果传递给UI线程(这里只考虑UI线程一种情况)。
串行执行的线程池实现
到了这里,Asynctask的执行部分、结果处理以及跨线程通信部分,都已经分析完成了,接下来我们来看后台线程是如何执行的,任务队列是如何实现的。
我们从上文知道,AsyncTask的execute方法中,直接使用了一个默认线程池sDefaultExecutor来负责后台线程的创建及执行。
回顾一下:
@MainThread
public final AsyncTask<Params, Progress, Result> execute(Params... params) {
return executeOnExecutor(sDefaultExecutor, params);
}
sDefaultExecutor的单线程队列的实现:
private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;
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);
}
}
}
我们可以看到,sDefaultExecutor是SerialExecutor的一个实例对象。
SerialExecutor的属性:
mTasks:是一个先进先出的队列,这里可以理解为容量无限的一个先进先出的队列(当然不可能无限,不过正常情况下我们是达不到它的最大容量的)。
mActive:表示当前正在执行的任务,它是一个Runnable对象。
后台任务队列的执行过程:
- 当第一次有任务进来时,先把任务添加到mTasks队列中,这时mActive == null,执行scheduleNext方法。
- scheduleNext方法中,从队列中取出队列头部的Runnale对象,并赋给mActive,最后把它交由THREAD_POOL_EXECUTOR线程池执行。
- 当第一个任务执行结束后,紧接着会调用scheduleNext方法执行下一个任务,以此类推……
串行执行的秘密:这里其实就实现了一个单线程的任务队列,所有AnsyncTask的任务,都会顺序的,单线程执行。它的实现原理其实就是,准备了一个可以扩容的先进先出的任务队列mTasks,所有的后台任务执行时,先进入队列中,然后调用scheduleNext执行,当前任务结束后,继续执行下一个任务,这样也就实现了串行的任务处理。
线程池THREAD_POOL_EXECUTOR
这里的线程池其实没有发挥线程池的作用,因为默认情况下,AsyncTask只会执行单个任务调用,因为在SerialExecutor中已经实现了任务的队列管理了,SerialExecutor会单个顺序的执行线程任务的调用。
THREAD_POOL_EXECUTOR的定义:
public static final Executor THREAD_POOL_EXECUTOR;
private static final int CORE_POOL_SIZE = 1;
private static final int MAXIMUM_POOL_SIZE = 20;
private static final int BACKUP_POOL_SIZE = 5;
private static final int KEEP_ALIVE_SECONDS = 3;
static {
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE_SECONDS, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>(), sThreadFactory);
threadPoolExecutor.setRejectedExecutionHandler(sRunOnSerialPolicy);
THREAD_POOL_EXECUTOR = threadPoolExecutor;
}
THREAD_POOL_EXECUTOR其实是一个可并发的线程池,它的核心线程数是1,最大线程数是20。(注意,不同Android版本中的实现会有不同)
线程的创建:
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());
}
};
线程池是通过ThreadFactory对象来进行线程创建的,并且会为线程命名为"AsyncTask #" + mCount.getAndIncrement()。
AsyncTask并发的实现
在Android 3.0时,AsyncTask的任务执行改回了单一线程中顺序执行,那么我们如果想用AsyncTask实现并发任务,可以做到吗?答案是肯定的,我们来看如何实现。
我们来看AsyncTask的executeOnExecutor方法:
public final AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec,
Params... params) {
……
exec.execute(mFuture);
return this;
}
源码解析:
executeOnExecutor方法,可以接受一个线程池的参数,来让使用者实现自定义线程池的目的。
我们可以自定义一个并发的线程池,通过调用executeOnExecutor方法,替换掉AsyncTask的默认线程池,即可实现并发处理。
注意:虽然这里可以实现线程池的并发处理,但是并不建议这么做,如果想要实现并发的后台任务,推荐使用java.util.concurrent并发工具包中的Executor、ThreadPoolExecutor和FutureTask等。
AsyncTask的最佳实践
到了这里,我们已经深入了解了AsyncTask的使用及实现原理了,那么我们在开发时,有哪些是需要注意的呢?
AsyncTask使用时的一些要求:
- AsyncTask类必须在UI线程中被加载(Android 4.1之后自动执行)。
- AsyncTask实例对象,必须在UI线程中创建。
- 必须在UI线程中调用execute()来运行任务。
- 必须创建AsyncTask的子类,以它的子类形式来使用。
- 必须在子类中至少实现doInBackground()回调方法,通常我们还会实现onPostExecute()方法。
- 不要手动调用onPreExecute(), onPostExecute(Result), doInBackground(Params…), onProgressUpdate(Progress…)方法。
- AsyncTask后台任务,只能执行一次,否则会抛出异常。
- 整个进程中的AsyncTask线程池及任务队列都是同一个(自定义的除外),大量的调用AsyncTask执行后台任务会造成任务队列整体执行时间的变长,导致任务执行的时间不可控。
- AsyncTask的默认线程优先级是Process.THREAD_PRIORITY_BACKGROUND(后台线程级别),优先级较低,分配的CPU资源会较少,不适合执行优先级较高的任务。
关于执行顺序及Android历史版本中的变更
AsyncTask后台任务的执行顺序是不可靠的,因为它在Android历史版本中经历了多次变更。在刚刚引入AsyncTask时,AsyncTask后台任务的执行,是在单一线程中顺序执行的;但是在Android 1.6中,AsyncTask的任务执行改成了可多线程并发执行;在Android 3.0时,AsyncTask的任务执行又改回了单一线程中顺序执行,以避免多线程造成的并发等问题。
如果我们想通过AsyncTask来并发执行后台任务,可以使用executeOnExecutor(java.util.concurrent.Executor, java.lang.Object[])方法,传递一个线程池来执行。
注意:AsyncTask在Android R(Android 11)中被标记为弃用状态,建议使用java.util.concurrent并发工具包来代替。
转载:https://blog.csdn.net/u011578734/article/details/106105042