likes
comments
collection
share

AsyncTask源码解析

作者站长头像
站长
· 阅读数 2

前言

我们在线程中执行耗时任务的过程中,如果需要更新 UI 显示任务进度,可以使用 Handler 来更新 UI,但是这样做相对比较麻烦,如果使用 AsyncTask 来实现,代码会简洁很多。从实现上来说,AsynTask 封装了线程池和 Handler。

AsyncTask简介

AsyncTask 经过几次修改,导致了对于不同的 API 版本的 AsyncTask 具有不同的表现,尤其是多任务的并发执行上。打开 AsyncTask 的源码(本文基于Android 11.0),代码如下:

@Deprecated
public abstract class AsyncTask<Params, Progress, Result> {
    ...
}

发现上面已经标记了@Deprecated,官方更推荐开发者使用Kotlin的协程进行异步操作:

Deprecated Use the standard java.util.concurrent or Kotlin concurrency utilities instead.

  1. 容易导致context的泄露
  2. 忘记回调
  3. 横竖屏切换导致崩溃
  4. 不同版本的AsyncTask的兼容问题
  5. 在doInBackground()中把异常直接吞掉了

AsyncTask 是一个抽象的泛型类,它提供了 Params、Progress和 Result 这三个泛型参数,其中 Params 表示参数的类型,Progress 表示后台任务的执行进度的类型,而 Result 则表示后台任务的返回结果的类型,如果 AsyncTask 确实不需要传递具体的参数,那么这三个泛型参数可以用Void来代替。

AsyncTask 提供了4个核心方法,它们的含义如下所示。

  1. onPreExecute(),在主线程中执行,在异步任务执行之前,此方法会被调用,一般可以用于做一些准备工作。
  2. doInBackground(Params...params),在线程池中执行,此方法用于执行异步任务,params参数表示异步任务的输入参数。在此方法中可以通过 publishProgress() 方法来更新任务的进度,publishProgress() 方法会调用 onProgressUpdate() 方法。另外此方法需要返回计算结果给 onPostExecute() 方法。
  3. onProgressUpdate(Progress...values),在主线程中执行,当后台任务的执行进度发生改变时此方法会被调用。
  4. onPostExecute(Result result),在主线程中执行,在异步任务执行之后,此方法会被调用,其中 result 参数是后台任务的返回值,即 dolnBackground() 的返回值。

上面这几个方法,onPreExecute() 先执行,接着是 doInBackground(),最后才是onPostExecute()。除了上述四个方法以外,AsyncTask 还提供了 onCancelled() 方法,它同样在主线程中执行,当异步任务被取消时,onCancelled() 方法会被调用,这个时候 onPostExecute() 则不会被调用。

AsyncTask的使用

下面是一个使用 AsyncTask 模拟耗时下载任务的例子:

public class MainActivity extends AppCompatActivity {

    private TextView myText;

    private DownloadFilesTask task;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        myText = findViewById(R.id.my_text);

