Android多线程之AsyncTask详解

一 概述

在 Android 平台上,要执行异步任务时,我们常常会用到 AsyncTask。这个类可以算是历史悠久,早在 Android 1.5 版时,它就存在了。

AsyncTask 的使用方法比较简单,无非是创建一个 AsyncTask 派生类对象,重写其 doInBackground() 函数,然后在合适时机调用这个对象的 execute() 或 executeOnExecutor() 函数即可。

private static class MyTask extends AsyncTask<Void, Void, Void>//......
    @Override
    public Void doInBackground(Void... param) {
        //......
        return null;
    }
private class TestClickListener implements View.OnClickListener {
    public void onClick(View v) {
        switch (v.getId()) {
        case R.id.scan_btn:
            testTask();
            break;
        ......
    }
            
    private void testTask() {
        for (int i = 0; i < 5; i++) {
            MyTask t = new MyTask(i+100);
            t.execute();    // 调用execute()即可
        }
    }

一般情况下,我们会像上面代码中这样调用 AsyncTask 的 execute() 函数,这样,投入执行的 task 会串行执行。不过,有时候我们也希望 task 们可以并行执行,此时只需把 execute() 换成 executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR) 即可。

二 AsyncTask的内部机制

AsyncTask 本身是个抽象的泛型基类,正如前面所说,在实际使用时,我们必须定义它的派生类,并在实现 AsyncTask 派生类时,重写其 doInBackground() 成员函数。

AysncTask 的声明如下:

frameworks/base/core/java/android/os/AsyncTask.java

public abstract class AsyncTask<Params, Progress, Result> 

作为一种异步执行的任务,AsyncTask 是依靠内部的线程池来完成任务调度的。大体上说,AsyncTask 内部搞了两个静态的执行器,分别表示成 AsyncTask.THREAD_POOL_EXECUTOR 和 AsyncTask.SERIAL_EXECUTOR,前者是可并行执行的执行器(线程池),后者是串行执行的执行器(线程池)。

AsyncTask 的构造函数如下:

/**
 * Creates a new asynchronous task. This constructor must be invoked on the UI thread.
 */
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) {
                ......
            } catch (CancellationException e) {
                postResultIfNotInvoked(null);
            }
        }
    };
}

构造函数的注释中说的很明确,必须在 UI 线程里构造 AsyncTask 对象。而且构造函数里为两个重要的成员:mWorker 和 mFuture 赋了值,这个我们后文再细说。

2.1 AsyncTask的execute()

我们先回过头看前文曾经提到的 AsyncTask 的 execute() 函数,其代码如下:

rameworks/base/core/java/android/os/AsyncTask.java

@MainThread
public final AsyncTask<Params, Progress, Result> execute(Params... params) {
    return executeOnExecutor(sDefaultExecutor, params);
}

因为 params 参数是可变长参数,所以 execute() 可以接受 0 到 n 个参数。注意,execute() 和 executeOnExecutor() 都必须在 UI 线程里调用。

execute() 只是简单地调用 executeOnExecutor() 而已,它传递的静态变量 sDefaultExecutor 引用的就是串行执行器AsyncTask.SERIAL_EXECUTOR:

private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;

executeOnExecutor() 的代码截选如下:

@MainThread
public final AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec,
        Params... params) {
    ......
    mStatus = Status.RUNNING;
    onPreExecute();
    mWorker.mParams = params;
    exec.execute(mFuture);  // 注意,mFuture本身实现了Runnable接口
    return this;
}

也就是说,最终还是在调用执行器的 execute() 函数,只不过会把一个 mFuture 委托给执行器去回调。

默认情况下使用的是串行执行器,对应的类是 SerialExecutor,它的代码如下:

private static class SerialExecutor implements Executor {
    final ArrayDeque<Runnable> mTasks = new ArrayDeque<Runnable>();
    Runnable mActive;

    public synchronized void execute(final Runnable r) { // 参数r一般就是mFuture引用的对象
        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);
        }
    }
}

