AsyncTask源码分析

作者: thinkChao | 来源:发表于2017-07-24 00:13 被阅读138次

基础了解

介绍

在Android中实现异步任务机制有两种方式:Handler和AsyncTask。

Handler模式需要为每一个任务创建一个新的线程,任务完成后通过Handler实例向UI线程发送消息,完成界面的更新,这种方式对于整个过程的控制比较精细,但也是有缺点的,例如代码相对臃肿,在多个任务同时执行时,不易对线程进行精确的控制。

为了简化操作,Android提供了工具类.AsyncTask,它使创建异步任务变得更加简单,不再需要编写任务线程和Handler实例即可完成相同的任务,但其内部也是使用Handler来传递消息,而且基于线程池。因此明显的AsyncTask比Handler要重量级。

功能总结:

  • 能定义以下执行过程的操作:预执行、执行后台任务、执行进度反馈、执行完毕
  • 对于以上功能,可以提供友好方便的API供开发人员调用
  • 可以停止任务的执行

涉及的主要类

  • AsyncTask<Params, Progress, Result>:将要介绍的主角,这是一个抽象类
  • Params:泛型类,是启动任务执行的输入参数类型
  • Progress:泛型类,是后台任务完成的进度值的类型,如进度百分比
  • Result:泛型类,是后台任务完成后的返回值类型

涉及的主要方法

  • doInBackground(Object[] params):继承该类唯一必须要实现的方法,做一些耗时操作
  • onPreExecute():耗时任务执行前的一些操作,运行在UI线程
  • publishProgress ():doInBackground()方法中进行调用,当这个方法被调用,会去执行onProgressUpdate()方法
  • onProgressUpdate(Object[] values):publishProgress()被调用时执行,用来实现进度的更新,运行在UI线程
  • onPostExecute(Object o):任务结束后的操作,运行在UI线程
  • onCancelled():取消执行

如何使用

步骤

1、新建一个类MyAsyncTask(随意命名),继承AsyncTask

2、实现其中的doInBackground()方法,来进行耗时的操作,这个是必须实现的;然后,根据需要选择实现onPreExecute()onProgressUpdate()onPostExecute()方法,它们的功能在前面已有介绍。

3、最后调用execute()方法,来开启这个异步任务。

注意事项

  • 异步任务的实例必须在UI线程中创建。
  • execute(Params... params)方法必须在UI线程中调用。
  • 不能在doInBackground(Params... params)中更改UI组件的信息。
  • 一个任务实例只能执行一次,如果执行第二次将会抛出异常。

代码示例

对应上面第一、二步:

private class MyAsyncTask extends AsyncTask<String, Void, Bitmap> {
     protected void onPreExecute() {

         // Good for toggling visibility of a progress indicator
         progressBar.setVisibility(ProgressBar.VISIBLE);
     }

     protected Bitmap doInBackground(String... strings) {
         // Some long-running task like downloading an image.
         Bitmap = downloadImageFromUrl(strings[0]);
         return someBitmap;
     }

     protected void onProgressUpdate(Progress... values) {
        // Executes whenever publishProgress is called from doInBackground
        // Used to update the progress indicator
        progressBar.setProgress(values[0]);
     }  

     protected void onPostExecute(Bitmap result) {
         // This method is executed in the UIThread
         // with access to the result of the long running task
         imageView.setImageBitmap(result);
         // Hide the progress bar
         progressBar.setVisibility(ProgressBar.INVISIBLE);
     }
}

第三步:

public void onCreate(Bundle b) {

   new MyAsyncTask().execute("http://images.com/image.jpg");
   
}

源码分析

流程分析

在分析源码时,针对于这种框架层源码,我采用流程分析法,也就是从我们最先开始调用的方法开始分析,逐步探寻里面是如何调用其它对象和其它方法的。

还有一种方法是直接打开AsyncTask类,查看里面的方法、字段等,不过容易让人摸不着头脑,不容易搞清楚流程,所以这里我还是先找到一个入口,然后顺着这个入口去分析。

毫无疑问,对于AsyncTask,我们最先调用的就是它的execute()方法,代码如下:

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

只有一行代码,就是调用了executeOnExecutor()方法,我们进入这个方法:

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

        mWorker.mParams = params;
        exec.execute(mFuture);

        return this;
    }
  • mStatus初始化是PENDING状态的,所以首先设置当前AsyncTask的状态为RUNNING。从上面的switch也可以看出,每个异步任务在完成前只能执行一次,否则报错。
  • 然后执行onPreExecute(),当前依然在UI线程,所以我们可以在其中做一些准备工作。
  • 将我们传入的参数赋值给mWorker.mParams
  • 调用exec.execute(mFuture)