        task = new DownloadFilesTask(this, myText);
        task.execute();
    }
    
    public static class DownloadFilesTask extends AsyncTask<Void, Integer, Boolean> {

        private  TextView myText;

        private Context context;

        public DownloadFilesTask(Context context, TextView myText){
            this.context = context;
            this.myText = myText;
        }

        @Override
        protected void onPreExecute() {
            // 开始下载前提示
            Toast.makeText(context, "Start download", Toast.LENGTH_SHORT).show();
        }

        @Override
        protected Boolean doInBackground(Void... voids) {
            for (int i = 0; i <= 100; i++) {
                try {
                    // 模拟耗时下载
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                // 通知主线程更新下载进度
                publishProgress((i));
                // Escape early if cancel() is called
                if (isCancelled()) break;
            }
            return true;
        }

        @Override
        protected void onProgressUpdate(Integer... progress) {
            // UI更新下载进度
            myText.setText(" Current progress is : " + Integer.valueOf(progress[0]));
        }

        @Override
        protected void onPostExecute(Boolean result) {
            // 任务结束后,显示下载成功还是失败
            if(result){
                Toast.makeText(context, "Download succeed", Toast.LENGTH_SHORT).show();
            }else{
                Toast.makeText(context, "Download failed", Toast.LENGTH_SHORT).show();
            }
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        if(task != null){
            task.cancel(true);
            task = null;
        }
    }
}

使用 AsyncTask 要注意:

  • AsyncTask的对象必须在主线程中创建。
  • 不要在程序中直接调用 onPreExecute()、onPostExecute()、doInBackground() 和 onProgressUpdate() 方法。

AsyncTask 在不同的版本中执行任务的方式不同:

  • 在Android1.6以前,AsyncTask 是串行执行任务的。
  • 从Android1.6到2.3,是并行执行,实现原理是用一个线程数为5的线程池进行并行执行,但是如果前5个任务执行时间过长,就会阻塞后面任务的执行,所以不适合大量任务并发执行。
  • Android3.0之后,为了避免 AsyncTask 所带来的并发错误,又改为了串行执行任务,不过你可以通过 AsyncTask 的 executeOnExecutor() 方法自己指定线程池来并行地执行任务。

下面我们来验证一下在 Android 11.0 中是串行还是并行。

怎么验证呢?我们同时执行3个 AsyncTask, 在 AsyncTask 的 doInBackground() 方法里面 sleep 2 秒后打印一条Log,如果是并行执行,大概2s左右就能把3个Log都打印出来;反之,如果是串行执行,整个执行过程大概需要6s左右。代码如下:

public class MainActivity extends AppCompatActivity {

    private MyAsyncTask mTask;

    private static final String TAG = "MyAsyncTask";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        for(int i = 0; i < 3; i++){
            mTask = new MyAsyncTask();
            mTask.execute();
        }
    }

    public static class MyAsyncTask extends AsyncTask<Void, Void, Void>{

        @Override
        protected void onPreExecute() {
            super.onPreExecute();
            Log.d(TAG, "onPreExecute");
        }

        @Override
        protected Void doInBackground(Void... voids) {
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            Log.d(TAG, Thread.currentThread().getName() + ": doInBackground");
            return null;
        }

        @Override
        protected void onPostExecute(Void unused) {
            super.onPostExecute(unused);
            Log.d(TAG, "onPostExecute");
        }
    }
}

打印如下:

2024-04-11 21:59:44.819 10346-10346 MyAsyncTask   com.example.test      D  onPreExecute
2024-04-11 21:59:44.820 10346-10346 MyAsyncTask   com.example.test      D  onPreExecute
2024-04-11 21:59:46.821 10346-10371 MyAsyncTask   com.example.test      D  AsyncTask #1: doInBackground
2024-04-11 21:59:46.822 10346-10346 MyAsyncTask   com.example.test      D  onPostExecute
2024-04-11 21:59:48.834 10346-10372 MyAsyncTask   com.example.test      D  AsyncTask #2: doInBackground
2024-04-11 21:59:48.835 10346-10346 MyAsyncTask   com.example.test      D  onPostExecute
2024-04-11 21:59:50.842 10346-10371 MyAsyncTask   com.example.test      D  AsyncTask #1: doInBackground
2024-04-11 21:59:50.843 10346-10346 MyAsyncTask   com.example.test      D  onPostExecute

整个过程耗时大概6秒,由此可知在Android 11.0中AsyncTask默认是串行执行异步任务的

源码解析

下面我们通过源码来分析 AsyncTask 的实现原理,先来看看 AsyncTask 的构造方法:

public abstract class AsyncTask<Params, Progress, Result> {

    private static InternalHandler sHandler;

    private final WorkerRunnable<Params, Result> mWorker;

    private final FutureTask<Result> mFuture;

    private final Handler mHandler;
  
    // 创建一个新的异步任务,必须在UI线程上调用此构造函数。
    public AsyncTask() {
        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);
                    //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);
                }
            }
        };
    }
}

在 AsyncTask 的构造方法中,分别对 mHandler、mWorker、mFuture 进行了初始化, WorkerRunnable 是 AsyncTask 的抽象静态内部类,实现了 Callable 接口:

private static abstract class WorkerRunnable<Params, Result> implements Callable<Result> {
    Params[] mParams;
}

在这里创建了 WorkerRunnable 的实例 mWorker, 并对 call() 方法进行了实现,然后把 mWorker 作为参数传给了 FutureTask,FutureTask 重写了 done() 方法,done() 方法在任务执行结束的时候调用。

