AsyncTask源码分析

使用AsyncTask的一般步骤是:

  • 定义一个类继承自AsyncTask,实现抽象方法
  • new 一个AsyncTask对象
  • 调用execute()方法执行任务

那么就一步一步来分析AsyncTask的实现原理,首先看构造函数

/** * Creates a new asynchronous task. This constructor must be invoked on the UI thread. */
public AsyncTask() {
    mWorker = new WorkerRunnable<Params, Result>() {
        public Result call() throws Exception {
            mTaskInvoked.set(true); 
           Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);            //noinspection unchecked
            Result result = doInBackground(mParams);
            Binder.flushPendingCommands();
            return postResult(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);
            }
        } 
   }}

构造函数很简单,就是初始化了2个全局的变量mWorkermFuture,并在创建mFuture的时候把mWorker作为参数传递进去。其中mWorker实现了Callable,mFuture是一个FutureTask。关于Callable和FutureTask请参考

然后执行execute()方法

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

调用了executeOnExecutor(sDefaultExecutor, params);params就是execute()传入的参数,这个sDefaultExecutor是在哪里定义的呢?

public static final Executor SERIAL_EXECUTOR = new SerialExecutor();
private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;

原来sDefaultExecutor是AsyncTask内部的一个常量指向的是SerialExecutor。继续跟进executeOnExecutor().

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

从这个函数可以看出,AsyncTask一共有三种状态,并且非PENDING状态下调用execute()都会抛出异常

  • PENDING 初始状态
  • RUNNING 执行状态
  • FINISHED 完成状态

第一次调用execute()会把状态置为RUNNING,任务完成时会把状态置为FINISHED。这就要求一个AsyncTask只能执行一次execute()只能完成一个后台任务,如果需要处理多个任务,只有重新创建一个AsyncTask

回到上面的函数,可以看到 onPreExecute()被最先调用,所以我们可以在这个回调中做一些初始化操作比如开始加载动画(这就是execute()一般都会在UI线程调用的原因)。然后把execute(Parem... param)传递过来的param赋值给在构造中初始化好的mWorkermParams变量。

再执行exec.execute(mFuture),其中exec是上文可以知道指向的是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就是mFuture
                    r.run();
                }
               finally {
                    scheduleNext();
                }
            }
        });
        if (mActive == null) {
            scheduleNext();
        }
    }
    protected synchronized void scheduleNext() {
        if ((mActive = mTasks.poll()) != null) {
            THREAD_POOL_EXECUTOR.execute(mActive);
        }
    }
}

可以看到这里调用了mFuture的run()方法,在来看看这个方法

public void run() {
    Callable<V> c = callable;
    if (c != null && state == NEW) { 
       V result;
        boolean ran;
      //这个的c就是mFuture创建时候,传递的参数mWorker 
        result = c.call();
        ran = true;
    }
}

注意c.call(),这个c什么呢?其实就是在AsyncTask构造中创建mFuture的参数mWorker,转了一大圈其实就是调用了mWorker.call()方法

mWorker = new WorkerRunnable<Params, Result>() {
    public Result call() throws Exception {
        mTaskInvoked.set(true);
        Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
        //调用doInBackground()做后台任务,mParams就是调用execute()时传递的参数
        Result result = doInBackground(mParams); 
        Binder.flushPendingCommands();
        return postResult(result);
    }
};

可以看到在这个方法中回调了doInBackground(mParams),而参数就是我们调用execute()时传递的参数。因为WorkerRunnable是实现Runnable接口的所以doInBackground的确是在子线程中执行的。

doInBackground(mParams)完成我们自己的后台逻辑之后,把结果作为参数传递给了postResult(result)

private Result postResult(Result result) {
    @SuppressWarnings("unchecked")
    Message message = getHandler().obtainMessage(MESSAGE_POST_RESULT,new AsyncTaskResult<Result>(this, result)); 
    message.sendToTarget();
    return result;
}
private static Handler getHandler() {
    synchronized (AsyncTask.class) {
        if (sHandler == null) {
            sHandler = new InternalHandler();
        }        return sHandler;
    }
}
private static class InternalHandler extends Handler {
    public InternalHandler() {
        //获取主线程Looper来构造Handler
        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);
                break;
        }
    }
}

这里代码比较简单,就是后台任务返回结果后,把结果封装进message发送,Handler接受到消息后,根据消息类型执行不同的逻辑。我们看到有2种消息类型,MESSAGE_POST_RESULT就是后台任务完成了,而MESSAGE_POST_PROGRESS看命名也大概知道了,没错,就是我们调用publishProgress()更新进度的时候就会发送这个类型的消息

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

注意 : 3.0 以后,默认会使用主线程的Looper来构造handler,所以不管AsyncTask在那个线程创建或者execute()在那个线程调用,onPostExecute(result)publishProgress()都是在主线程执行

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

finish()中判断后台任务时候取消过,是,onPostExecute(result)就不再调用,而是回调onCancelled(result)。并且设置状态,保证AsyncTask只能执行一次。

3.0之后,AsyncTask默认是串行执行任务的,来看看是怎么实现串行的

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就是mFuture
                    r.run();
                }
               finally {
//上一个任务执行完毕后,才会从队列中取出下一个任务
                    scheduleNext();
                }
            }
        });
        if (mActive == null) {
            scheduleNext();
        }
    }
