美文网首页android
AsyncTask带你更深入点看源码

AsyncTask带你更深入点看源码

作者: mandypig | 来源:发表于2019-02-02 17:02 被阅读83次

    关键词

    AsyncTask源码分析 FutureTask源码分析

    快过年了,公司同事已经开始进行迁移之旅,目前来看已经走掉了大半,自己离家还算近,所以打算奋战到最后一天。说是奋战只是一个冠冕堂皇的说辞,其实这几天事情已经没什么可做的,算是提前进入了过年模式,一年之中可能最轻松的日子也就是年前这段时间吧。

    工作上的事情轻松了,但是不意味着咱们可以停止学习的脚步,每天要做的就是进步一点点。今天想说一下AsyncTask的东西,也是对自己之前的一点解惑吧,AsyncTask可以说是android系统自带的元老级后台任务工具了,对于它的源码分析已经有很多文章,同时也有一些文章指出过AsyncTask会可能存在内存泄露问题的,根源就在于AsyncTask的生命周期和Activity不能同步,也就是说在activity退出之后如果AsyncTask没有执行完毕,那它将会一直存在直到doinbackground执行完毕,导致被AsyncTask引用着的Activity无法被释放。

    那么问题来了,为什么调用了AsyncTask的cancel方法不能立即停止掉doinbackground,其实稍微深入点Asynctask的源码基本都能找到问题答案,现在就把自己在分析时的一点心得分享出来。

    以下所有源码都是基于sdk26

    先看下关于cancel的源码

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

    mCancelled就是一个原子操作的boolean值类型,这个比较容易理解。接下来的mFuture必须从源头看看到底是什么东西,这个类最初是在AsyncTask的构造函数中被创建的。

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

    里面有两个重要的成员变量mWorker和mFuture,先说下mWorker说白了就是一个实现了call方法的实现类

        private static abstract class WorkerRunnable<Params, Result> implements Callable<Result> 
    

    call方法中最重要的逻辑就是doInBackground,没错mWorker就是这么点东西。重要的是另一个成员变量
    mFuture,先来看看它的结构

    public class FutureTask<V> implements RunnableFuture<V> {
    public interface RunnableFuture<V> extends Runnable, Future<V> {
    

    实现了RunnableFuture接口,而RunnableFuture接口又实现了Runnable和Future,Runnable自然都不用说大家都懂的,关键是这个Future接口,里面定义了和线程操作相关的几个重要方法,大致可以看一下

        boolean cancel(boolean mayInterruptIfRunning);
        boolean isCancelled();
        boolean isDone();
        V get() throws InterruptedException, ExecutionException;
        V get(long timeout, TimeUnit unit)
            throws InterruptedException, ExecutionException, TimeoutException;
    

    第一个cancel方法是不是很眼熟,AsyncTask调用cancel方法时本质上就是调用了Future上的该方法,先不管实现Future接口的FutureTask究竟是怎么实现cancel方法的,先来锊一下WorkerRunnable和FutureTask的关系

    两者的关系

    WorkerRunnable和FutureTask的关系打个比方的话就是工人和包工头的关系,工人负责干活而包工头负责调度,处理善后工作。WorkerRunnable看名字也就知道是个工人角色,它干活就是调用call方法,劳动成果就是call返回的参数,而FutureTask就是那个包工头了,从代码也能看出

     mFuture = new FutureTask<Result>(mWorker)
    

    包含了那个mWorker对象,FutureTask内部就是通过调用mWorker执行它的call方法来指挥mWorker干活的,所以可以看出来真正干活的实际上是mWorker,但这样就能否认FutureTask不重要吗,当然不可以,干完活后这个劳动成果怎么处理还是需要FutureTask来控制的。

    FutureTask开始执行时机

    既然FutureTask是个包工头,那么这个包工头是什么时候开始让mWorker开始干活的呢,答案就在Asynctask的execute方法中,该方法最终会调用到SerialExecutor的execute方法上,直接看关键代码

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

    execute方法需要传入一个Runnable对象,还记得FutureTask的结构吧,实现了runnable接口,所以实际上这里传入的Runnable对象就是一个FutureTask,r.run()执行的就是FutureTask的run方法,就是从这里开始执行线程的具体任务。

    深入一点点看FutureTask

    看看run里面做了什么事情

    public void run() {
            if (state != NEW ||
                !U.compareAndSwapObject(this, RUNNER, null, Thread.currentThread()))
                return;
            try {
                Callable<V> c = callable;
                if (c != null && state == NEW) {
                    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 must be non-null until state is settled to
                // prevent concurrent calls to run()
                runner = null;
                // state must be re-read after nulling runner to prevent
                // leaked interrupts
                int s = state;
                if (s >= INTERRUPTING)
                    handlePossibleCancellationInterrupt(s);
            }
            
    }
    

    代码有点多,先抓个重点,可以看到在try代码块中执行了c.call操作,最终将得到的结果通过 set(result)给处理了。c就是一个callable对象,对应FutureTask上的worker对象,也就是我说的那个被包工头指挥的工人,worker在FutureTask中的体现也就这么一句代码,剩下的事情都和worker没什么关系了。接下来要做的事情就是好好分析下run里面代码。

    state状态位

    先看看这个if里面做的事,state!=NEW是个什么东西,FutureTask内部通过state这个标志位来确定线程的状态,可以看到FutureTask内部定义了好几种状态

    private volatile int state;
        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;
    

    这里简单说下理解,
    NEW即为初始化状态,在FutureTask构造函数里面执行。
    COMPLETING正在执行状态,在线程正在执行的时候设置
    NORMAL完成状态,在线程执行完毕之后设置
    EXCEPTIONAL异常状态
    CANCELLED线程被取消
    INTERRUPTING,INTERRUPTED线程被中断

    现在回头看

      if (state != NEW ||
                !U.compareAndSwapObject(this, RUNNER, null, Thread.currentThread()))
                return;
    

    如果不是new状态,说明不是初始化状态直接return没什么问题

    CAS操作

    应该说CAS是FutureTask实现多线程安全的精髓所在,在FutureTask内部可以大量看到这种类似的操作,内部原理建议有兴趣的可以去参考下网上的一些分析,比如说这篇文章面试必问的CAS,要多了解,在了解了CAS是什么操作之后再来看这句代码就好理解了。

    U.compareAndSwapObject(this, RUNNER, null, Thread.currentThread())
    

    就是将FutureTask中RUNNER对应的字段从null设置为Thread.currentThread()的过程,关于RUNNER的初始化可以在静态代码块找到

    try {
                STATE = U.objectFieldOffset
                    (FutureTask.class.getDeclaredField("state"));
                RUNNER = U.objectFieldOffset
                    (FutureTask.class.getDeclaredField("runner"));
                WAITERS = U.objectFieldOffset
                    (FutureTask.class.getDeclaredField("waiters"));
            } catch (ReflectiveOperationException e) {
                throw new Error(e);
            }
    

    CAS通过类似c语言指针的形式对相应的字段进行修改。到此关于if语句的逻辑就比较清楚了,就是判断下状态标志位然后将runner字段设置为了Thread.currentThread()。

    set方法分析

    现在FutureTask的run方法就剩下set没看了,来看看里面到底做了些什么

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

    又是CAS操作,现在应该能明白具体意思了吧,将state状态从NEW设置为了COMPLETING,并将结果保存到了outcome中之后又将STATE设置成NORMAL,最终执行finishCompletion,该方法内部最重要的就是执行了done方法。

    关于done方法

    先不管done里面做了什么,从上面代码可以看出在FutureTask的run方法正常执行完毕会调用到done方法,其他地方是否还会调用到done方法呢,确实是存在的,搜索FutureTask中源码可以看出在线程异常,或者FutureTask执行了cancel方法的时候也会调用到done方法,FutureTask中done是一个可以被重写的方法,具体实现可以在Asynctask的构造函数中找到

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

    做的事情很简单,调用下get方法,看看get又在倒腾些什么东西

    public V get() throws InterruptedException, ExecutionException {
            int s = state;
            if (s <= COMPLETING)
                s = awaitDone(false, 0L);
            return report(s);
        }
    
    private V report(int s) throws ExecutionException {
            Object x = outcome;
            if (s == NORMAL)
                return (V)x;
            if (s >= CANCELLED)
                throw new CancellationException();
            throw new ExecutionException((Throwable)x);
        }
    

    可以看到如果是正常执行FutureTask的run方法没有涉及到cancel或者其他异常的情况下state此时是NOrMAL,直接调用report返回最终结果。
    到此我们已经将一次正常的Asynctask所做的事情大致给整理了一遍。

    你以为文章就这样结束了?

    当然不可能,细心的同学可能会发现我之前有一块代码没有分析,就是finishCompletion,当时我是直接跳到了done方法,而忽略了里面的一大块代码,这块代码这里先不贴出,到后面用到的时候再给出,要想明白这里面的作用,就要结合get方法好好分析分析了。只有明白了这块逻辑才算是真正理解了FutureTask的作用

    finishCompletion中的代码需要和awaitDone结合着看才能明白里面的逻辑,回来再看看get方法,可以知道在 if (s <= COMPLETING)的条件下会进入到awaitDone,那么什么时候会符合这个条件,如果有认真看我文章的话可以很快想到在worker还在执行任务没有结果返回时,就符合进入awaitDone条件,这种场景其实很常见,一个任务要执行20秒,你如果在执行到10秒的时候就开始调用get方法显然是不能得到结果的,不能得到结果该怎么办,那就进入awaitDone去看看它怎么处理的。

    private int awaitDone(boolean timed, long nanos)
            throws InterruptedException {
            // The code below is very delicate, to achieve these goals:
            // - call nanoTime exactly once for each call to park
            // - if nanos <= 0L, return promptly without allocation or nanoTime
            // - if nanos == Long.MIN_VALUE, don't underflow
            // - if nanos == Long.MAX_VALUE, and nanoTime is non-monotonic
            //   and we suffer a spurious wakeup, we will do no worse than
            //   to park-spin for a while
            long startTime = 0L;    // Special value 0L means not yet parked
            WaitNode q = null;
            boolean queued = false;
            for (;;) {
                int s = state;
                if (s > COMPLETING) {
                    if (q != null)
                        q.thread = null;
                    return s;
                }
                else if (s == COMPLETING)
                    // We may have already promised (via isDone) that we are done
                    // so never return empty-handed or throw InterruptedException
                    Thread.yield();
                else if (Thread.interrupted()) {
                    removeWaiter(q);
                    throw new InterruptedException();
                }
                else if (q == null) {
                    if (timed && nanos <= 0L)
                        return s;
                    q = new WaitNode();
                }
                else if (!queued)
                    queued = U.compareAndSwapObject(this, WAITERS,
                                                    q.next = waiters, q);
                else if (timed) {
                    final long parkNanos;
                    if (startTime == 0L) { // first time
                        startTime = System.nanoTime();
                        if (startTime == 0L)
                            startTime = 1L;
                        parkNanos = nanos;
                    } else {
                        long elapsed = System.nanoTime() - startTime;
                        if (elapsed >= nanos) {
                            removeWaiter(q);
                            return state;
                        }
                        parkNanos = nanos - elapsed;
                    }
                    // nanoTime may be slow; recheck before parking
                    if (state < COMPLETING)
                        LockSupport.parkNanos(this, parkNanos);
                }
                else
                    LockSupport.park(this);
            }
        }
    

    顺着我说的思路很容易看明白,此时state还是NEW状态,所以直接会进入到

    else if (q == null) {
                    if (timed && nanos <= 0L)
                        return s;
                    q = new WaitNode();
                }
    

    这个分支里面,此时timed为false,直接调用到 q = new WaitNode();WaitNode其实就是一个单链接结构的数据模型,创建完毕之后会进入到第二次循环中此时就是执行

    else if (!queued)
                    queued = U.compareAndSwapObject(this, WAITERS,
                                                    q.next = waiters, q);
    

    将这个链表赋值给了WAITERS字段,再次执行for循环后进入到LockSupport.park(this);将当前线程阻塞住。到此你就能明白调用get方法后如果没有数据返回,调用该方法的线程就是被阻塞住。

    再看finishCompletion方法

    接着上面的分析,阻塞住的线程什么时候会被重新唤醒,答案就在finishCompletion当中了,当worker执行完毕call方法后最终会调用到该方法,展示那块代码的时候到了

    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
        }
    

    当初看着这一大坨代码是不是很懵逼,经过我分析再看是不是已经非常清楚了,它的作用很简单就是把链表上处于阻塞的线程一个个都唤醒起来,唤醒之后原来的线程又会从awaitdone重新开始执行,这里就不再贴awaitdone中的代码了,这时会直接进入到

    if (s > COMPLETING) {
                    if (q != null)
                        q.thread = null;
                    return s;
                }
    

    将s返回回去,此时s的值即为NORMAL,接下来就是将最终的结果返回给get了。

    到此关于一次完整的FutureTask的run执行才算完成。

    终于要结束了?

    长吁一口气,总算把流程给理了一遍,嗯...但是还没结束,搞半天连cancel都没分析,文章标题中说好的cancel分析呢,我之前的所有分析都算是一次正常的流程,没有异常,没有中断,如果在worker执行的中途来一个cancel会怎么样,我们可以来看看里面的代码

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

    看着都是很熟悉的方法,到现在大家应该都能看懂这些代码了,挑重点说,比较有意思的就是参数mayInterruptIfRunning,当设置为true时会调用runner上的interrupt方法,否则直接调用finishCompletion。

    先看第一种情况,mayInterruptIfRunning为false,最终会调用到report中的

    if (s >= CANCELLED)
                throw new CancellationException();
    

    抛出的异常被mFuture直接捕获住,然后就没然后了,这里需要明白一个地方就是抛异常是在ui线程中发生的,因为Asynctask是在ui线程初始化的,所以这个异常对于worker所在线程没有任何影响,worker还是该干嘛就干嘛,换句话说mayInterruptIfRunning为false不能对worker造成干扰。

    再看第二种情况,mayInterruptIfRunning为true,会调用到interrupt,实际结果是interrupt并没有什么卵用,因为它只是将thread的中断标志位设置为了true,能够检测到该中断标志位的方法大致有wait,sleep和join,这些方法内部的原理就是会检测该标志位,当发现是true时会做两件事
    一:重置该标志位 二:抛出中断异常
    这里插个题外话就是interrupted和isinterrupted的区别,interrupted会判断标志位的值并将该标志位重置为false,而isinterrupted的作用就只是判断标志位。

    分析完第二种情况,就能明白,要想interrupt能中断worker,除非你在实现doinbackground方法的时候有调用到检测中断标志位的方法否则第二种情况也不能真正中断当前执行的线程。

    现在大家应该就能明白一些文章中所说的Asnctask的内存泄露问题,在activity的ondestory方法中执行cancel不能阻止doinbackground的执行,但话有说回来,就因为这个就否定Asnctask的作用也是不合适的,其实一些稍微耗时的操作完全可以放在Asnctask中执行,最多也就是activity关闭后小小的泄露几秒,如果连这个都不能接受的话,那你只能另寻方法了。

    小小的题外话

    关于FutureTask中的源码如果仔细看的话会发现state这个状态码是一个volatile,这就会涉及到关于volatile的作用,如果对于这个关键字还一知半解的话建议去网上找找文章。还有另一个点就是FutureTask中对线程进行阻塞和唤醒是通过LockSupport.park(t);和LockSupport.unpark(t);来实现的,仔细思考的话会发现是不是有点类似于wait和notify方法,应该说park和unpark在性能上是高于wait和notify的,可重入锁的内部实现要是没记错的话就是使用LockSupport来实现的,感兴趣的话也可以多去了解一些这方面东西。

    总结

    关于Asynctask的分析就到此结束了,可以有信心说和网上大部分的Asynctask分析都不太一样,也算是小小的深入了一下FutureTask的源码,其实Asynctask我一直觉得是学习线程相关问题一个非常好的事例代码,代码不长除掉注释估计也就300多行,好好的深入分析一下对理解多线程是非常有好处的,网上关于Asynctask的分析很多了,一般都是从Asynctask本身进行分析,所以才有了这么一篇文章,也希望能看到大家在写文章时能更多的有自己的思考。以上都是我的一些个人感想,大家随便看看,该干嘛还是干嘛去,最后祝大家新年快乐。

    坚持写文章实属不易,对大家有帮助的话希望点个赞

    相关文章

      网友评论

        本文标题:AsyncTask带你更深入点看源码

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