从代码里可以看到,所谓的串行执行器内部,其实也是在复用 THREAD_POOL_EXECUTOR,只不过利用对 mActive 的判断,把调用的流程改成串行的了。

SerialExecutor 内部使用的是 java.util.ArrayDeque 队列,它的 poll() 函数可以检索并移除此队列的头部,如果返回 null,则表示此队列已经取空了。每次摘取一个列头,并记录在 mActive 变量里,然后交给 THREAD_POOL_EXECUTOR 来处理。

ThreadPoolExecutor 是 java 提供的线程池实现。总之,线程池会在后续的某个时刻,回调上面插入的 Runnable 对象的 run()。在 executeOnExecutor() 函数里,我们已经看到向执行器添加了 AsynctTask 的 mFuture 成员,而 mFuture 本身实现了 Runnable 接口,以后回调就是回调 mFuture 的 run() 函数。

2.2 AsyncTask和线程池的协作

2.2.1 AsyncTask里的mFuture

AsyncTask 的 mFuture 非常重要,它的定义如下:

private final FutureTask<Result> mFuture;

类型为 FutureTask,其实现可以参考 JDK 里的代码:

java/util/concurrent/FutureTask.java

public class FutureTask<V> implements RunnableFuture<V> 

java/util/concurrent/RunnableFuture.java

public interface RunnableFuture<V> extends Runnable, Future<V>

在前文列出 AsyncTask 构造函数时,我们已经看到 mFuture 的创建代码了,注意,在创建 FutureTask 对象时,传入了 mWorker,它会被记入 mFuture 内部(如果分析 JDK 的代码,可以看到大体上就是记入 mFuture.sync.callable 了)。后续在被线程池执行时,这个 mWorker 才是最核心的对象。

欲了解详情,我们先得看看 AsyncTask 机制运用的线程池。在 AsyncTask 类里这样定义线程池成员的:

private static final BlockingQueue<Runnable> sPoolWorkQueue =
        new LinkedBlockingQueue<Runnable>(128);
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;
}
public static final Executor SERIAL_EXECUTOR = new SerialExecutor();

注意,线程池都是记在静态变量里的,它的生命期和进程的生命期基本一致。

细心的同学还记得,前文在定义 AsyncTask 派生类时,我们写的是 private static class,大家不要忘记加 static,否则就是写了一个普通内嵌类,而普通内嵌类对象内部会隐式地引用其外部类,这样当我们的 task 对象记入线程池后,就有可能导致 task 的外部类(很有可能是个 Activity 或 Service)对象在较长时间内都不能被垃圾回收机制回收,从而导致内存泄漏。

本文的重点并不想太深入线程池的内部机理,我们只做必要的探讨即可。我们大体上只需知道线程池里的线程会执行 FutureTask 的 run() 函数即可。而 FutureTask 的 run() 代码如下:

java/util/concurrent/FutureTask.java

public void run() {
    sync.innerRun();
}
// 而 FutureTask.Sync 的 innerRun() 代码如下:
void innerRun() {
    if (!compareAndSetState(READY, RUNNING))
        return;

    runner = Thread.currentThread();
    if (getState() == RUNNING) { // recheck after setting thread
        V result;
        try {
            result = callable.call();  // 这一步间接调用到AsyncTask的doInBackground()。
        } catch (Throwable ex) {
            setException(ex);
            return;
        }
        set(result);  // 如果不出异常的话,会对call返回的结果执行set()操作。
    } else {
        releaseShared(0); // cancel
    }
}

其中会调用 callable.call(),这一步就会间接调用到 AsyncTask 的 doInBackground()。再接下来,如果不出异常的话,会对 call() 返回的结果执行 set() 操作。大家还记得前文 WorkerRunnable 实现的 call() 函数吗?它最后返回语句为:return postResult(result);现在设置的就是 postResult() 返回的 Result 对象,其归根溯源就是 doInBackground() 返回的那个 Result 对象。

FutureTask 的 set() 函数的代码如下:

java/util/concurrent/FutureTask.java

