美文网首页
AsyncTask原理

AsyncTask原理

作者: gczxbb | 来源:发表于2018-04-05 15:52 被阅读7次

    AsyncTask是Android提供的一个异步框架,使用它可以轻松完成异步任务,并将执行进度通知到主线程,进行UI更新。
    通常在一些比较耗时且主线程需要知道执行过程的场景下使用,比如进度条更新。本文介绍AsyncTask的实现原理。

    通知模型采用线程池+Handler,异步任务执行结果获取采用FutureTask+Callable。

    模型结构图 AsyncTask基本模型.jpg
    private static class InternalHandler extends Handler {
        public InternalHandler() {
            super(Looper.getMainLooper());//绑定主线程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;
            }
        }
    }
    

    静态内部类,绑定主线程Looper,内部不含任何AsyncTask对象引用。所有AsyncTask对象都指向同一个Handler,消息都发送到同一Handler,发布进度与结果。
    Handler如何分辨是哪一个AsyncTask发送的消息呢?在AsyncTaskResult中封装具体的AsyncTask对象。
    既然Handler绑定主线程,它可以保证进度与结果路由到主线程运行,那么AsyncTask由哪一个线程触发就显得没有那么重要了,不过一般情况由主线程发起,有时会重写onPreExecute,该方法可是在发起线程运行的噢,总之一个原则UI更新交给主线程。


    基础方法

    首先,AsyncTask是抽象类,使用时,需要重写它的几个方法,它提供了三种泛型AsyncTask<Params, Progress, Result>参数。

    Params:任务启动时的输入参数。
    Progress:任务进度百分比。
    Result:任务执行结果。

    需要重写的方法:
    doInBackground(Params…params):接受入参、执行后台耗时任务。
    onPostExecute(Result result):处理结果。

    可选择重写的方法:
    onProgressUpdate(Progress... values):进度更新,主线程。
    onPreExecute():任务启动前调用,主线程。
    onCancelled():用户取消时调用,主线程。

    protected abstract Result doInBackground(Params... params){
        ... 耗时任务
        //同步进度到主线程
        publishProgress(value);
    }
    

    线程耗时任务,publishProgress方法负责实时进度发布。基本原理是利用InternalHandler发送消息。

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

    触发AsyncTask#onProgressUpdate方法更新进度,实现类一般会重写onProgressUpdate,通知主线程UI同步。

    AsyncTask并不知道这个耗时操作产生的中间值需要干什么,它只提供了一个后台运行框架,触发的中间值操作由使用者(子类实现)完成。

    AsyncTask#finish方法。

    private void finish(Result result) {
        if (isCancelled()) {
            onCancelled(result); //触发onCancelled空方法,子类可重写
        } else {
            onPostExecute(result);// 子类重写
        }
        mStatus = Status.FINISHED;
    }
    

    任务完成时,触发AsyncTask#postResult方法,利用Handler发布结果,主线程执行AsyncTask#finish方法,触发重写的onPostExecute方法。


    基本原理

    AsyncTask的构造方法。

    public AsyncTask(@Nullable Looper callbackLooper) {
         //Handler绑定主线程Looper
        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);
                    result = doInBackground(mParams);
                    Binder.flushPendingCommands();
                } catch (Throwable tr) {
                    mCancelled.set(true);
                    throw tr;
                } finally {
                    //结束call之前发送结果到主线程
                    postResult(result);
                }
                return result;
            }
        };
    
        mFuture = new FutureTask<Result>(mWorker) {
            @Override
            protected void done() {
                try {
                    postResultIfNotInvoked(get());
                } catch (InterruptedException e) {
                } catch (ExecutionException e) {
                } catch (CancellationException e) {//取消抛出异常,
                    postResultIfNotInvoked(null);
                }
            }
        };
    }
    

    从构造方法看出,AsyncTask采用Callable+Future结构来获取任务结果,首先,创建WorkerRunnable和FutureTask任务。
    WorkerRunnable是Callable类型,与Runnable类似,执行主体是call方法,它可以返回执行结果。AsyncTask异步任务主要在这里执行,看上面代码,调用了doInBackground,做用户设定的任务。call方法最后将Result返回,在finally时,postResult方法派发消息通知结果,call结束。
    FutureTask也是一种Runnable,实现Future,利用Future可以获取结果,它内部封装了Callable,这里重写了done方法进行结果处理。
    内部任务关系图。

    AsyncTask内部任务关系图.jpg

    创建AsyncTask后,执行execute方法。

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

    调用executeOnExecutor方法。

    @MainThread
    public final AsyncTask<Params, Progress, Result> executeOnExecutor(
                    Executor exec,Params... params) {
        if (mStatus != Status.PENDING) {
            switch (mStatus) {
                case RUNNING:
                    //抛异常
                case FINISHED:
                   //抛异常
            }
        }
        mStatus = Status.RUNNING;
        onPreExecute();
        mWorker.mParams = params;
        exec.execute(mFuture);
        return this;
    }
    

    内部三种任务状态,PENDING(等待运行),RUNNING(运行中),FINISHED(完成)。初始状态是PENDING
    任务开始,状态设置成RUNNING,onPreExecute进行准备工作(空方法,子类可选择重写),将初始参数交给WorkerRunnable保存,交给线程池是FutureTask。
    线程池是SerialExecutor。

    private static class SerialExecutor implements Executor {
        final ArrayDeque<Runnable> mTasks = new ArrayDeque<Runnable>();
        Runnable mActive;
        public synchronized void execute(final Runnable r) {
            //将Runnable加入队列,等待执行。
            mTasks.offer(new Runnable() {
                public void run() {
                    try {
                        r.run();
                    } finally {
                        scheduleNext();
                    }
                }
            });
            //当前mActive任务是空,说明还未取出过任务将其赋值。即当前没有任务执行。
            if (mActive == null) {
                scheduleNext();
            }
        }
        //队列弹出任务,交给真正执行的线程池
        protected synchronized void scheduleNext() {
            if ((mActive = mTasks.poll()) != null) {
                THREAD_POOL_EXECUTOR.execute(mActive);
            }
        }
    }
    

    任务队列ArrayDeque,将派发的FutureTask任务再次封装成一个新Runnable,投入队列。顺序执行。
    从队列取任务,交给真正的线程池处理。结束后,poll取下一个任务。

    public static final Executor THREAD_POOL_EXECUTOR
                    = new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE,
                    KEEP_ALIVE,TimeUnit.SECONDS, sPoolWorkQueue, sThreadFactory);
    

    核心线程3个(针对2个cup),最大线程数量是5,任务队列是LinkedBlockingDeque(128),对这个线程池来说,任务是按一次一个的顺序派发的,当前任务完成后,下一个派发任务创建新线程(<核心数量),若线程数量达到核心数量,随机使用老线程处理。

    注意
    在Android2.3,api11之前,直接采用5个线程的线程池处理任务,在api11后,加入SerialExecutor变成串行。因此,可以加入判断,大于11时直接用THREAD_POOL_EXECUTOR线程池,绕过SerialExecutor可避免串行。SerialExecutor的功能是控制任务按顺序串行的交给线程池执行。

    线程开始,首先执行FutureTask#run方法,在run方法中触发Callable的call方法,即WorkerRunnable#call方法。上面已经提到过了,最终调用的是doInBackground方法。
    run代码之前,我们先看一下FutureTask的七种状态。

    private static final int NEW          = 0;
    private static final int COMPLETING   = 1;
    private static final int NORMAL       = 2;
    private static final int EXCEPTIONAL  = 3;
    private static final int CANCELLED    = 4;
    private static final int INTERRUPTING = 5;
    private static final int INTERRUPTED  = 6;
    

    他们分别代表,新建,完成,普通,异常,取消和中断,前三种是正常状态。后四种是异常状态,下面从执行过程中看状态变化。

    public void run() {
        if (state != NEW ||
                !U.compareAndSwapObject(this, RUNNER, null, Thread.currentThread()))
            return;
        try {
            Callable<V> c = callable;
            if (c != null && state == NEW) {//存在callable,执行其call方法
                V result;
                boolean ran;
                try {
                    result = c.call();
                    ran = true;
                } catch (Throwable ex) {
                    result = null;
                    ran = false;
                    setException(ex);
                }
                //成功标志
                if (ran)
                    set(result);
            }
        } finally {
            runner = null;
            int s = state;
            if (s >= INTERRUPTING)
                handlePossibleCancellationInterrupt(s);
        }
    }  
    

    FutureTask构造方法时,初始化NEW状态,开始执行时,必须去执行状态状态的,若不是NEW或者compareAndSwapXxx原子方法设置runner为当前线程(且初值需要是null)不成功,都直接返回。
    Callable是AsyncTask中的mWorker,执行完毕,返回结果。在call方法执行时,有两种情况,成功和抛出异常,状态变化是不同的。
    成功时,状态变化情况是NEW -> COMPLETING-> NORMAL。

    protected void set(V v) {
        if (U.compareAndSwapInt(this, STATE, NEW, COMPLETING)) {
            outcome = v;
            U.putOrderedInt(this, STATE, NORMAL); // final state
            finishCompletion();
        }
    }
    

    当有成功标志时,set方法设置结果,改变状态,finishCompletion结束任务,outcome存储成功的结果。
    异常时,状态变化情况是NEW -> COMPLETING-> EXCEPTIONAL。

    protected void setException(Throwable t) {
        if (U.compareAndSwapInt(this, STATE, NEW, COMPLETING)) {
            outcome = t;
            U.putOrderedInt(this, STATE, EXCEPTIONAL); 
            finishCompletion();
        }
    }
    

    在catch中,setException方法设置异常,改变状态,finishCompletion结束任务,outcome存储异常的Throwable。

    private void finishCompletion() {
        // assert state > COMPLETING;
        for (WaitNode q; (q = waiters) != null;) {
            if (U.compareAndSwapObject(this, WAITERS, q, null)) {
                for (;;) {
                    Thread t = q.thread;
                    if (t != null) {
                        q.thread = null;
                        LockSupport.unpark(t);
                    }
                    WaitNode next = q.next;
                    if (next == null)
                        break;
                    q.next = null; // unlink to help gc
                    q = next;
                }
                break;
            }
        }
    
        done();
    
        callable = null;        // to reduce footprint
    }
    

    任务结束时,利用LockSupport.unpark(Thread)唤醒在get处阻塞的线程,get方法的情况适用于当执行任务时,其他线程(如主线程)想通过FutureTask#get获取任务结果,若还未完成,则阻塞,直到任务完成更新状态后,通知唤醒,在这里,此处还未执行过get方法。
    最后,执行done方法,FutureTask子类重写了它,在前面构造方法代码中看到,在done方法中,利用get方法获取结果,将结果传递给postResultIfNotInvoked方法,因为在call方法设置了mTaskInvoked标志,因此,向主线程postResult发送完成消息不在done方法,而在call方法中finally处。

    public V get() throws InterruptedException, ExecutionException {
        int s = state;
        if (s <= COMPLETING)
            s = awaitDone(false, 0L);
        return report(s);
    }
    

    在done方法get结果时,状态<=COMPLETING,进行中,awaitDone方法陷入等待。状态>COMPLETING,说明已经完成,包括正常/异常,report结果。

    private V report(int s) throws ExecutionException {
        Object x = outcome;
        //正常完成,返回结果
        if (s == NORMAL)
            return (V)x;
        //取消异常,CANCELLED,INTERRUPTING,INTERRUPTED三种情况
        if (s >= CANCELLED)
            throw new CancellationException();
        throw new ExecutionException((Throwable)x);//中断异常
    }
    

    report有两种情况,返回了结果,抛出一个异常。这个异常将在构造方法的done处被捕获到。
    我们任务进行时,若执行了AsyncTask#cancel方法,通过Future控制任务取消。

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

    同时改变AsyncTask的mCancelled标志位。

    public boolean cancel(boolean mayInterruptIfRunning) {
        if (!(state == NEW &&
                  U.compareAndSwapInt(this, STATE, NEW,
                      mayInterruptIfRunning ? INTERRUPTING : CANCELLED)))
            return false;
        try {    // in case call to interrupt throws exception
            if (mayInterruptIfRunning) {
                try {
                    Thread t = runner;
                    if (t != null)
                        t.interrupt();
                } finally { // final state
                    U.putOrderedInt(this, STATE, INTERRUPTED);
                }
            }
        } finally {
            finishCompletion();
        }
        return true;
    }
    

    首先,必须是执行中的状态,非NEW状态。
    mayInterruptIfRunning标志由用户控制,在cancel方法传入。
    若是中断任务,触发Thread#interrupt方法,此时,线程并不会停止,仍会继续运行,改变的仅仅是中断标志,后面可根据判断这个标志来停止任务。此刻的状态变化是NEW -> INTERRUPTING -> INTERRUPTED。
    若是取消任务,状态变化是NEW -> CANCELLED,此时线程也是一样,仍继续执行。

    注意
    若用户在上层调用了AsyncTask#cancel方法,主线程UI更新会停止,但是doInBackground执行不会停止,在后台任务publishProgress时,会判断标志位mCancelled,它已经在cancel方法设置,因此,不会再通知主线程进度。我们看不到UI更新了,以为线程被终止了,其实后台线程还在运行中。

    cancel方法最后,调用finishCompletion方法,然后进入done方法,在这种情况下get时,因状态是INTERRUPTED或CANCELLED,report便会抛出CancellationException异常。在done方法捕捉到CancellationException异常,执行postResultIfNotInvoked时,传入结果null。

    既然线程会继续执行,那么在call方法完成后,postResult方法派送结果时,根据状态触发方法是onCancelled方法而不是onPostExecute方法。


    总结

    AsyncTask是一个可获取过程与结果的任务处理框架,启动后,后台执行,进度更新(如果需要) ,结果处理,方法不需要亲自调用,只需要重写。
    不一定由主线程创建AsyncTask ,因内部Handler已经自动绑定主线程Looper。
    SerialExecutor的任务队列依次执行,也可以自己定义线程池。


    任重而道远

    相关文章

      网友评论

          本文标题:AsyncTask原理

          本文链接:https://www.haomeiwen.com/subject/bbgaxftx.html