AsyncTask分析

简介

Android中只能在主线程中进行UI操作,如果是其它子线程,需要借助异步消息处理机制Handler。除此之外,还有个非常方便的AsyncTask类,这个类内部封装了Handler和线程池。本文先简要介绍AsyncTask的用法,然后分析具体实现。

基本用法

AsyncTask是一个抽象类,我们需要创建子类去继承它,并且重写一些方法。AsyncTask接受三个泛型参数:
-1. Params: 指定传给任务执行时的参数的类型
-2. Progress: 指定后台任务执行时将任务进度返回给UI线程的参数类型
-3. Result: 指定任务完成后返回的结果的类型
除了指定泛型参数,还需要根据需要重写一些方法,常用的如下:
-4. onPreExecute(): 这个方法在UI线程调用,用于在任务执行前做一些初始化操作,如在界面上显示加载进度控件
-5. doInBackground: 在onPreExecute()结束之后立刻在后台线程调用,用于耗时操作。在这个方法中可调用publishProgress方法返回任务的执行进度
-6. onProgressUpdate: 在doInBackground调用publishProgress后被调用,工作在UI线程
-7. onPostExecute: 后台任务结束后被调用,工作在UI线程

源码分析

下面分析这个类的实现,主要有线程池以及Handler两部分。
线程池
当执行一个AsyncTask的时候调用的是execute()方法,就从这个开始看:

public final AsyncTask<Params, Progress, Result> execute(Params... params){
    return executeOnExecutor(sDefaultExecutor, params);
}public 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
    onPreExecute();  
  
    mWorker.mParams = params;  
  
    exec.execute(mFuture);  
    return this; 
} 

execute方法会调用executeOnExecutor。在这个方法中先检查任务是否已经执行或者执行结束,然后把任务标记为running。最开始执行的是onPreExecute,接着把参数赋值给mWorker对象。这个mWorker是一个Callable对象,最终被包装为FutureTask,代码如下:

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

mWorker = new WorkerRunnable<Params, Result>() {  
        public Result call() throws Exception {  
            mTaskInvoked.set(true);  

            Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);  
            //noinspection unchecked  
            return postResult(doInBackground(mParams));  
        }  
    };
    
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 occured while executing doInBackground()",  
                    e.getCause());  
        } catch (CancellationException e) {  
            postResultIfNotInvoked(null);  
        }  
    }  
}; 

从上面的代码可以看出,在mWorker对象中的call()方法会调用doInbackground,返回值交给postResult方法,这个方法通过Handler发送消息,这一点稍后再详细分析。
在mWorker对象被封装成FutureTask之后交由线程池执行,从execute方法可以看出,使用的是sDefaultExecutor,它的值默认为SERIAL_EXECUTOR,也就是串行执行器,实现如下:

 private static class SerialExecutor implements Executor {  
    //线性双向队列,用来存储所有的AsyncTask任务  
    final ArrayDeque<Runnable> mTasks = new ArrayDeque<Runnable>();  
    //当前正在执行的AsyncTask任务  
    Runnable mActive;  

    public synchronized void execute(final Runnable r) {  
        //将新的AsyncTask任务加入到双向队列中  
        mTasks.offer(new Runnable() {  
            public void run() {  
                try {  
                    //执行AsyncTask任务  
                    r.run();  
                } finally {  
                    //当前任务执行结束后执行下一个任务
                    scheduleNext();  
                }  
            }  
        });    
        if (mActive == null) {  
            scheduleNext();  
        }  
    }  

    protected synchronized void scheduleNext() {  
        //从任务队列中取出队列头部的任务,如果有就交给并发线程池去执行  
        if ((mActive = mTasks.poll()) != null) {  
            THREAD_POOL_EXECUTOR.execute(mActive);  
        }  
    }  
}
public static final Executor THREAD_POOL_EXECUTOR  
        = new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE,  
                TimeUnit.SECONDS, sPoolWorkQueue, sThreadFactory);    

在上面的代码中,如果有任务执行,那么SerialExecutor的execute方法会被调用,它的逻辑是把Runnable对象加入ArrayDeque队列中,然后判断mActivie是否为空。第一次执行时mActive当然为空,所以执行scheduleNext,其实就是取出任务队列中的第一个任务交给线程池(THREAD_POOL_EXECUTOR)执行。加入mTask队列的Runnable对象的run方法里最终一定会调用scheduleNext,那么又会从任务队列中取出队头任务执行。这样便实现了单线程顺序执行任务,所以在AsyncTask中默认启用的是单线程执行,只有上一个任务执行后才会执行下一个任务。如果想要启用多线程执行任务,可以直接调用 executeOnExecutor(Executor exec, Params... params),这里的Executor参数可以使用AsyncTask自带的THREAD_POOL_EXECUTOR,也可以自己定义。
Handler
AsyncTask内部用Handler传递消息,它的实现如下:

private static class InternalHandler extends Handler {  
    @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;  
        }  
    }  
} 

如果消息类型是任务执行后的返回值(MESSAGE_POST_RESULT)将调用finish()方法:

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

从上面可以知道,如果任务取消了,将调用onCancelled,否则调用onPostExecute,所以一个AsyncTask任务如果取消了,那么onPostExecute将不会得到执行。
如果消息类型是执行进度(MESSAGE_POST_PROGRESS)将调用onProgressUpdate,这个方法默认是空方法,我们可以根据自己的需要重写。
总结
AsyncTask的主要逻辑就如上面所分析的,总结几个需要注意的地方:
·AsyncTask的类必须在UI线程加载(从4.1开始系统会帮我们自动完成)
·AsyncTask对象必须在UI线程创建
·execute方法必须在UI线程调用
·不要手动调用onPreExecute()、doInBackground、onProgressUpdate方法
·一个任务只能被调用一次(第二次调用会抛出异常)
·多个 AsyncTask 默认是串行执行,可以改为并发执行,但要注意资源同步的问题。
·大量 AsyncTask 任务填满线程池的队列会抛出异常。

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