AsyncTask 你真的了解吗-[Android_YangKe]

AsyncTask

AsyncTask 是一种轻量级的异步任务类,它可以在线程池中执行后台任务,然后把执行的进度和最终结果传递给主线程并在主线程中更新 UI。从实现上来说, AsyncTask 封装了 Thread 和 Handler,通过 AsyncTask 可以更加方便地执行后台任务以及在主线程中访问 UI,但是 AsyncTask 并不适合进行特别耗时的后台任务,对于特别耗时的任务来说,建议使用线程池。

AsyncTask 是一个抽象的泛型类,它提供了 Params、Progress 和 Result 这三个泛型参数,其中 Params 表示参数的类型,Progress 表示后台任务的执行进度的类型,而 Result 则表示后台任务的返回结果的类型,如果 AsyncTask 确实不需要传递具体的参数,那么这三个泛型参数可以用 Void 来代替。AsyncTask 这个类的声明如下。

public abstract class AsyncTask<Params, Progress, Result>。

AsyncTask 提供了 4 个核心方法,他们的含义如下表示。

  • onPreExecute(),在主线程中执行,在异步任务执行之前,此方法会被调用,一般可以用于做一些准备工作。
  • doInBackground(Params...params),在线程池中执行,此方法用于执行异步任务,params 参数表示异步任务的输入参数。在此方法中可以通过 publishProgress 方法来更新任务的进度,publishProgress 方法会调用 onProgressUpdate 方法。另外此方法需要返回计算结果给 onPostExecute 方法。
  • onProgressUpdate(Progress...values),在主线程中执行,当后台任务的执行进度发生改变时此方法会被调用。
  • onPostExecute(Result result),在主线程中执行,在异步任务执行之后,此方法会被调用,其中 result 参数是后台任务的返回值,即 doInBackground 的返回值。

上面几个方法,onPreExecute 先执行,接着是 doInBackground,最后才是 onPostExecute。除了上述四个方法以外,AsyncTask 还提供了 onCancelled 方法,他同样在主线程中执行,当异步任务被取消时,onCancelled 方法会被调用,这个时候 onPostExecute 则不会被调用。下面提供一个典型的示例,如下表示。

private class DownloadFiledTask extends AsyncTask<URL, Integer, Long> {
        protected Long doInBackground (URL... urls) {
                int count = urls.length;
                long totalSize = 0;
                for (int i = 0; i < count; i++) {
                      totalSize += Downloader.downloadFile(urls[i]);
                       publishProgress((int) ((i / (float) count) * 100));
                        //Escape early if cancel() is called
                        if (isCancelled())
                              break;      
                }
                return totalSize;
        }
        protected void onProgressUpdate (Integer... progress) {
                   setProgressPercent(progress[0]);
        }
        
        protected void onPostExecute (Long result) {
                    showDialog("Downloaded" + result + "bytes");
        }
}

在上面的代码中,实现了一个具体的 AsyncTask 类,这个类主要用于模拟文件的下载过程,它的输入参数类型为 URL,后台任务的进程参数为 Integer,而后台任务的返回结果为 Long 类型。注意到 doInBackground 和 onProgressUpdate 方法它们的参数中均包含...的字样,在 Java 中...表示参数的数量不定,它是一种数组形式的参数,...的概念和 C 语言中的...是一致的。当要执行上述下载任务时,可以通过如下方式来完成:

new DownloadFilesTask().execute(url1, url2, url3);

在 DownloadFilesTask 中,doInBackGround 用来执行具体的下载任务并通过 publishProgress 方法来更新下载的进度,同时还要判断下载任务是否被外界取消了。当下载任务完成后,doInBackground 会返回结果,即下载的总字节数。需要注意的是,doInBackground 是在线程池中执行的。onProgressUpdate 用于更新界面中的下载进度,它运行在主线程,当 publishProgress 被调用时,此方法就会别调用,当下载任务执行完成后,onPostExecute 方法就会被调用,它也是运行在主线程中,这个时候我们就可以在界面上做出一些提示,比如弹出一个对话框告知用户下载已经完成。

AsyncTask 在具体使用过程中也是有一些条件限制的,主要有如下几点:

  • AsyncTask 的类必须在主线程中加载,这就意味着第一次访问 AsyncTask 必须发生在主线程,当然这个过程在 Android 4.1 及以上版本中已经被系统自动完成。在 Android 5.0 的源码中,可以查看 ActivityThread 的 main 方法,它会调用 AsyncTask 的init 方法,这就满足了 AsyncTask 的类必须在主线程中进行加载这个条件了。
  • AsyncTask 的对象必须在主线程中创建
  • execute 方法必须在 UI 线程调用
  • 不要在程序中直接调用 onPreExecute、onPostExecute、doInBackground 和 onProgressUpdate 方法。
  • 一个 AsyncTask 对象只能执行一次,即只能调用一次 execute 方法,否则会报运行时异常。
  • 在 Android 1.6 以前,AsyncTask 是串行执行任务的,Android 1.6 的时候 AsyncTask 开始采用线程池处理并行任务,但是从 Android 3.0 开始,为了避免 AsyncTask 所带来的并发错误,AsyncTask 又采用一个线程来串行执行任务。尽管如此,在 Android 3.0 以及后续的版本中,我们仍然可以通过 AsyncTask 的 executeOnExecutor 方法来并行地执行任务。
