AsyncTask的原理解析及使用缺陷

AsyncTask是Android从1.5引入的类,它内部做了比较完整的封装。借助它,我们可以很easy的在子线程进行耗时操作,在主线程处理结果,而不用操作Thread或者Handler。即使到目前,用AsyncTask做类似文件的上传或下载也是很方便的。

使用就不多介绍了,就是按照AsyncTask的泛型定义,自己构造子类继承AsyncTask。

初始化

继承AsyncTask的子类在初始化的时候,会调用AsyncTask的无参构造函数,构造函数必须在UI线程调用,具体原因后说。查看源码,精简后如下:

mHandler=getMainHandler();
mWorker = new WorkerRunnable<Params, Result>() {
            public Result call() throws Exception {
               ......
            }
        };

mFuture = new FutureTask<Result>(mWorker) {
            @Override
            protected void done() {
                ......
            }
        };

这段代码,初始化了一个Handler对象mHandler,一个Callable对象mWorker和一个FutureTask对象mFuture,并且将mWorker作为mFuture的参数传入。但这些只是定义好了回调和一些异常处理,还没有真正的触发执行。

任务的执行

开启任务,需要执行execute(),具体源码:

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

这里就比较清楚了,首先对mStatus做了状态判断,证实任务处于未执行状态时,将任务的状态置为执行中,同时调用onPreExecute,这里子类可以做一些初始化工作。因为execute在主线程执行,因此该方法也是在UI线程中被调用。最后用传进来的Executor对象去执行mFuture中定义的任务,因为FutureTask也是实现了Runnable接口的。这里的Executor对象就是sDefaultExecutor,这是个什么东西?

sDefaultExecutor

private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;

public static final Executor SERIAL_EXECUTOR = new SerialExecutor();

在AsyncTask初始化的时候就为我们提供了一个默认的Executor对象,并且是静态类型的,也就是说该进程内所有的AsyncTask子类都共用这一个对象。再来看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);
        }
    }
}

该类内部维护了一个双端队列mTasks,当开始执行时新建一个Runnable放入mTasks队尾,这里面调用mFuture的run方法,点到FutureTask的源码里面可以看到,它引用了初始化时传入的Callable对象,也就是mWorker,并调用其call方法,这就和前面构造函数初始化的时候串起来了。

前面说了,一个进程中不管new多少AsyncTask实例都是公用的一个SerialExecutor。实际上新建一个Runnable加入到队尾,并没有触发任务开始,什么时候开始执行呢?就是THREAD_POOL_EXECUTOR.execute(mActive)。在第一个任务调用execute方法时,mActive为null,执行scheduleNext方法。该方法从队列头部取值如果不为空,则赋值给mActive并开始执行。如果该任务耗时比较长,那么下一个任务进来的时候mActive不为null,不会立即执行scheduleNext。这时候真正执行的时机是,第一个任务在自己执行完毕后一定要走finally代码块中的scheduleNext,这时第二个任务已经被加入到队尾中,自然就可以保证执行了。这说明SerialExecutor实际上是一个单线程类型的Executor,在任务耗时稍长而有多个任务的情况下,同一时刻只有一个任务执行,执行完毕后才能执行下一个任务。

THREAD_POOL_EXECUTOR

SerialExecutor这个线程池保证了任务的线性执行,真正执行任务的线程池是THREAD_POOL_EXECUTOR。该线程池在AsyncTask类被加载时创建:

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 ThreadFactory sThreadFactory = new ThreadFactory() {
    private final AtomicInteger mCount = new AtomicInteger(1);

    public Thread newThread(Runnable r) {
        return new Thread(r, "AsyncTask #" + mCount.getAndIncrement());
    }
};

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

AsnycTask默认构造的这个线程池,核心线程和最大线程数和CPU期望核心数有关,keepAliveTime为30s,阻塞队列长度为128。最大线程数虽然不大,但是因为任务实际是单线程执行的,并不会抛异常。

任务的执行流程

回过头来,再看异步任务mWorker和mFuture的初始化过程:


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 (CancellationException e) {
                    postResultIfNotInvoked(null);
                }
            }
        };