前两步没什么可说的,很容易理解,重点是最后两步,我们先分析mWorker.mParamsmWorker在代码中的声明如下:

private final WorkerRunnable<Params, Result> mWorker;

很明显,我们接下来要看看WorkerRunnable这个类是如何定义的:

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

它是一个抽象类,实现了Callable接口,且包含一个mParams用于保存我们传入的参数,下面看一下它的初始化过程:

mWorker = new WorkerRunnable<Params, Result>() {
            public Result call() throws Exception {
                mTaskInvoked.set(true);
                Result result = null;
                try {
                    Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
                    //noinspection unchecked
                    result = doInBackground(mParams);
                    Binder.flushPendingCommands();
                } catch (Throwable tr) {
                    mCancelled.set(true);
                    throw tr;
                } finally {
                    postResult(result);
                }
                return result;
            }
        };

mWorker在构造方法中完成了初始化,可以看到在这里new了一个实现类,实现了call方法,call方法中设置mTaskInvoked=true,且最终调用doInBackground(mParams)方法,doInBackground(mParams)由开发者来实现,然后返回Result值作为参数给postResult()方法,下面进入postResult():(注意:这段代码是初始化,但是这个call()方法并不是在这里调用的,不过我们这里先把这一段的代码逻辑给讲了,后面的讲解会和这里衔接上)

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

可以看到postResult中出现了我们熟悉的异步消息机制,AsyncTaskResult就是一个简单的携带参数的对象,最终通过postResult将结果投递给UI线程。

看到这一步,一定会想到在某处肯定存在一个Handler,且复写了其handleMessage方法等待消息的传入,以及消息的处理。所以我们进入getHandler()方法进行查找,最终找到代码如下:

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

对于mWorker.mParams = params方法就分析到这,这行代码的作用就是获得从UI现存传递来的参数,下面开始分析exec.execute(mFuture)方法:

public interface Executor {
    void execute(Runnable command);
}

进入execute(mFuture)发现这是一个接口,所以我们需要找到它的实现类:

    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()方法,它有两步:

  • 第一步: 封装任务(目的是让任务串行),然后放入队列。新建一个Runable包裹提交进来的任务,在try作用域里面先执行r.run(),必须等任务执行完后(或者会执行失败也算是‘执行完’),要保证队列中的其他任务执行,所以在finally作用域中会执行 scheduleNext()调度下一个任务。所以任务是串行执行的。最后我们会把这个新建的Runnable对象放入队列mTasks中。

  • 第二步:查看是否有可立即执行的任务mActive。 如果有执行的,则立即放入THREAD_POOL_EXECUTOR线程池调度器执行。

关于ThreadPoolExecutor这里不做介绍,下面直接进入到ThreadPoolExecutor的execute()方法,如下 :

    public void execute(Runnable command) {
        if (command == null)
            throw new NullPointerException();
        int c = ctl.get();
        if (workerCountOf(c) < corePoolSize) {
            if (addWorker(command, true))
                return;
            c = ctl.get();
        }
        if (isRunning(c) && workQueue.offer(command)) {
            int recheck = ctl.get();
            if (! isRunning(recheck) && remove(command))
                reject(command);
            else if (workerCountOf(recheck) == 0)
                addWorker(null, false);
        }
        else if (!addWorker(command, false))
            reject(command);
    }

这段代码的主要功能是将异步任务mFuture加入到将要执行的队列中,重要的函数为addWoker(),这里就不再进入讲解了。需要说明的是,这个线程池是一个静态变量;那么在同一个进程之内,所有地方使用到的AsyncTask默认构造函数构造出来的AsyncTask都使用的是同一个线程池。

如果App模块比较多并且不加控制的话,那么超过了工作队列以及线程数目的限制导致这个线程池发生阻塞,那么悲剧发生,默认的处理方式会直接抛出一个异常导致进程挂掉。假设你自己写一个异步图片加载的框架,然后用AsyncTask实现的话,当你快速滑动ListView的时候很容易发生这种异常,这也是为什么各大ImageLoader都是自己写线程池和Handlder的原因。

看到这里,我们发现接连使用了两个调度器,一个是SerialExecutor的execute()方法,一个是ThreadPoolExecutor的execute(),为什么要用两个调度器呢?