protected void set(V v) {
    sync.innerSet(v);
}

    void innerSet(V v) {
        for (;;) {
            int s = getState();
            if (s == RAN)
                return;
            if (s == CANCELLED) {
                releaseShared(0);
                return;
            }
            if (compareAndSetState(s, RAN)) {
                result = v;
                releaseShared(0);
                done();
                return;
            }
        }
    }

结果记录进 Sync 类的 result 成员,然后回调 FutureTask 的 done() 函数,这也就回调到前文我们看到的 AysncTask 的 mFuture 的 done() 函数了。我们再列一下 mFuture 的代码:

frameworks/base/core/java/android/os/AsyncTask.java

  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);
            }
        }
    };

done() 里面做的无非是一些善后处理。

private void postResultIfNotInvoked(Result result) {
    final boolean wasTaskInvoked = mTaskInvoked.get();
    if (!wasTaskInvoked) {
        postResult(result);
    }
}

2.2.2 AsyncTask里的mWorker

AsyncTask 的另一个重要成员是 mWorker

private final WorkerRunnable<Params, Result> mWorker;

除了在 executeOnExecutor() 里会为 mWorker 的 mParams 成员赋值外,AsyncTask 一般不会直接操作 mWorker。mWorker 会间接记录进 mFuture。当 mFuture 被回调时,系统会间接回调 mWorker 的 call() 成员函数,而这个 call() 函数是整个 AsyncTask 的核心行为。

现在我们可以画一张 AsyncTask 的示意图:
在这里插入图片描述
其实,当一个 AsyncTask 被安插进线程池时,线程池主要关心的是其 mFuture 成员引用的 FutureTask。所以我们可以画出如下示意图:
在这里插入图片描述
当回调发生时,最终间接执行到 mWorker 成员的 call() 函数,在介绍 AsyncTask 的构造函数时,我们已经见过该函数的代码,现在再列一遍:

    mWorker = new WorkerRunnable<Params, Result>() {
        public Result call() throws Exception {
            mTaskInvoked.set(true);

            Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); // 设为后台线程
            //noinspection unchecked
            Result result = doInBackground(mParams);
            Binder.flushPendingCommands();   // ???
            return postResult(result);
        }
    };

看到了吗,当线程池里的某个线程回调到上面的 call() 函数时,会先把线程优先级设置为“后台线程”,然后会调用 doInBackground() 函数。大家还记得吧,前文说过我们在实现一个 AsyncTask 派生类时,主要重写的就是这个 doInBackground() 函数,现在终于派上用场了。

上面代码中还调用了一个不常见的函数:Binder.flushPendingCommands()。这个函数对应的注释是这样说的:(本函数)会将所有在当前线程里挂起的 “Binder命令” 扔回内核驱动。一般可以在执行那些有可能阻塞较长时间的操作之前调用一下该函数,这样可以确保挂起的对象引用被及时释放,避免 “持有执行对象的进程” 占据比 “实际需要持有的时间” 更长的时间。这部分说明让人有点儿迷惑,或许此处的调用仅仅只是为了在 doInBackground() 之后做一些 binder 驱动层的清理动作。

2.2.3 UI线程和AsyncTask工作线程之间的协作

回调的 call() 函数最终还会通过 postResult(),发回一条 MESSAGE_POST_RESULT 消息。postResult() 的代码如下:

private Result postResult(Result result) {
    @SuppressWarnings("unchecked")
    Message message = getHandler().obtainMessage(MESSAGE_POST_RESULT,
            new AsyncTaskResult<Result>(this, result));
    message.sendToTarget();
    return result;
}

此处的 getHandler() 得到的实际是一个可向 UI 线程发送消息的 handler(即 AsyncTask 的静态成员 sHandler)。getHandler() 的代码如下:

private static Handler getHandler() {
    synchronized (AsyncTask.class) {
        if (sHandler == null) {
            sHandler = new InternalHandler();
        }
        return sHandler;
    }
}

这里搞了个类似单例的 sHandler,类型为 InternalHandler:

private static class InternalHandler extends Handler {
    public InternalHandler() {
        super(Looper.getMainLooper());    // 用于向UI线程发送消息!
    }