下面看看 AsyncTask 的 execute() 方法:

public abstract class AsyncTask<Params, Progress, Result> {

    // 按顺序一次执行一个任务
    @Deprecated
    public static final Executor SERIAL_EXECUTOR = new SerialExecutor();

    @UnsupportedAppUsage
    private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;

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

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

在 executeOnExecutor() 方法中,AsyncTask 的 onPreExecute 方法最先执行,然后线程池开始执行。线程池执行前,会把 params 赋值给 mWorker.mParams,线程池的 execute() 方法传入的参数为 mFuture。

当你调用 AsyncTask 的 execute() 方法时,使用的是默认的线程池 sDefaultExecutor,里面执行任务是串行的。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);
        }
    }
}

SerialExecutor 是 AsyncTask 的内部类,SerialExecutor 的 execute() 方法上使用了 synchronized 关键字修饰,锁的是这个 SerialExecutor 的实例,又由于 SerialExecutor 的实例——sDefaultExecutor 是 static volatile 类型的,一个进程中只有一个,所以必须等待前一个任务在线程池中执行完了才会执行下一个任务。execute() 方法传入的参数为 FutureTask,FutureTask 实现了 Runnable 接口。execute() 方法首先会把 FutureTask 对象插入到任务队列 mTasks 中,如果这个时候没有正在活动的 AsyncTask 任务,就会调用 SerialExecutor 的 scheduleNext() 方法来执行下一个 AsyncTask 任务。同时当一个 AsyncTask 任务执行完后,AsyncTask 会继续执行其他任务直到所有的任务都被执行为止。

AsyncTask中的另一个线程池 THREAD_POOL_EXECUTOR 用于真正地执行任务:

private static final int CORE_POOL_SIZE = 1;
private static final int MAXIMUM_POOL_SIZE = 20;
private static final int KEEP_ALIVE_SECONDS = 3;

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

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

THREAD_POOL_EXECUTOR 的核心线程数为1,最大线程数为20,闲置时间为3秒。

前面分析 Android 11.0 中默认是串行还是并行的例子中,你可以把execute()方法改成并行执行的方式,传入这个线程池:

for(int i = 0; i < 3; i++){
    mTask = new MyAsyncTask();
    mTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}

再看看打印的时间是不是不一样。

由于 FutureTask 的 run() 方法会调用 mWorker 的 call() 方法,因此 mWorker 的 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;
    }
};

在 mWorker 的 call() 方法中,首先将 mTaskInvoked 设为 true,表示当前任务已经被调用过了,然后执行 doInBackground() 方法,接着将其返回值传递给 postResult() 方法,它的实现如下所示:

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

postResult()方法会通过 mHandler 发送一个 MESSAGE_POST_RESULT 的消息,mHandler来自 getMainHandler() 方法:

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

在 getMainHandler() 方法中新建了 InternalHandler 实例,传参为主线程的 Looper 对象,这也就意味着InternalHandler 的 handleMessage() 方法运行在主线程。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;
        }
    }
}

InternalHandler 收到 MESSAGE_POST_RESULT 消息后调用 AsyncTask 的 finish() 方法:

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

里面判断如果 AsyncTask 被取消执行了,就调用 onCancelled() 方法,否则调用 onPostExecute() 方法,可以看到 doInBackground() 的返回结果会传递给 onPostExecute() 方法,到这里 AsyncTask 的工作流程就分析完毕了。

前面的代码中创建 THREAD_POOL_EXECUTOR 的时候,给它设置了拒绝策略 sRunOnSerialPolicy,当THREAD_POOL_EXECUTOR 拒绝执行任务的时候,具体的处理逻辑就会分发到 sRunOnSerialPolicy 里面的rejectedExecution()方法,其代码如下:

private static final int BACKUP_POOL_SIZE = 5;

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");
        // As a last ditch fallback, run it on an executor with an unbounded queue.
        // Create this executor lazily, hopefully almost never.
        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);
    }
};

里面启用了一个备用的线程池,核心线程数为5,让备用的线程池来执行被拒绝的任务。这里设置了核心线程也遵循闲时策略,让核心线程在没有任务执行一段时间后自动终止。