//取出队列头部的任务
    protected synchronized void scheduleNext() {
        if ((mActive = mTasks.poll()) != null) {
            THREAD_POOL_EXECUTOR.execute(mActive);
        }
    }
}

调用AsyncTask.execute()后会调用SerialExecutor.execute(),这是AsyncTask中维护的一个静态串行控制器,不管创建多少个AsyncTask都会使用同一个SerialExecutor来完成串行控制

ArrayDeque是一个线性双向队列,每一次调用SerialExecutor.execute()都会把传进来的任务(FutureTask)加入到这个队列中。首先判断mActive是否为空第一次肯定是null,调用schecduleNext()会从队列中取出头部任务,交给AsyncTask维护的线程池去执行,第二次mActive!=nullschecduleNext()就执行不到,那如果第二个任务怎么执行呢?注意try..finally中的逻辑,在前一个.run执行完之后,才会又一次schecduleNext()被执行。这就实现了任务的串行控制

SerialExecutor是一个串行控制器,他会把加入的任务按顺序加入到任务队列中,给任务排序,然后一个一个去处交给AsyncTask中维护的线程池执行。

AsyncTask中维护的线程池是一个ThreadPoolExecutor,这是一个并发线程池,3.0以前默认同一时刻运行的线程数是5个,最大线程数是128个,3.0以后根据CPU配置而定。

所以,在3.0以后我们也是可以让AsyncTask并行执行的,就是不让SerialExecutor来控制任务的串行,即调用AsyncTask.executeOnExcutor(THREAD_POOL_EXECUTOR),参数参入线程池的引用,这样任务就跳过了排序,被直接交给了线程池去执行

AsyncTask的缺点

AsyncTask我们肯定都用过,优点就不说了,使用简单,简化代码,过程可控。我们来说一说它的缺点

  • 使用不当可能造成内存泄漏。因为涉及到异步操作,非静态内部类/匿名内部类AsyncTask都有可能引起activity的泄漏

  • 任务有没有正确取消导致nullPointer。AsyncTask并不会随着创建它的activity的生命周期的结束而结束,相反它会一直执行直到doInBackground()方法执行完毕,如果我们的Activity销毁之前,没有取消 AsyncTask,就有可能引起Crash,因为它想要处理的view已经不存在了。并且,就算调用cancle(true)来取消任务,也不一定保证成功,因为这个方法是调用Thread.interrupt(),如果正在做一个IO操作,还会抛出 ClosedByInterruptException异常

  • 结果丢失。屏幕旋转或Activity在后台被系统杀掉等情况会导致Activity的重新创建,之前运行的AsyncTask会持有一个之前Activity的引用,这个引用已经无效,这时调用onPostExecute()再去更新界面将不再生效。

的确以上都是在使用AsnycTask过程中可能遇到的问题,但是我认为这些都不是AsyncTask自身的缺陷,而是程序猿代码设计的问题,因为使用Thread+handler或者相似的类库都可能出现同样的问题。比如内存泄漏,我们完全可以用静态内部类+弱引用、或者是用MVP解耦来解决。对于cancle()可能不正常取消,那也是Thread的问题。

** AsyncTask真正的缺点是处理多个任务和控制串行、并行**

  • 多任务处理,通过源码我们知道一个AsyncTask调用一次execute()处理一次后台任务,如果有多个任务的时候只能多次创建,就像这样的代码
task1.execute()
task2.execute()
task3.execute()

我们还得关心每个task的回调,每一个任务的cancle处理,并且如果每个任务需要的泛型参数不同的话,就的写多个AsyncTask。

  • 串行、并行控制。AsyncTask内部维护了一个静态并发线程池THREAD_POOL_EXECUTOR和一个静态串行任务执行器**SERIAL_EXECUTOR **,这个执行器中实现了串行控制,会循环的取出一个个任务交给并发线程池去执行 。也就是说同时有多个Task.execute的时候,其实也是按顺序一个一个串行执行的。如果确实需要并行执行的时候,就需要手动调用AsyncTask.executeOnExecutor(THREAD_POOL_EXECUTOR, Params... params)传入上述静态并发线程池。所以,AsyncTask在处理串并行的时候还是显得比较尴尬

总结

  • AsyncTask的内部封装线程池和Handler,暴露不同的执行在不同线程的回调函数,大大简化程序猿写子线程处理耗时任务--主线程刷新UI的逻辑

  • 常常在网上或者面试的时候被问到为什么AsyncTask必须在主线程创建或者execute()必须在主线程执行?
    我觉得这种说法至少不准确的,的分版本,3.0以前,创建AsyncTask的时候会根据当前线程创建一个Handler,如果是子线程而我们又没有手动初始化Looper,程序会直接crash(而3.0以后会默认用主线程Looper创建HandleronPostExecute(),publishProgress()等回调都会执行在AsyncTask创建的的线程,execute()中会回调onPreExecute(),如果我们在这些回调中更新UI就一定会抛出异常。

所以,3.0以前创建AsynacTask必须在主线程,而3.0以后只要不在onPreExecute(),publishProgress(),onPostExecute()回调中更新UI,AsyncTask完全可以不在主线程创建。但是这明显跟我们日常的使用相悖


最后附一篇任玉刚大神分析的源码,代码注释非常详细

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

推荐阅读更多精彩内容