    @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]);
                break;
            case MESSAGE_POST_PROGRESS:
                result.mTask.onProgressUpdate(result.mData);
                break;
        }
    }
}

从 InternalHandler 的构造函数可以看到,postResult() 最终就是向 UI 线程发回 MESSAGE_POST_RESULT 消息的。

当 UI 线程最终处理 MESSAGE_POST_RESUTL 消息时,会调用 AsyncTask 的 finish()。

private void finish(Result result) {
    if (isCancelled()) {
        onCancelled(result);
    } else {
        onPostExecute(result);
    }
    mStatus = Status.FINISHED;
}

另一方面,用户在编写 doInBackground() 时,还可以在合适时机调用 publishProgress(),向 UI 线程发出 MESSAGE_POST_PROGRESS 消息。publishProgress() 的代码如下:

@WorkerThread
protected final void publishProgress(Progress... values) {
    if (!isCancelled()) {
        getHandler().obtainMessage(MESSAGE_POST_PROGRESS,
                new AsyncTaskResult<Progress>(this, values)).sendToTarget();
    }
}

这个消息同样被刚刚说到的 InternalHandler 处理,处理时会回调 AsyncTask 的 onProgressUpdate()。

关于 UI 线程和执行 AsyncTask 的线程之间的交互,我们可以画一张示意图如下:
在这里插入图片描述
这张图反映了一个 AsyncTask 对象在运作时,大体上是如何被 UI 线程和工作线程调用执行的。

2.2.4 AsyncTask的内部状态

细心的读者还会发现,AsyncTask 在 finish() 时会把自己的状态置为 Status.FINISHED。简单说来,AsyncTask 可以处于3种状态,分别是 PENDING、RUNNING、FINISHED。这3种状态的切换很简单,示意图如下:
在这里插入图片描述

2.2.5 cancel动作

当然,用户还可以随时中途放弃执行当前任务。不管是在主线程处理 MESSAGE_POST_PROGRESS 时,还是在工作线程处理 doInBackground() 时,用户都可以调用 cancel() 函数。该函数的代码如下:

frameworks/base/core/java/android/os/AsyncTask.java

public final boolean cancel(boolean mayInterruptIfRunning) {
    mCancelled.set(true);
    return mFuture.cancel(mayInterruptIfRunning);
}

java/util/concurrent/FutureTask.java

public boolean cancel(boolean mayInterruptIfRunning) {
    return sync.innerCancel(mayInterruptIfRunning);
}

java/util/concurrent/FutureTask.java

boolean innerCancel(boolean mayInterruptIfRunning) {
    for (;;) {
        int s = getState();
        if (ranOrCancelled(s))
            return false;
        if (compareAndSetState(s, CANCELLED))
            break;
    }
    if (mayInterruptIfRunning) {
        Thread r = runner;
        if (r != null)
            r.interrupt();
    }
    releaseShared(0);
    done();
    return true;
}

简单地说,cancel() 动作会将 mCancelled 设为 true,这样以后再调用 isCancelled() 时,就会返回 true。前文我们已经看过 AsyncTask 的 finish() 的代码,现在再列一下:

private void finish(Result result) {
    if (isCancelled()) {
        onCancelled(result);
    } else {
        onPostExecute(result);
    }
    mStatus = Status.FINISHED;
}

可以看到,如果该任务是被用户 cancel 的,那么 finish 时执行的会是 onCancelled(),而不是 onPostExecute()。另外,为了确保在用户 cancel 任务之后,该任务能真的快速退出,我们应该在 doInBackground() 里周期性地检查一下 isCancelled() 的返回值,一旦发现,就立即退出。

三 小结

关于 AsyncTask 的知识,我们就先说这么多。现在大体总结一下:

  • 使用 AsyncTask 时,主要是重写其派生类的 doInBackground(),而且该函数会在线程池的某个工作线程里被回调
  • 必须在 UI 线程调用 AsyncTask 的 execute() 或 executeOnExecutor()
  • 可以在 doInBackground() 里的合适时机调用 publishProgress(),向 UI 线程通知工作进展
  • 可以随时调用 cancel(),放弃执行任务