前面说过FutureTask的run()会调用传入的Callable对象的call(),也就是mWorker的call(),一旦call开始执行,mTaskInvoked置为true,这是一个原子型变量,标记任务开始执行。try代码块中又调用了doInBackground(mParams),这样就完成了异步调用。过程中如果发生异常,原子变量mCancelled会置为true。最终获取doInBackground的返回结果,并传递给postResult。该方法源码:

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

显然这个就是Handler的异步消息处理机制。其中的AsyncTaskResult是AsyncTask内部封装的一个消息实体。而发送消息的Handler就是getHandler()返回的mHandler,它是在mWorker初始化之前通过getMainHandler()创建了一个静态内部类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;
        }
    }
}

这里就可以看到,handleMessage里就切回到了主线程,并开始处理接收到的异步信息。因为前面发送的是MESSAGE_POST_RESULT,而result.mTask就是AsyncTask对象本身,所以是调用了类内部的finish()

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

到这里,如果mCancelled不为true,也就是在上述任务执行过程中未发生异常或主动取消,那么就会调用onPostExecute(result),所以我们可以重写这个方法,在UI线程处理返回的结果。当然如果取消了任务,也可以重写onCancelled(result)做一些异常处理。最后将mStatus置为FINISHED。

更新任务进度

注意到消息类型还有一个MESSAGE_POST_PROGRESS,发现在publishProgress方法里发送了这个消息:

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

这个方法AsyncTask本身自己不会调用,而是由使用者在子类的doInBackground中调用。InternalHandler收到消息后开始处理,又会调用onProgressUpdate。这样使用者可以获取任务执行的进度,并且在主线程实时更新,最常用的就是下载文件显示进度条。

使用缺陷

从上面的分析看,AsyncTask为我们封装好了一整套Thread、Handler、Executor、Runnable,包括异常处理,泛型和回调,有很好的思想。当然,它也有一些缺陷,无法适用一些场景。

首先就是最初AsyncTask是单线程依次执行,从安卓1.6开始,变为多线程并行执行,但同时最多支持128个任务并发,一旦任务耗时稍长而又有很多个任务的情况下就很容易抛异常。因此从安卓3.0以后,又引入了SerialExecutor,等于还是默认单线程一个一个任务执行。其实安卓也意识到了这个问题,所以它的executeOnExecutor(Executor exec,Params... params)允许我们可以传入自己定义的Executor,而不是默认的SerialExecutor。比如下例子:

private void execute() {
    ExecutorService executorService = Executors.newCachedThreadPool();
    for (int i = 0; i < 150; i++) {
        new MyTask().executeOnExecutor(executorService);
    }
}

private class MyTask extends AsyncTask<Void, Void, Void> {

    @Override
    protected Void doInBackground(Void... voids) {
        try {
            Log.i(TAG, Thread.currentThread().getName());
            Thread.sleep(1000);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
}

可以看到快速打印出来的日志,而不是每隔1s打印一次日志,确实是并行的。什么场景要用到并行执行?很典型的一个例子就是本地相册,因为Bitmap的解析不能放到UI线程中进行,而如果使用AsyncTask的串行执行,能想像向下滑动时图片要一个一个的依次加载出来?但是实际上还是有很多坑。

即使是采用并行执行任务,如果相册里有几千张图片甚至更多,初次加载没有缓存的话,当快速滑动的时候仍然会发生卡顿。就是当任务负载量短时间内比较高的情况下,doInBackground没有及时得到执行的原因,在一帧(16ms)内没有完成任务,造成了丢帧。具体为什么,我认为可能是因为是使用FutureTask包装了任务执行的原因,降低了程序整体效率。因为FutureTask本身就适用于很耗时的任务,而AsyncTask注释又说明了自身适合的场景是短时间的轻量任务,也不知道谷歌为何要这么设计,虽然已经满足了大部分需求了。除此之外,AsyncTask还存在如在组件中的生命周期、内存泄漏等缺陷。因此高负载量的后台任务,还是用ThreadPoolExecutor+Runnable比较好。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值