那是因为它们负责的功能不同,从我们上面对SerialExecutor的分析可以看出,SerialExecutor是为了保证任务被一个一个的取出执行,也就是说AsyncTask中任务是串行执行的,只有一个AsyncTask实例执行结束,才能去调度另一个。我们从SerialExecutor的实例是个静态变量这一点也可以看出。那THREAD_POOL_EXECUTOR线程池调度器则是根据CPU数构造线程Thread缓存池,节约资源,提高AsyncTask执行性能。

也许你不理解,为什么AsyncTask默认把它设计为串行执行的呢?

由于一个进程内所有的AsyncTask都是使用的同一个线程池执行任务;如果同时有几个AsyncTask一起并行执行的话,恰好AysncTask的使用者在doInbackgroud里面访问了相同的资源,但是自己没有处理同步问题;那么就有可能导致灾难性的后果!

由于开发者通常不会意识到需要对他们创建的所有的AsyncTask对象里面的doInbackgroud做同步处理,因此,API的设计者为了避免这种无意中访问并发资源的问题,干脆把这个API设置为默认所有串行执行的了。如果你明确知道自己需要并行处理任务,那么你需要使用executeOnExecutor(Executor exec,Params... params)这个函数来指定你用来执行任务的线程池,同时为自己的行为负责。(处理同步问题)

讲完exe.execute(mFuture),其中的参数mFuture我们还没讲,下面看看它:

private final FutureTask<Result> mFuture;

mFuture是FutureTask的一个实例,它的初始化也是在AsyncTask的构造函数中,代码如下:

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

简单来说,mFuture包装了这个mWorker对象,在这个mFuture的run函数(文中没有给出这部分代码)中又会调用mWork对象的call方法,在call方法中又调用了doInBackground()方法。因为mFuture提交给了线程池来执行,所以,使得doInBackground()执行在非UI线程。得到doInBackground()的结果后,通过postResult()传递结果给UI线程。这一段逻辑我们已经在开始讲过了,这样就可以和前面连接起来了。

而且我们可以看到,mFuture实例对象的done()方法中,如果捕捉到了CancellationException类型的异常,则发送一条“MESSAGE_POST_CANCEL”的消息;如果顺利执行,则发送一条“MESSAGE_POST_RESULT”的消息,而消息都与一个sHandler对象关联。

设计模式

  • 模版方法模式

AsyncTask中用到的一个很明显的设计模式就是模版方法模式,一个execute()方法中封装了onPreExecute()doInBacground()onPostExecute()这几个流程,用户可以根据自己的需求再复写这几个方法。这个模式理解起来很简单,就不再做太多介绍。

疑惑

  • 既然AsyncTask的任务是串行执行的,也就是一个任务一个任务的执行,那使用线程池的意义又是什么呢?希望对这个有理解的同行可以在我的简书下留言,确实是不解。

参考:

相关文章

  • Android源码解析-Asynctask

    android源码分析-AsyncTask 我们一般创建一个AsyncTask的任务代码如下: 下面开始分析源码:...

  • AsyncTask异步任务类

    目录介绍 01.先看下AsyncTask用法 02.AsyncTask源码深入分析2.1 构造方法源码分析2.2 ...

  • AsyncTask源码分析

    前言 IntentService使用及源码分析 HandlerThread源码分析 AsyncTask使用及封装实...

  • AsyncTask 使用及封装实践

    前言 IntentService使用及源码分析 HandlerThread源码分析 AsyncTask使用及封装实...

  • Android - AsyncTask (SDK 24)

    利用AsyncTask给ListView异步加载图片 实现图文混排 链接 AsyncTask源码分析 知乎

  • 4.AsyncTask使用,原理

    资料 AsyncTask处理机制详解及源码分析-工匠若水 AsyncTask 源码解析-鸿洋 带你认识不一样的As...

  • AsyncTask带你更深入点看源码

    关键词 AsyncTask源码分析 FutureTask源码分析 快过年了,公司同事已经开始进行迁移之旅,目前来...

  • AsyncTask工作原理

    AsyncTask工作原理调用图 AsyncTask工作原理源码分析,建议配合调用图一起看 调用 AsyncTas...

  • AsyncTask源码分析

    一 AsyncTask使用的场景 在MainThread线程中执行耗时不长的最多几秒钟的后台工作。 二 Async...

  • AsyncTask源码分析

    Android中UI的更新主要在主线程中,而耗时操作一般在子线程中进行;我们可以通过handler发送消息的方式处...

网友评论

    本文标题: AsyncTask源码分析

    本文链接:https://www.haomeiwen.com/subject/dfsvkxtx.html