四 AsyncTask演化历程

我们先复习下线程池执行器 ThreadPoolExecutor。这个类的构造函数如下:

public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory,
                          RejectedExecutionHandler handler)

corePoolSize 指明了线程池的基本大小,如果线程池里当前存在的线程数小于 corePoolSize,那么添加任务时,线程池就会创建新线程来干活。而如果线程数达到了 corePoolSize,那么任务会先缓存进 workQueue,直到塞满 workQueue。

塞满队列后,如果还有新的工作添加进来,线程池就会超额创建新线程来干活。当然,超额也是有限度的,最多能达到 maximumPoolSize 个线程。

而如果在队列已满且线程数也到达最大阀值后,还继续添加新工作,那么线程池就会依照某个拒绝策略进行拒绝了。上面构造函数里最后一个参数,就体现了拒绝策略。

为了更有效地运用线程,线程池还设定了两个关于时间的控制量,一个是 keepAliveTime,另一个是 allowCoreThreadTimeOut。

简单地说,如果线程池里的线程的空闲时长达到 keepAliveTime 阀值时,线程池就会让超时的线程退出,直到线程数量降到 corePoolSize 大小为止,此时一般不会再轻易退出线程了,除非 allowCoreThreadTimeOut 的值为 true,这个值明确告诉线程池,即便线程数小于 corePoolSize 了,也会一直把空闲线程退出去,直到线程数量为0。

注意,线程的空闲时长是指做完当前任务后,等待新任务被分配给它的那段时长,不是任务执行过程中 sleep 的时长。

线程池的那些关键参数一般都可在运行期动态设置,常见的设置函数有:

  • void setCorePoolSize(int corePoolSize)
  • void setKeepAliveTime(long time, TimeUnit unit)
  • void setMaximumPoolSize(int maximumPoolSize)
  • void setRejectedExecutionHandler(RejectedExecutionHandler handler)
  • void setThreadFactory(ThreadFactory threadFactory)

现在我们可以绘制一张线程池的示意图:
在这里插入图片描述

4.1 on Android 2.3

复习完以上这些知识,我们就可以着手看 AsyncTask 的变动历史了。我们先看 Android2.3 上的线程池:

AsyncTask.java on Android2.3

private static final int CORE_POOL_SIZE = 5;
private static final int MAXIMUM_POOL_SIZE = 128;
private static final int KEEP_ALIVE = 1;
 
private static final BlockingQueue<Runnable> sWorkQueue = new LinkedBlockingQueue<Runnable>(10);
. . . . . .
private static final ThreadPoolExecutor sExecutor = new ThreadPoolExecutor(CORE_POOL_SIZE, 
                          MAXIMUM_POOL_SIZE, KEEP_ALIVE, TimeUnit.SECONDS, 
                          sWorkQueue, sThreadFactory);

也就是说,尝试形成一个核心池大小为 5 的线程池。如果有 5 个任务尚在执行时,又来了第 6 个任务,则新任务会缓存进 sWorkQueue 队列,不过这个队列其实也不怎么大,最多能缓存 10 个任务。在队列塞满之后,如果还有新任务到来,则开始创建新线程来做事,而且最多再创建 123(即128-5)个线程。于是,在极端情况下线程池会是下图这个样,怎么看都觉得线程数挺壮观了:
在这里插入图片描述

4.2 on Android 4.4

到了 Android 4.4,Google 的工程师似乎发现,用户在使用 AsyncTask 时,大多数情况下是希望那些被添加的任务能够一个个串行执行的,只有较少的情况是希望多线程并行执行的。所以,新 AsyncTask 里的默认执行器在声明时就写为“串行执行器”了。

private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;

但是,早期 Android 版本里 AsyncTask 已经写成多线程并行执行的鬼样子了,这总得兼顾一下嘛。于是,AsyncTask 内部搞了两个静态的执行器,分别表示成 AsyncTask.THREAD_POOL_EXECUTOR 和刚刚看到的 AsyncTask.SERIAL_EXECUTOR,前者是可并行执行的执行器,后者是串行执行的执行器。

