AsyncTask研究(以Android 10.0为准)

Posted 悠然红茶

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了AsyncTask研究(以Android 10.0为准)相关的知识,希望对你有一定的参考价值。

1. 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();    // 在UI线程里,调用execute()即可
        
    

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

需要注意的是,AsyncTask的execute()只能在UI线程里调用,所以上面示例代码里,我们是在响应控件点击动作的onClick()函数里调用t.execute()的。

2. 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() 
    this((Looper) null);

隐藏的有参形式如下:

/**
 * Creates a new asynchronous task. This constructor must be invoked on the UI thread.
 * @hide
 */
public AsyncTask(@Nullable Handler handler) 
    this(handler != null ? handler.getLooper() : null);

一般来说,可以携带handler参数的函数,都可以将后续的回调处理放到某个工作线程执行,这至少可以减轻UI线程的一些负担。只可惜这个构造函数是@hide的。

前两种构造函数最终都会用到下面这个隐藏的构造函数:
【frameworks/base/core/java/android/os/AsyncTask.java】
 

/**
 * Creates a new asynchronous task. This constructor must be invoked on the UI thread.
 * @hide
 */
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) 
                throw new RuntimeException("An error occurred while executing doInBackground()", e.getCause());
             catch (CancellationException e) 
                postResultIfNotInvoked(null);
            
        
    ;

构造函数的注释中说的很明确,必须在UI线程里构造AsyncTask对象。而且构造函数里为两个重要的成员:mWorker和mFuture赋了值。AsyncTask本身是泛型化的,其泛型参数Param、Result会进一步确定mWorker和mFuture的最终类型(即WorkerRunnable<Params, Result>和FutureTask<Result>),另一个泛型参数Progress会用于确定“和任务进度相关的函数”,比如AsyncTask的onProgressUpdate()和publishProgress()。有关这部分的细节,我们后文再细说。

2.1 AsyncTask的execute()

我们先回过头看前文曾经提到的AsyncTask的execute()函数,其代码如下:
【frameworks/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()而已,正如我们前文所说,execute()(以及executeOnExecutor())都必须在UI线程里调用。传给executeOnExecutor()的参数sDefaultExecutor是个静态变量,它引用的就是串行执行器AsyncTask.SERIAL_EXECUTOR:

private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;

executeOnExecutor()的代码截选如下:
【frameworks/base/core/java/android/os/AsyncTask.java】

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

可以看到,在UI线程里,我们最终是在调用执行器的execute()函数,只不过会把一个mFuture对象委托给执行器去回调而已。这个执行器可以是串行的,也可以是并行的。mFuture的类型为FutureTask<Result>,而且间接实现了Runnable接口。

默认情况下使用的是串行执行器,其类是SerialExecutor,它的代码如下:
【frameworks/base/core/java/android/os/AsyncTask.java】

private static class SerialExecutor implements Executor 
    final ArrayDeque<Runnable> mTasks = new ArrayDeque<Runnable>();  // task队列
    Runnable mActive;  // 用mActive来控制串行行为

    public synchronized void execute(final Runnable r)  // 参数r一般就是mFuture引用的对象
        mTasks.offer(new Runnable() 
            public void run() 
                try 
                    r.run();
                 finally 
                    scheduleNext();  // 每当一个task的mFuture.run()返回,会接着调度下一个
                
            
        );
        if (mActive == null)   // 每次向mTasks队列里添加完新节点后,会先判断mActive,只有
                                // 无事可做时,才会scheduleNext()。
            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/FunnableFuture.java】

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

在前文列出AsyncTask构造函数时,我们已经看到mFuture的创建代码了,

mFuture = new FutureTask<Result>(mWorker)

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

欲了解详情,我们先得看看AsyncTask机制运用的线程池。在AsyncTask类里这样定义线程池成员的:
【frameworks/base/core/java/android/os/AsyncTask.java】

public static final Executor THREAD_POOL_EXECUTOR;

static 
    ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
            CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE_SECONDS, TimeUnit.SECONDS,
            // 同步队列已经不是以前的BlockingQueue<Runnable>了
            new SynchronousQueue<Runnable>(), sThreadFactory);  
    threadPoolExecutor.setRejectedExecutionHandler(sRunOnSerialPolicy);  // 设置拒绝策略
    THREAD_POOL_EXECUTOR = threadPoolExecutor;

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

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

还有一点要说明一下,以前(比如Android 7上),线程池使用的“待处理工作队列”是BlockingQueue<Runnable>形式的,而到了Android 10上,则变成了SynchronousQueue。这会导致线程池有什么不同吗?我们可以这样理解,SynchronousQueue的内部是没有任务缓存队列的,所以当CorePool线程用完后,其实是立即起新线程来做事的,直到线程数达到MAXIMUM_POOL_SIZE。这就不像以前那样,还有个填充任务队列的过程。在线程数达到MAXIMUM_POOL_SIZE之后,如果还有新任务,则会按拒绝策略处理。

