android_AsyncTask的源码解析

AsyncTask的相关结论

下面是AsyncTask的相关结论,相关结论都会在下面的源码分析中一一印证。

  1. onPreExecute()是在异步任务执行之前调用的,它是在主线程中运行。
  2. doInBackground()是在子线程中运行,该方法执行的耗时的异步任务。
  3. onPostExecute(Result result)是执行完doInBackground之后被调用,参数result就是doInBackground计算的结果。
  4. 1,2,3以上3个方法不用手动调用,它们的调用都是AsyncTask自己去调用的。
  5. onProgressUpdate(Progress...values)在UI线程中执行,用于时时的更新UI,以进度条的方式,增加用户体验。
  6. protected final void publishProgress(Progress... values) 该方法一般会在doInBackground中执行,将计算的数据时时通过该方法传递给onProgressUpdate方法。该方法是final类型的。
  7. 异步任务就是能执行耗时耗时操作,并且将操作结果通知到UI线程中,完成此操作就需要使用到Handler消息机制,在AsyncTask中是通过InternalHandler去实现的。
  8. AsyncTask通过线程池去维护多个异步任务的创建和运行。
  9. AsyncTask实例需要在主线程中去实例化,因为postExecute是AsyncTask的方法,AsyncTask若是在子线程中运行,那么该方法也是在非UI线程调用,那么达不到刷新UI的效果了。
  10. AsyncTask#execute()需要在主线程调用,若在子线程中调用,那么在onPreExecute就会在子线程中运行,那么操作UI相关的操作就会出问题。
  11. AsyncTask异步任务不可以被执行多次,每一个异步任务都会有一个状态标记,分别为PENDDING,RUNING,FINISH,只要状态不是为PENDING都会抛出异常。
  12. AsyncTask#cancel(boolean)设置取消异步任务,设置之后会调用onCancelled()通知用户。
  13. AsyncTask#onCancelled()在finish()中判断当前状态是否为取消状态,取消状态的话则调用onCancelled(Result)方法。
  14. AsyncTask默认是串行执行的,需要并行执行异步任务的话则需要在executeOnExecutor方法传递一个Executor就可以实现并行执行。

1. AsyncTask构造方法

实例化对象中,实例化了mWorker,mFuture对象,具体实现待会具体分析。

public AsyncTask() {
    mWorker = new WorkerRunnable<Params, Result>() {
    public Result call() throws Exception{
            ...
        }
    };
    mFuture = new FutureTask<Result>(mWorker) {
    @Override
    protected void done() {
            ...
        }
    };
}

2.WorkerRunnable&FutureTask

从类的定义可以知道mWorker是Callable的子类对象,mFuture是Runnabel的子类对象。

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

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

3.AsyncTask#execute(Params... params)

有了AsyncTask实例之后,怎样去开始执行一个异步任务?可以通过execute方法开启一个异步任务。有一个疑问?params是什么?我们看源码得出去找params用来干嘛。execute会通过executeOnExecutor(Executor exec,Params... params) 去执行任务。

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

4.AsyncTask#executeOnExecutor(Executor exec,Params... params)

从源码中可知道execute传递的params原来是会被赋值给mWorker.params,具体什么作用,我们先不分析,待会用到再说。现在我们看到一个熟悉的方法,那就是onPreExecute(),这证实了AsyncTask的第(1)条结论,那就是onPreExecute()是在主线程中运行(前提:AsyncTask是在主线程创建的),然后是在异步任务执行之前调用,通常用于准备工作。

@MainThread
public final AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec,Params... params) {
    ...
    onPreExecute();//异步执行之前调用。
    mWorker.mParams = params;
    exec.execute(mFuture);//将任务放到线程池的,开始异步任务。
    return this;
}

疑问1:exec是什么?

它是一个线程池,从传入的参数sDefaultExecutor可以看出,它是一个默认的线程池,内部维护一个ArrayDeQue队列。AsyncTask里默认维护了一个静态的线程池,不管有多少个AsyncTask调用execute方法都会去共享这个静态的线程池,该线程池只允许一次运行一个任务,多余的任务需要等待,这印证了第(8)点结论。SerialExecutor内部只是维护了一个队列,真正执行任务还是THREAD_POOL_EXECUTOR 去执行。代码如下:

public static final Executor SERIAL_EXECUTOR = new SerialExecutor();
private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;//默认的线程池就是它
private static class SerialExecutor implements Executor {
    final ArrayDeque<Runnable> mTasks = new ArrayDeque<Runnable>();//内部维护一个队列

    public synchronized void execute(final Runnable r) {
        ...
        //真正执行任务还是THREAD_POOL_EXECUTOR 去执行,代码暂时不贴。
    }
     ...
}

疑问2:怎么体现异步任务在子线程执行?