串行执行时,示意图如下:
在这里插入图片描述
串行时其实最终也会用到线程池,只是这个线程池已经退化到只有一个线程在干活了。

并行执行时,AsyncTask 也稍微变化了一点儿。估计是为了改变以前那种线程满天飞的壮观场面,同时又考虑到多核 CPU 已经比较普遍,于是开始让 AsyncTask 在不过度产生线程的情况下,充分利用一下多核,所以 AsyncTask 里线程池的相关代码变成了这样:

AsyncTask.java on Android4.4

private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
private static final int CORE_POOL_SIZE = CPU_COUNT + 1;
private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;
private static final int KEEP_ALIVE = 1;
. . . . . .
. . . . . .
private static final BlockingQueue<Runnable> sPoolWorkQueue =
        new LinkedBlockingQueue<Runnable>(128);
 
public static final Executor THREAD_POOL_EXECUTOR
        = new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE,
                TimeUnit.SECONDS, sPoolWorkQueue, sThreadFactory);

可以看到,会先调用 availableProcessors() 获取虚拟机当前可用的处理器数量。再基于这个数量计算 Core Pool 和线程池最大的线程数。比如 CPU 数为 2 时,满负荷时的线程池示意图如下:
在这里插入图片描述
看到了吧,最多才会有 5 个线程在同时干活。也就是说,线程有点儿贵,要谨慎地给。

但是,设计师很明显不希望普通用户能设定 AsyncTask 的默认行为,所以 setDefaultExecutor() 成员函数被注解为 @hide,就是不让别人用嘛。

/** @hide */
public static void setDefaultExecutor(Executor exec) {
    sDefaultExecutor = exec;
}

这个函数主要在 ActivityThread 里调用了一下,做了一点兼容性处理:

if (data.appInfo.targetSdkVersion <= android.os.Build.VERSION_CODES.HONEYCOMB_MR1) {
    AsyncTask.setDefaultExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}

可以看到,如果所运行的 App 是那种针对旧系统(Android 3.1(HONEYCOMB MR1)之前的系统)的应用,则把运行该应用的进程里的 AsyncTask 的默认行为设为“按多线程并行执行”,而如果运行的是针对新系统的应用,则 AsyncTask 的默认行为统统按串行执行。

当然,上面只是修改了默认行为,如果某个新版应用明确要求其 AsyncTask 按多线程并行处理,它可以直接调用 executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR)。

4.3 on Android 7.0

待到发展到 Android 7.0,在并行执行时,又变化了一点儿。仍然会先调用 availableProcessors() 获取虚拟机当前可用的处理器数量,然而 CORE_POOL_SIZE 不再只是简单地按 CPU_COUNT + 1 来计算啦,这可能是因为发现在极端情况下,把所有的 CPU 都占上有点儿太狠了,这肯定会影响到其他后台线程的调度。新的 CORE_POOL_SIZE 被控制在2到4之间,说明在占用资源方面下手的确轻了少许。相关的代码截选如下:

AsyncTask.java on Android7.0

private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
private static final int CORE_POOL_SIZE = Math.max(2, Math.min(CPU_COUNT - 1, 4));
private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;
private static final int KEEP_ALIVE_SECONDS = 30;
......
private static final BlockingQueue<Runnable> sPoolWorkQueue =
        new LinkedBlockingQueue<Runnable>(128);
 
 
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;
}

串行执行的情况没什么变化,我们就不赘述了。我们还以 CPU 数为2为例,并行执行的示意图如下:
在这里插入图片描述
另外,在 Android 7 上,AsyncTask 线程池对线程的空闲时长也更加容忍了。以前空闲 1 秒钟,就会终止线程,现在最多允许空闲 30 秒。这是为了保证不会出现频繁快速地终止、创建线程。而且,线程池执行器还调用了 allowCoreThreadTimeOut(true),也就是说,如果空闲时间过长,连 CorePool 里的线程也可以终止。

