Android AsyncTask的源码分析

本文详细分析了Android中的AsyncTask的实现原理,从任务创建、线程池执行、结果传递到UI线程的全过程。讨论了如何利用线程池解决大量异步任务的问题,以及如何确保onPostExecute方法在UI线程中执行。同时,文章还介绍了AsyncTask中涉及的模板方法、策略和Half-Sync/Half-Async模式。
摘要由CSDN通过智能技术生成

1. AsyncTask的实现原理简介

首先抛出一个问题,如何在不用AsyncTask的情况下实现异步下载一张图片并在一个ImageView中显示?
可以这么做,创建一个线程,在这个线程中下载图片,然后通过Handler消息机制在UI主线程中显示这张图片。大致代码如下:

public class MainActivity extends Activity {

    private ImageView mImageView;

    private Handler mHandler = new Handler() {

        @Override
        public void handleMessage(Message msg) {
            Bitmap result = (Bitmap) msg.obj;
            switch (msg.what) {
                case MESSAGE_POST_RESULT:
                    // There is only one result
                    mImageView.setImageBitmap(result);
                    break;
            }
        }
    }

    private void executeDownloadImageTask(Uri uri) {
        new Thread(new Runnable() {

            public void run() {
                Bitmap bitmap = downloadImage(uri);
                Message msg = Message.obtain();
                msg.what = MESSAGE_POST_RESULT;
                msg.obj = bitmap;
                mHandler.sendMessage(msg);
            }
        }).start();
    }
}

上述实现有一个缺陷,每次执行executeDownloadImageTask方法都会创建一个新的线程,当有大量的图片下载请求时,线程的数量就不可控,大量的线程之间可能因为互相抢占系统资源而导致阻塞,同时线程的创建和销毁都会带来性能开销。AsyncTask的实现与上述方法唯一不同的地方正是使用了线程池来管理线程。AsyncTask类中的方法onPostExecute(Result result)能在UI线程中处理结果,正是利用了Handler消息机制将执行doInBackground方法的返回结果传递到了UI线程。

在学习AsyncTask源码前,请先了解以下概念

  • Callable<V>
  • FutureTask<>
  • Executor
  • 线程池

若不了解,请参考博文Java Executor
下面就来具体分析一下AsyncTask的源码。

2. AsyncTask源码分析

整个AsyncTask由3部分组成,任务FutureTask,线程池Executor,与UI线程关联的Handler,结构示意图如下:
这里写图片描述

2.1 创建任务

AsyncTask中的doInBackground方法会在非UI线程中执行,如何将其封装为一个任务类。源码如下:

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

            Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
            //noinspection unchecked
            return postResult(doInBackground(mParams));
        }
    };

    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 occured while executing doInBackground()",
                        e.getCause());
            } catch (CancellationException e) {
                postResultIfNotInvoked(null);
            }
        }
    };
}

可以看到在AsyncTask的构造方法中初始化了两个成员变量,mWorker是一个WorkerRunnable对象,mFuture是一个FutureTask对象。这些两个对象正代表了要执行的异步任务。FutureTask其实只是WorkerRunnable的包装类,真正在后台线程执行的方法正是WorkerRunnable中的call方法。最终放入线程池执行的就是mFuture这个对象。

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

2.2 将任务放入线程池执行

AsyncTask一开始就初始化了两个线程池SerialExecutor和THREAD_POOL_EXECUTOR。源码如下:

/**
 * 用于并行执行任务.
 */
public static final Executor THREAD_POOL_EXECUTOR
        = new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE,
                TimeUnit.SECONDS, sPoolWorkQueue, sThreadFactory);

/**
 * 用于串行执行任务.
 */
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);
        }
    }
}

SerialExecutor用于任务的排队,真正执行任务的还是THREAD_POOL_EXECUTOR。
THREAD_POOL_EXECUTOR的初始化参数如下:

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

参数跟CPU数量有关,以四核CPU为例,那么该线程池有5个核心线程,最大线程数为9,线程闲置超时时间为1s,任务队列的容量为128。这说明当任务队列中任务数量为128即满时,无法再向线程池提交任务,所以当使用AsyncTask并使用默认线程池时存在这样一个缺陷。

注意:这几个配置线程池的参数在不同SDK版本中不一样。

AsyncTask提供了两个方法来执行异步任务。源码如下:

private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;

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

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

由第一个方法可以看出默认情况下使用的是SerialExecutor。
第二个方法则可以使用自定义线程池。同时,可以看到当任务状态为RUNNING或FINISHED时,执行任务会抛出 IllegalStateException,所以AsyncTask对象只能被execute一次。
AsyncTask的状态有以下三种:

public enum Status {
    /**
     * Indicates that the task has not been executed yet.
     */
    PENDING,
    /**
     * Indicates that the task is running.
     */
    RUNNING,
    /**
     * Indicates that {@link AsyncTask#onPostExecute} has finished.
     */
    FINISHED,
}

当状态为PENDING时,才会正常执行。接着看executeOnExecutor方法。将mStatus设为RUNNING,表示正在执行。执行大家很熟悉的onPreExecute方法,此时还在UI线程,最后exec.execute(mFuture),将上一步创建的任务对象mFuture提交给线程池exec。

public final AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec,
            Params... params) {
    ...

    mStatus = Status.RUNNING;

    onPreExecute();

    mWorker.mParams = params;
    exec.execute(mFuture);

    return this;
}

这样,mWorker中的call方法就会在线程中执行了。

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

Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
        //noinspection unchecked
        return postResult(doInBackground(mParams));
    }
};

mTaskInvoked为一个AtomicBoolean对象,设为true表示call方法被调用
postResult(doInBackground(mParams)),这里终于看到了我们熟悉的doInBackground方法,此时正处于线程池中的执行,执行结果被postResult方法传递到了UI线程。

2.3 将异步任务执行结果传递到UI线程

正如文章开头所述,将异步任务执行结果传递到UI线程使用了Handler。

private static final InternalHandler sHandler = new InternalHandler();

private static class InternalHandler extends Handler {
    @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;
        }
    }
}

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

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

在InternalHandler处理MESSAGE_POST_RESULT消息时,调用了AsyncTask的finish方法。

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

finish方法首先判断任务是否被Cancel,如果被Cancel,则执行onCancelled回调方法,反之,则执行我们熟悉的onPostExecute。最后将状态设置为FINISHED,防止再次执行该任务。

2.4 如何确保onPostExecute方法在UI线程中执行?

根据Handler消息机制可知,要想使onPostExecute方法在UI线程中执行,sHandler必须与UI线程Looper绑定,那么根据AsyncTask的源码分析,第一次使用AsyncTask必须在UI线程,不然在非UI线程new InternalHandler()则有可能报错,因为普通thread没有Looper,所以会抛出RuntimeException(
“Can’t create handler inside thread that has not called Looper.prepare()”)。那么AsyncTask是如何保证第一次使用Async是在UI线程的呢?请看ActivityThread类中的main方法,AsyncTask的init方法会被调用,目的就是为了在主线程加载AsyncTask类,从而是InternalHandler在UI线程中初始化,源代码如下:

public static void main(String[] args) {
    ...

    Looper.prepareMainLooper();

    ActivityThread thread = new ActivityThread();
    thread.attach(false);

    if (sMainThreadHandler == null) {
        sMainThreadHandler = thread.getHandler();
    }

    AsyncTask.init();

    if (false) {
        Looper.myLooper().setMessageLogging(new
                LogPrinter(Log.DEBUG, "ActivityThread"));
    }

    Looper.loop();

    throw new RuntimeException("Main thread loop unexpectedly exited");
}

3. AsyncTask中的设计模式

3.1 模板方法模式

AsyncTask中的模板方法

protected abstract Result doInBackground(Params... params);

protected void onPreExecute() {}

@SuppressWarnings({"UnusedDeclaration"})
protected void onPostExecute(Result result) {}

@SuppressWarnings({"UnusedDeclaration"})
protected void onProgressUpdate(Progress... values) {}

@SuppressWarnings({"UnusedParameters"})
protected void onCancelled(Result result) {
    onCancelled();
}    

protected void onCancelled() {}

3.2 策略模式

不同的线程池相当于不同的策略。

3.3 Half-Sync/Half-Async模式

将系统中的服务分为Async和Sync两层,Async中的任务不能阻塞,相当于UI线程,Sync中的任务可以阻塞,相后台线程池。然后添加队列层实现Async层与Sync层的通信,即线程池使用的BlockingQueue<Runnable>。
这里写图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值