AsyncTask完全解析篇

AsyncTask的基本用法

AsyncTask本身是一个抽象类,若想要使用它,需要创建一个子类去继承它,且必须复写它的抽象方法doInBackground()。
在继承AsyncTask类时需要指定三个泛型参数:

public abstract class AsyncTask<Params, Progress, Result> {
......
}

这三个参数的用途:

  • Params
    在执行AsyncTask的execute(Params)时传入的参数,可用于在doInBackground()任务中使用。
  • Progress
    后台执行任务进度值的类型。
  • Result
    后台任务执行完毕后,如果需要结果返回,可指定为Result类型的泛型数据作为返回值类型。

举个具体的例子:

/**
 * Created by Kathy on 17-2-17.
 */

public class MyTask extends AsyncTask<Object, Integer, Boolean> {


    @Override
    protected Boolean doInBackground(Object... params) {
        return null;
    }
}

第一个泛型参数指定为Object,表示在执行AsyncTask任务时,execute(Object)方法需要传入Object类型的参数给后台;
第二个泛型参数指定为Integer,表示将使用整型数据作为进度显示单位;
第三个泛型参数指定为Boolean,标识用布尔型数据来反馈执行结果。

AsyncTask的基本方法及用途

  1. onPreExecute()
    这个方法在后台任务执行前调用,用于进行界面的初始化,比如显示一个进度条对话框等。

  2. doInBackground(Params... params)
    必须重写的抽象方法!这个方法中的所有代码都会在子线程中运行,我们应该在这里去处理所有的耗时任务。任务一旦完成就可以通过return语句来将任务的执行结果进行返回,如果AsyncTask的第三个泛型参数指定的是Void,就可以不返回任务执行结果。注意,在这个方法中是不可以进行UI操作的,如果需要更新UI,比如说反馈当前任务的执行进度,可以调用publishProgress(Progress...)方法来完成。

  3. publishProgress(Progress... values)
    反馈当前任务的执行进度,在doInBackground()中调用。

  4. onProgressUpdate(Progress... values)
    当在后台任务中调用了publishProgress(Progress...)方法后,这个方法就很快会被调用,方法中携带的参数就是在后台任务中传递过来的。在这个方法中可以对UI进行操作,利用参数中的数值就可以对界面元素进行相应更新。

  5. onPostExecute(Result result)
    当后台任务执行完毕并通过return语句进行返回时,这个方法就很快会被调用。返回的数据会作为参数传递到此方法中,可以利用返回的数据来进行一些UI操作,比如说提醒任务执行的结果,以及关闭掉进度条对话框等。

  6. boolean cancel(boolean mayInterruptIfRunning)
    试图取消任务的执行,但是如果任务已经被完成会取消失败。执行这个方法会导致doInBackground()执行完后不会去执行onPostExecute()方法,而是执行onCancelled()方法。

  7. boolean isCancelled()
    如果在任务执行完成前被取消,该方法返回true。

  8. onCancelled(Result result)
    任务被取消后回调的方法。

简单实例

    class DownloadImageTask extends AsyncTask<Void, Integer, Boolean> {  
      
        @Override  
        protected void onPreExecute() {  
            // 显示进度条
            progressBar.show();  
        }  
      
        @Override  
        protected Boolean doInBackground(Void... params) { 
                   int downloadprogress = doDownload();  
                   // 通知进度条更新UI
                   publishProgress(progress);
            return true;  
        }  
      
        @Override  
        protected void onProgressUpdate(Integer... values) {  、
            // 更新进度条
            progressBar.setProgress(values[0] );  
        }  
      
        @Override  
        protected void onPostExecute(Boolean result) {  
            // 进度条消失
            progressBar.setVisibility(View.INVISIBLE);
            progressBar.setProgress(0);
        }  
    }  

在UI线程里执行上述任务,只需要调用如下execute()方法即可:

new DownloadImageTask().execute(); 

源码角度分析AsynTask

启动AsynTask任务,需要构建它的实例,首先来看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);
            }
        };
e
        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);
                }
            }
        };
    }

AsyncTask的构造函数只是初始化了两个变量mWorker和mFuture,并在初始化mFuture时传入了mWorker作为参数。

如果需要启动某个任务,需要调用AsyncTask的execute()方法:

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

    //向下调用
    @MainThread
    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;
    }

这里判断了任务的状态,任务有三种状态:PENDING / RUNNING / FINISHED

    /**
     * Indicates the current status of the task. Each status will be set only once
     * during the lifetime of a task.
     */
    public enum Status {
        /**
         * Indicates that the task has not been executed yet.
         */
        PENDING,
        /**
         * Indicates that the task is running.
         */
        RUNNING,
        /**
         * Indicates that {@link AsyncTask#onPostExecute} has finished.
         */
        FINISHED,
    }

可以发现execute()执行方法中,最先被调用的是onPreExecute()方法。接下来发现调用了exec.execute(mFuture)这个语句并将mFuture这个FutureTask对象传递进来,那么exec是Executor类型的,且向上追溯,是执行execute()方法时由sDefaultExecutor传递进来的,且看sDefaultExecutor定义的地方:

    /**
     * An {@link Executor} that executes tasks one at a time in serial
     * order.  This serialization is global to a particular process.
     */
    public static final Executor SERIAL_EXECUTOR = new SerialExecutor();
    private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;

可以看到sDefaultExecutor的赋值是一个常量,且为SerialExecutor类的实例,则exec.execute(mFuture)的执行方法可以追溯到SerialExecutor的execute()方法中。

    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.run();
                    } finally {
                        scheduleNext();
                    }
                }
            });
            if (mActive == null) {
                scheduleNext();
            }
        }

        protected synchronized void scheduleNext() {
            if ((mActive = mTasks.poll()) != null) {
                THREAD_POOL_EXECUTOR.execute(mActive);
            }
        }
    }