本文的重点并不想太深入线程池的内部机理,我们只做必要的探讨即可。我们大体上只需知道线程池里的线程会执行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()函数吗?它返回的Result对象,归根溯源就是doInBackground()返回的那个Result对象。现在对这个Result对象执行set()操作。

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();   // 回调到AsyncTask的mFuture的done()函数
                return;
            
        
    

结果记录进Sync类的result成员,然后回调FutureTask的done()函数,这也就回调到前文我们看到的AsyncTask的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);
    

一般情况下,上面的mTaskInvoked变量会在AsyncTask的mWorker的call()函数中设置为true,所以在mFuture的done()函数中读到的mTaskInvoked还会是为true,于是postResultIfNotInvoked()就直接返回了;而如果其值为false,说明这个任务很可能在线程池回调它之前就被cancel了,也就是说还没轮到call()执行就被取消了,此时call()里面的postResult(result)当然还没有执行过,我们在这里要补救性地调用一次postResult(result)。当然,这种情况比较少见。

我们画一张调用关系示意图:


2.2.2 AsyncTask里的mWorker

AsyncTask的mWorker和mFuture是紧密配合的。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);
            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;
        
    ;

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

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

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

前文我们已经看到,回调的call()函数最终会通过postResult(),发回一条MESSAGE_POST_RESULT消息。postResult()的代码如下:
【frameworks/base/core/java/android/os/AsyncTask.java】

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

在Android 7上,getHandler()拿到的是AsyncTask的静态成员sHandler,可以向UI线程发送消息。但到了Android 10上,至少在frameworks内部是可以指定Looper的,那么就不一定是向UI线程发送消息啦,也有可能向其他工作线程发送消息。我再摘录一下前文AsyncTask构造函数里关于mHandler的句子

mHandler = callbackLooper == null || callbackLooper == Looper.getMainLooper()
    ? getMainHandler()
    : new Handler(callbackLooper);

在Android10上,getHandler()的代码很简单:

private Handler getHandler() 
    return mHandler;

当然,在大多数情况下,getHandler()拿到的handler是向UI线程发消息的,其具体值来自于getMainHandler():

private static Handler getMainHandler() 
    synchronized (AsyncTask.class) 
        if (sHandler == null) 
            sHandler = new InternalHandler(Looper.getMainLooper());
        
        return sHandler;
    

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

private static class InternalHandler extends Handler 
    public InternalHandler(Looper looper) 
        super(looper);
    

    @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;
        
    

因为在new InternalHandler()时传入的looper就是UI线程looper,所以其实就是在UI线程处理MESSAGE_POST_RESULT消息的,而且处理时会调用AsyncTask的finish()。

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

从这段代码可以知道,如果我们cancel了某个AsyncTask,就不会执行onPostExecute()了,取而代之的是调用onCancelled()。

另一方面,用户在编写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()时,用户都可以调用AsyncTask的cancel()函数。该函数的代码如下:
【frameworks/base/core/java/android/os/AsyncTask.java】

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

其中第一步就是在设置mCancelled变量,而第二步mFuture.cancel()内部会在mayInterruptIfRunning参数为true时,间接调用“运行该AsyncTask的线程”的interrupt()函数。

【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()的返回值,一旦发现任务已经取消了,就请立即退出。

3 小结

关于AsyncTask的知识,我们就先说这么多。现在大体总结一下:
1)使用AsyncTask时,主要是重写其派生类的doInBackground(),而且该函数会在线程池的某个工作线程里被回调的;
2)必须在UI线程调用AsyncTask的execute()或executeOnExecutor();
3)AsyncTask默认是按后台优先级(THREAD_PRIORITY_BACKGROUND)在线程池中执行的,如果你尝试做一些重要的工作,那么可能会面临抢不到CPU资源而执行缓慢的情况。
4)可以在doInBackground()里的合适时机调用publishProgress(),向UI线程通知工作进展;
5)可以随时调用cancel(),放弃执行任务。
 

以上是关于AsyncTask研究(以Android 10.0为准)的主要内容,如果未能解决你的问题,请参考以下文章

AsyncTask研究(以Android 10.0为准)

AsyncTask研究(以Android 10.0为准)

AsyncTask研究(以Android 10.0为准)

AsyncTask研究(以Android 10.0为准)

如何在 Android 中以编程方式隐藏 AsyncTask 中的 ProgressBar?

来自 asyncTask 的 Android 致命信号 6 (SIGABRT)