AsyncTask 任务


一、介绍

AsyncTask 类是一个异步类,监听异步任务的执行进度,通知主线程,耗时任务且主线程需要知道执行过程的场景下使用,比如进度条更新。消息通知模型采用线程池 + Handler,异步任务结果获取采用 FutureTask + Callable。

AsyncTask模型
private static InternalHandler sHandler;

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:
                result.mTask.finish(result.mData[0]);
                break;
            case MESSAGE_POST_PROGRESS:
                result.mTask.onProgressUpdate(result.mData);
                break;
        }
    }
}

InternalHandler 类,静态内部类,主线程 Looper 绑定,主线程处理任务进度和结果,所有 AsyncTask 对象公用同一个静态 Handler 对象。
AsyncTaskResult 类,封装 AsyncTask 对象,区分具体消息发送者。
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方法,更新进度,AsyncTask类通常会重写该方法。
任务完成,调用AsyncTask的postResult方法,通过InternalHandler类发送结果。

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

主线程AsyncTask的finish方法,调用重写的onPostExecute方法。

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

AsyncTask不清楚耗时操作产生的中间值做的事情,它只提供一个后台运行框架,中间值操作由使用者完成,子类实现。

二、原理

采用 Callable+Future 结构获取任务结果,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);
            }
        }
    };
}

创建WorkerRunnable和FutureTask任务,WorkerRunnable是Callable类,和Runnable类似,执行主体是call方法,可以返回执行结果。
在call方法,调用doInBackground方法,做用户设定的耗时事情,将Result返回,最后,在finally代码块,postResult方法,发送消息通知结果,call方法结束。
FutureTask是Runnable类任务,实现Future接口,内部封装Callable任务,通过Future接口获取结果,重写done方法,处理结果。

AsyncTask内部任务关系

创建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保存,push到线程池SerialExecutor的具体任务是FutureTask。

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取下一个任务。
SerialExecutor功能是控制任务按顺序串行交给线程池。

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)任务队列,任务是由SerialExecutor一次一个的顺序push的,当前任务完成,下一个任务创建新线程(<核心数量),线程数量达到核心数量,随机使用老线程处理。

注意

在Android2.3,API11前,直接采用5个线程的线程池,API11后,加入线程池SerialExecutor,变成串行。因此,可以加入判断,大于11时直接使用线程池THREAD_POOL_EXECUTOR,绕过SerialExecutor可避免串行。

三、任务

线程池THREAD_POOL_EXECUTOR处理任务,新Runnable的run方法,FutureTask的#run方法,触发Callable的call方法,即WorkerRunnable#call方法,最终,耗时操作是doInBackground方法。

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;

FutureTask有七种状态,新建,完成,普通,异常,取消和中断,前三种是正常状态,后四种是异常状态,执行过程触发状态变化。

public FutureTask(Callable<V> callable) {
    if (callable == null)
        throw new NullPointerException();
    this.callable = callable;
    this.state = NEW;
}

FutureTask构造方法,初始化NEW状态。

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

run方法,必须执行NEW状态的,若不是NEW或compareAndSwapXxx原子方法设置runner为当前线程(且初值需要是null)不成功,直接返回。
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子类重写,利用get方法获取结果,传递给AsyncTask类的postResultIfNotInvoked方法。

private void postResultIfNotInvoked(Result result) {
    final boolean wasTaskInvoked = mTaskInvoked.get();
    if (!wasTaskInvoked) {
        postResult(result);
    }
}

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

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方法。

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

通过Future接口控制任务取消,改变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。
最后,调用finishCompletion方法,进入done方法,这种情况下get方法结果时,状态是INTERRUPTED或CANCELLED,report方法会抛出CancellationException异常。在done方法,捕捉到CancellationException异常,执行postResultIfNotInvoked时,传入结果null。
耗时任务会继续执行,当call方法完成,postResult方法派送结果时,根据状态触发onCancelled方法,而不是onPostExecute方法。

注意

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

四、总结

AsyncTask 类是一个可获取过程与结果的任务处理类,启动后,后台执行,进度更新(如果需要) ,结果处理,方法不需要亲自调用,只需要重写。

不一定由主线程创建 AsyncTask ,因内部 Handler 已经自动绑定主线程Looper。
SerialExecutor 的任务队列依次执行,也可以自己定义线程池。


任重而道远

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,569评论 4 363
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,499评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,271评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,087评论 0 209
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,474评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,670评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,911评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,636评论 0 202
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,397评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,607评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,093评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,418评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,074评论 3 237
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,092评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,865评论 0 196
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,726评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,627评论 2 270

推荐阅读更多精彩内容