AsyncTask你真的会用吗?实战、原理、最佳实践!(Android Q)

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

示例中的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 {//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 execute(Params... params) {
        return executeOnExecutor(sDefaultExecutor, params);
    }

这里调用了executeOnExecutor方法,参数sDefaultExecutor是一个线程池,作为默认线程池。

executeOnExecutor方法:

    @MainThread
    public final AsyncTask 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() {
            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(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 implements Callable {
        Params[] mParams;
    }
逻辑解析:
  1. 首先初始化Handler对象,因为AsyncTask涉及到后台线程和UI线程之间的通信,它内部实现使用了Handler通信机制。
  2. mWorker属性赋值,这里的mWorker是一个WorkerRunnable对象,它是一个抽象类,继承自Callable接口,用于执行异步任务并获取线程执行结果。WorkerRunnable对象的call()方法是在后台线程中执行的。这里可以看到在call()方法中,调用了doInBackground方法。
  3. call()方法中设置了线程的优先级为THREAD_PRIORITY_BACKGROUND(后台线程优先级)。
  4. mFuture属性赋值,这里的mFuture是一个FutureTask对象,FutureTask可以用于线程任务的控制等逻辑,异步任务执行完成后,会回调done()方法。
  5. 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(this, result));
        message.sendToTarget();
        return result;
    }

postResult方法,创建一个message对象,把执行结果封装成一个AsyncTaskResult对象,通过Handler机制,将后台执行结果传递给UI线程(这里只考虑UI线程一种情况)。

串行执行的线程池实现

到了这里,Asynctask的执行部分、结果处理以及跨线程通信部分,都已经分析完成了,接下来我们来看后台线程是如何执行的,任务队列是如何实现的。

我们从上文知道,AsyncTask的execute方法中,直接使用了一个默认线程池sDefaultExecutor来负责后台线程的创建及执行。

回顾一下:

    @MainThread
    public final AsyncTask 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 mTasks = new ArrayDeque();
        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对象。

后台任务队列的执行过程:

  1. 当第一次有任务进来时,先把任务添加到mTasks队列中,这时mActive == null,执行scheduleNext方法。
  2. scheduleNext方法中,从队列中取出队列头部的Runnale对象,并赋给mActive,最后把它交由THREAD_POOL_EXECUTOR线程池执行。
  3. 当第一个任务执行结束后,紧接着会调用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(), 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 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并发工具包来代替。

你可能感兴趣的:(Android技术实现原理解析)