答案就在SerialExecutor#execute怎么去执行Runnbale的?mTasks.offer将一个Runnabel添加到mTask中,这样做的目的就是按顺序地去执行任务,具体的实现就在scheduleNext()中。现在所处就是在子线程中了

 private static class SerialExecutor implements Executor {
    final ArrayDeque<Runnable> mTasks = new ArrayDeque<Runnable>();//不支持并发访问,是一个Collection数据结构,不安全
        Runnable mActive;

     public synchronized void execute(final Runnable r) {
         mTasks.offer(new Runnable() {//插入一个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);
         }
     }
}

5.FuturnTask#run()

execute方法的最终会调用Runnable中run方法被执行,也就是说mFuture#run会被执行,接下来看看run怎么实现的

public void run() {
    Callable<V> c = callable;//就是 mWorker 对象
    ...
    V result;
    result = c.call();//开始计算
    ...
    if (ran)
        set(result);
}
           

疑问1:callable是什么?

callable实际上就是在AsyncTask构造中传递给mFuture的构造参数(mWorker对象)

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

疑问2:result又是什么?

分析:result = c.call()将子线程中运行的结果赋值给result。result的类型就是Callable<V>的泛型类型。在mFuture初始化的时候已经被指定Result类型,也就是在AscnycTask异步任务执行结果返回值类型。接下来看看call怎么去计算异步结果的。

6.计算异步结果WorkerRunnable#call()

标记postResult方法调用的标记,这句话的作用现暂时放着,待会再解释。这时候我们又看到熟悉的身影了(doInBackground),这段代码实在子线程中执行的,也就印证了第(2)条结论。将返回结果传递给postResult,接下来看看postResult的实现

public Result call() throws Exception {
    mTaskInvoked.set(true);//标记postResult为被调用
    Result result = doInBackground(mParams);
    return postResult(result);
};

7.AsyncTask#postResult

大家看到这里相信应该都有点眉目了,刚才call方法是在子线程中运行的,现在出现的Message和Handler的身影,那么100%就将异步结果放到主线程中去。我们看看怎么实现:

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

8.InternalHandler消息机制实现消息的放松

既然需要更新UI,那么就需要将消息发送到主线程中去,那么就得拿到主线程中的Looper轮训器,在InternalHandler中的构造可以看到,获取到的Looper是主线程中的。
在这里可以看到两个case:MESSAGE_POST_RESULT是在postResult中调用,MESSAGE_POST_PROGRESS是在publishProgress中调用的
然后在onProgressUpdate中去调用,该方法是在需要子类去实现处理的,在该方法中将数据回调在主线程去。 印证了第(7)点结论

public InternalHandler() {
    super(Looper.getMainLooper());
}

@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);//印证第(5)结论
        break;
        }
    }
}

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

9.AsyncTask#finish(Result result)

在该方法中我又看到一个熟悉的身影那就是onPostExecute,由于finish是在handleMessage中调用的,所以它也是在主线程中调用,并且有Result结果,可以更新UI。 这里就印证了第(3)条结论 。

private void finish(Result result) {
    if (isCancelled()) {//判断是否取消
        onCancelled(result);
    } else {//没有取消
        onPostExecute(result);//将结果传递给该方法,主线程更新UI
    }
    mStatus = Status.FINISHED;//更新标记为完成
}

10.FuturnTask#set(result)

在call方法将结果计算之后,就需要将该结果保存起来,set方法就是做这件事的,在set中将result结果赋值给outcome,并且调用FutureTask#done(),看到这里我们会有点印象,那就是在 AsyncTask构造中实例化mFuture时重写了done()方法,这个方法就是在这里回调的 。下面我们看看done做了什么事?

protected void set(V v) {
    if (U.compareAndSwapInt(this, STATE, NEW, COMPLETING)) {
        //将计算的结果保存到 outcome 中。
        outcome = v;
        U.putOrderedInt(this, STATE, NORMAL); // final state
        finishCompletion();
    }
}

11.FutureTask#done()

该方法由子类去实现,当异步任务执行完毕之后会调用。该方法被调用表示Callable#call()方法的计算结果已经被保存在outcome中了,在需要的地方取出即可。done()除了在finishCompletion()中调用外,还有innerCancel(boolean)中调用,这个接下来再分析。

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

get()是什么?可以源码直到,它实质上返回就是我们刚才说的call方法计算返回的值(outcome)。该值传递给postResultIfNotInvoked(Result result)又做了什么事呢?

12.postResultIfNotInvoked(Result result)

当看到这个wasTaskInvoked这个值的时候,我们应该有一丢丢印象,那就是在call方法执行的该标记被置为true了。这时候被取出来也是为true,那么接下来的代码就没执行了。现在有一个疑问,if条件在什么情况会成立呢?请看异步任务取消的代码就直到了。

private void postResultIfNotInvoked(Result result) {
    final boolean wasTaskInvoked = mTaskInvoked.get();
    if (!wasTaskInvoked) {//wasTaskInvoked这个标记唯一被赋值的地方就是call方法,标记为true表示postResult被调用,那么就不要再调用postResultIfNotInvoked方法了,除非cancel了该任务,导致在call方法没有对该标记赋值,所以该方法就会被调用。
        postResult(result);
    }
}

13.AsyncTask#postResult()

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

14.取消异步任务AsyncTask#cancel(boolean)

将取消标记为true,并且调用mFuture#cancel取消任务。

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

15.FutureTask#cance(boolean)

在该方法中也执行了done()方法,该方法在AsyncTask构造中已经可以看到,直接回到先前的代码查阅,它会去调用postResultIfNotInvoked(Result),这就解释了为什么这个方法的命名,当异步任务没有执行完毕,它是不会去执行设置mTaskInvoked.set(true)的,因此该方法就可以顺利成章的执行了。代码最终会去执行postResult。

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

推荐阅读更多精彩内容