4.4 on Android 8.0

Android 8.0 上的 AsyncTask 和 Android 7.0 的差不多,在线程池的调度方面没什么变化。只是在创建 AsyncTask 时,做了点小手脚。从代码上看,AsyncTask 多了两个隐藏(@hide)的构造函数:

  • public AsyncTask(@Nullable Handler handler)
  • public AsyncTask(@Nullable Looper callbackLooper)

也就是说,在系统内部可以指定一个 looper,处理 AsyncTask 的 MESSAGE_POST_RESULT 和 MESSAGE_POST_PROGRESS。

public AsyncTask(@Nullable Looper callbackLooper) {
    mHandler = callbackLooper == null || callbackLooper == Looper.getMainLooper()
        ? getMainHandler()
        : new Handler(callbackLooper);

如果 looper 是 UI 线程里的 looper,这两个事件由 getMainHandler() 返回的 InternalHandler 处理,而如果是其他线程的 looper,那么就是用一个最普通的 Handler 来处理,相当于什么事都不做。这难道不让人困惑吗?

4.5 on Android 10.0

Android 9 上的 AsyncTask 和 Android 8 的完全一样,所以我们直接看 Android 10 上的 AsyncTask。在 Android 10 上:

1)主线程池的队列从 LinkedBlockQueue 改成了 SynchronousQueue
2)主线程池的 CorePool 大小改成了1
3)明确设定了新的拒绝策略 sRunOnSerialPolicy

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;
......
public static final Executor THREAD_POOL_EXECUTOR;
 
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;
}

以前使用 LinkedBlockQueue 的情况,我们已经了解了,此处不再赘述。现在改成 SynchronousQueue 后,线程池的行为会有什么不同吗?我们可以这样理解,SynchronousQueue 的内部是没有任务缓存队列的,所以当 CorePool 线程用完后,其实是立即起新线程来做事的,直到线程数达到 MAXIMUM_POOL_SIZE。这就不像以前那样,还有个填充任务队列的过程。在线程数达到 MAXIMUM_POOL_SIZE 之后,如果还有新任务,则会按拒绝策略处理。

Android 10 上没有采用 ThreadPoolExecutor 已有的拒绝策略,而是专门设计了一个自定义的拒绝策略:

private static ThreadPoolExecutor sBackupExecutor;
private static LinkedBlockingQueue<Runnable> sBackupExecutorQueue;
 
private static final RejectedExecutionHandler sRunOnSerialPolicy =
        new RejectedExecutionHandler() {
    public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
        android.util.Log.w(LOG_TAG, "Exceeded ThreadPoolExecutor pool size");
 
        synchronized (this) {
            if (sBackupExecutor == null) {
                sBackupExecutorQueue = new LinkedBlockingQueue<Runnable>();
                sBackupExecutor = new ThreadPoolExecutor(
                        BACKUP_POOL_SIZE, BACKUP_POOL_SIZE, KEEP_ALIVE_SECONDS,
                        TimeUnit.SECONDS, sBackupExecutorQueue, sThreadFactory);
                sBackupExecutor.allowCoreThreadTimeOut(true);
            }
        }
        sBackupExecutor.execute(r);
    }
};

看到了吗,在拒绝策略中又用到了一个备用的线程池,线程池的 Core Pool 大小和最大线程数都是 5(BACKUP_POOL_SIZE),也就是说,当拒绝策略处理任务时,最多还可再启动 5 个线程来干活,如果仍然不够用的话,新任务就会记录进一个几乎无限大的 LinkedBlockingQueue。示意图如下:
在这里插入图片描述

4.6 小结

经过以上的介绍,大家是不是能感到维护 AsyncTask 的工程师的一点小纠结呢?一开始希望做事的线程多一点,后来发现太多了,要按 CPU 数限制一下。一开始在主线程池里用 LinkedBlockingQueue 来缓存任务,后来干脆把 LinkedBlockingQueue 移到了拒绝策略里。大家是不是觉得挺有趣?

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值