可以看到SerialExecutor类中也有一个execute()方法,这个方法里的所有逻辑就是在子线程中执行的。且执行execute()方法时传入的Runnable对象为FutureTask对象,所以会调用到FutureTask类的run()方法。最终的最终,会执行到mWorker对象的call()方法:

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

可以看到以上方法中调用到了doInBackground(mParams)去做耗时处理,所以doInBackground()实在onPreExcute()方法之后执行,且在子线程中执行。
接下来,将返回值类型为Result的doInBackground()的执行结果传递到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;
    }

postResult()方法的作用是什么呢?它使用Handler发送一条消息MESSAGE_POST_RESULT,并携带上AsyncTaskResult对象,这个对象中携带任务执行的结果。那么接收这条消息的回调方法在哪儿呢?这个取决于发送消息的Handler:

    private static Handler getHandler() {
        synchronized (AsyncTask.class) {
            if (sHandler == null) {
                sHandler = new InternalHandler();
            }
            return sHandler;
        }
    }

InternalHandler的回调方法如下,且发现InternalHandler的Looper是主线程的,所以InternalHandler的回调方法是执行在主线程中!

    private static class InternalHandler extends Handler {
        public InternalHandler() {
            super(Looper.getMainLooper());
        }

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

从以上InternalHandler可以看到:
1.如果收到MESSAGE_POST_RESULT消息,会执行finish()方法;
2.如果收到MESSAGE_POST_PROGRESS消息,会执行onProgressUpdate()方法。
我们来看finish()方法:

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

逻辑很清晰了,执行完doInbackground()方法后,返回的结果通过IntenalHandler的实例发送消息传递给IntenalHandler的回调方法,最后,在主线程中执行finish()方法;如果任务执行被取消,则执行onCancelled()方法;如果没有被取消,则继续向下执行onPostExecute()方法,且将状态设置为Status.FINISHED。

可以看到,在IntenalHandler的回调方法中接收另外一个消息MESSAGE_POST_PROGRESS,这个消息是在哪儿发送的呢?

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

以上可知,在doInBackground()方法中通过调用publishProgress()实现子线程 和UI线程通信,publishProgress()通过handler发送消息,在主线程中接收消息的地方会执行到onProgressUpdate()方法去更新UI。

综上可知,Asynctask背后使用的仍然是Handler异步消息处理机制,只是在源码层做了封装,我们在使用时,不必考虑而已。

使用AsyncTask的注意事项

  • AsynTask的实例需要在UI线程中创建
  • execute(Params... params)方法必须在UI线程中调用
  • AsynTask的doInBackground(Prams)方法执行异步任务运行在子线程中,其他方法运行在主线程中,可以操作UI组件。
  • 运行后,可以随时调用AsynTask对象的cancel(boolean)方法取消任务,如果成功,调用isCancelled()将返回true,并且不再执行onPostExcute()方法,而是执行onCancelled() 方法。

你必须知道的AsyncTask的缺陷

1.生命周期
AsyncTask不随着Activity的生命周期的销毁而销毁,这使得AsyncTask执行完成后,Activity可能已经不在了。AsyncTask会一直执行doInBackground()方法直到执行结束。一旦上述方法结束,会依据情况进行不同的操作。
但是,AsyncTask的cancel(mayInterruptIfRunning)可以通过传入一个布尔值来打断执行的任务,mayInterruptIfRunning设置为true,表示打断任务,正在执行的任务会继续执行直到完成。但是可以在doInBackground()方法中有一个循环操作,通过调用isCancelled()来判断任务是否被打断,如果isCancelled() == true,则应避免再执行后续的一些操作。
总之,使用AsyncTask需要确保AsyncTask正确地取消。

2.内存泄露风险
在Activity中使用非静态匿名内部AsyncTask类,由于Java内部类的特点,AsyncTask内部类会持有外部类的隐式引用。由于AsyncTask的生命周期可能比Activity的长,当Activity进行销毁AsyncTask还在执行时,由于AsyncTask持有Activity的引用,导致Activity对象无法回收,进而产生内存泄露。

3.结果丢失
另一个问题就是在屏幕旋转等造成Activity重新创建时AsyncTask数据丢失的问题。当Activity销毁并重新创建后,还在运行的AsyncTask会持有一个Activity的非法引用即之前的Activity实例。

4.串行还是并行?

    new AsyncTask1.execute();  
    new AsyncTask2.execute();  

上面两个任务是同时执行呢,还是AsyncTask1执行结束之后,AsyncTask2才能执行呢?实际上是结果依据API不同而不同。

关于AsyncTask时串行还是并行有很多疑问,这很正常,因为它经过多次的修改。
在1.6(Donut)之前:
在第一版的AsyncTask,任务是串行调度。一个任务执行完成另一个才能执行。由于串行执行任务,使用多个AsyncTask可能会带来有些问题。所以这并不是一个很好的处理异步(尤其是需要将结果作用于UI试图)操作的方法。

从1.6到2.3(Gingerbread)
后来Android团队决定让AsyncTask并行来解决1.6之前引起的问题,这个问题是解决了,新的问题又出现了。很多开发者实际上依赖于顺序执行的行为。于是很多并发的问题蜂拥而至。

3.0(Honeycomb)到现在
好吧,开发者可能并不喜欢让AsyncTask并行,于是Android团队又把AsyncTask改成了串行。当然这一次的修改并没有完全禁止AsyncTask并行。你可以通过设置executeOnExecutor(Executor)来实现多个AsyncTask并行。关于API文档的描述如下
If we want to make sure we have control over the execution, whether it will run serially or parallel, we can check at runtime with this code to make sure it runs parallel

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

推荐阅读更多精彩内容