AsyncTask 的工作原理

为了分析 AsyncTask 的工作原理,我们从它的 execute 方法开始分析,execute 方法又会调用 executeOnExecutor 方法,它们的实现如下所示。

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.");
            break;
            case FINISHED:
                throw new IllegelStateException("Cannot execute task: the task has already been executed (a task can be executed only once)");
            break;
        }
    }
    mStatus = Status.RUNNING;
    onPreExecute();
    mWorker.mParams = params;
    exec.execute(mFuture);
    return this;
}

在上面的代码中,sDefaultExecutor 实际上是一个串行的线程池,一个进程中所有的 AsyncTask 全部在这个串行的线程池中排队执行,这个排队执行的过程后面会在进行分析。在 executeOnExecutor 方法中,AsyncTask 的 onPreExecute 方法最先执行,然后线程池开始执行。下面分析线程池的执行过程,如下所示。

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) {
        mTasks.offer (new Runnable() {
            public void run () {
                try {
                    r.run();
                } finall {
                    scheduleNext();
                }
            }
        });
        if (mActive == null){
            scheduleNext ();
        }
    }

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

从 SerialExecutor 的实现可以分析 AsyncTask 的排队执行的过程。首先系统会把 AsyncTask 的 Param 参数封装为 FutureTask 对象,FutureTask 是一个并发类,在这里它充当了 Runnable 的作用。接着这个 FutureTask 对象插入到任务队列 mTask 中,如果这个时候没有正在活动的 AsyncTask 任务,那么就会调用 SerialExecutor 的 scheduleNext 方法来执行下一个 AsyncTask 任务。同时当一个 AsyncTask 任务执行完成后,AsyncTask 会继续执行其他任务直到所有的任务都被执行为止,从这一点可以看出,在默认情况下,AsyncTask 是串行执行的。

AsyncTask 中有两个线程池(SerialExecutor 和 THREAD_POOL_EXECUTOR) 和一个 Handler (InternalHandler),其中线程池 SerialExecutor 用于任务的排队,而线程池切换到主线程。在 AsyncTask 的构造方法中有如下这么一段代码,由于 FutureTask 的 run 方法会调用 mWorker 的 call 方法,因此 mWorker 的 call 方法最终会在线程池中执行。

mWorker = new WorkerRunnable<Params, Result>() {
    public Result call () throw Exception {
        mTaskInvoked.set(true);
        
        Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
        //noinspection unchecked
        return postResutl(doInBackground(mParams));
    }
};

在 mWorker 的 call 方法中,首先将 mTaskInvoked 设为 true,表示当前任务已经被调用过了,然后执行 AsyncTask 的 doInBackground 方法,接着将其返回给 postResult 方法,它的实现如下所示。

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

在上面的代码中,postResult 方法会通过 sHandler 发送一个 MESSAGE_POST_RESULT 的消息,这个 sHandler 的定义如下所示。

private static final InternalHandler = new InternalHandler ();

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

可以发现,sHandler 是一个静态的 Handler 对象,为了能够将执行环境切换到主线程,这就要求 sHandler 这个对象必须在主线程中创建。由于静态成员在加载类的时候进行初始化,因此这就变相要求 AsyncTask 的类必须在主线程中加载,否则同一个进程中的 AsyncTask 都将无法正常工作。sHandler 收到 MESSAGE_POST_RESULT 这个消息后会调用 AsyncTask 的 finish 方法,如下所示。

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

AsyncTask 的 finish 方法的逻辑比较简单,如果 AsyncTask 被取消执行了,那么就调用 onCancelled 方法,否则就会调用 onPostExecute 方法,可以看到 doInBackground 的返回结果会传递给 onPostExecute 方法,到这里 AsyncTask 的整个过程就分析完毕了。

注:此篇摘自 Android 开发艺术。
完~

喜欢有帮助的话: 双击、评论、转发,动一动你的小手让更多的人知道!关注 Android_YangKe

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

推荐阅读更多精彩内容

  • Android中的线程 线程,在Android中是非常重要的,主线程处理UI界面,子线程处理耗时操作。如果在主线程...
    shenhuniurou阅读 748评论 0 3
  • Android Handler机制系列文章整体内容如下: Android Handler机制1之ThreadAnd...
    隔壁老李头阅读 3,168评论 1 15
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,368评论 25 707
  • 今天中午从丽都的餐厅走出来时下意识看了眼手机,突然被拉进一个叫【海波后援队】的微信群,群里有几十条未读消息。我想起...
    霜巍阅读 177评论 0 0
  • 今年的三伏足足持续了四十天,而且一直热到最后一天,让人无处遁形。可是这天一过,蓉城便开启了秋日模式。大雨中雨小雨,...
    Yumi玉米粒阅读 713评论 7 2