Android进阶2:线程和线程池(1)—— AsycTask原理解析

Android不允许UI主线程做耗时操作,不允许子线程刷新UI,声明UI控件单线程模式,至于为什么看上篇文章:
这些问题都催生了链接子线程和主线程Handler的诞生,然而Google也为我们提供了一个老牌的Thread + handler的神器:AsyncTask。

AsyncTask用法:

先来看下AsyncTask 的用法:
1. 衍生类的三个泛型限制:Params :doInBackground方法内的参数;Progress:异步过程中,onProgressUpdate更新UI的参数; Result: doInBackground的返回值;
2. onPreExecute:UI线程启动任务之前会回调此方法。
3. doInBackground:再次方法内,可以进行耗时操作,此方法内不能更新UI
4. onProgressUpdate:任务执行过程中,可以在doInBackground方法内调用publishProgress方法获取任务执行的进度,此时会回调此方法; 此方法内能更新UI
5. onPostExecute:当任务执行完毕之后,回调此方法,更新UI;

那么上述几个方法什么时候调用呢?
看源码,老规矩,还是从AsyncTask属性变量入手。

AsyncTask属性变量

对于看源码之前成员变量的分析,还是很有必要的,至少看完之后,我们能知道,该源码的时候都用到了哪些知识点。

    //THREAD_POOL_EXECUTOR线程池的一些设置:
    private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
    private static final int CORE_POOL_SIZE = Math.max(2, Math.min(CPU_COUNT - 1, 4));
    private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;
    private static final int KEEP_ALIVE_SECONDS = 30;
    private static final ThreadFactory sThreadFactory = new ThreadFactory() {

        private final AtomicInteger mCount = new AtomicInteger(1);

        public Thread newThread(Runnable r) {
            return new Thread(r, "AsyncTask #" + mCount.getAndIncrement());
        }
    };

    private static final BlockingQueue<Runnable> sPoolWorkQueue =
            new LinkedBlockingQueue<Runnable>(128);
    public static final Executor THREAD_POOL_EXECUTOR; //并发执行的线程池
    static {
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
                CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE_SECONDS, TimeUnit.SECONDS,
                sPoolWorkQueue, sThreadFactory);
        threadPoolExecutor.allowCoreThreadTimeOut(true);
        THREAD_POOL_EXECUTOR = threadPoolExecutor;
    }
    //handler发送消息的what标记
    private static final int MESSAGE_POST_RESULT = 0x1; //执行结果
    private static final int MESSAGE_POST_PROGRESS = 0x2; //执行过程

    //单任务线程池,保证一次一个任务进行
    public static final Executor SERIAL_EXECUTOR = new SerialExecutor(); //一次一个的执行任务(先进先出,保证顺序执行)
    private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;
    private static InternalHandler sHandler; //创建的handler(如果Looper是null,就赋值给mHandler)
    private final WorkerRunnable<Params, Result> mWorker; //创建任务
    private final FutureTask<Result> mFuture; //监控任务执行的进度
    private volatile Status mStatus = Status.PENDING; // 默认任务标记是:准备状态
    private final AtomicBoolean mCancelled = new AtomicBoolean(); //任务是否取消
    private final AtomicBoolean mTaskInvoked = new AtomicBoolean(); //任务调度(futureTask方法是否发送消息)
    private final Handler mHandler; //全局发送消息并处理消息的handler

    //三种枚举类型
    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, //执行完毕
    }

从上述成员变量,得出的信息是:

  • 使用了线程池,还是两个线程池。并且这两个线程池是整个应用下有且只有这一份,无论创建多少个AsyncTask对象
  • 嗅到了handler的气息
  • 声明了异步任务

成员变量看完了,是不是该构造方法了?

通过成员变量需要思考的问题:

  1. 线程池,还两个,这是干什么用的?
  2. 枚举类型是干什么用的?
  3. 定义的两个handler标记,发送消息?发送的都是什么消息?

AsyncTask的构造方法

般使用的时候都是直接调用的空的构造方法,源码示下:

    /**
     * Creates a new asynchronous task. This constructor must be invoked on the UI thread.
     */
    //翻译:创建一个新的异步任务,必须在UI现呈上调用此构造函数
    public AsyncTask() {
        this((Looper) null); //空的looper对象
    }

从上述源码可以看出,内部调用了另一个构造方法,而且参数是Looper,只不过此时的looper的null。但是是不是已经嗅到了一股handler的气息。 另外需要注意:创建一个新的异步任务,必须在UI现呈上调用此构造函数,这是官方说的!!!

   public AsyncTask(@Nullable Looper callbackLooper) {
        // looper是否是null 或者 是否是Ui主线程的Looper ?
        //创建UI线程下的handler对象
        mHandler = callbackLooper == null || callbackLooper == Looper.getMainLooper() ? getMainHandler() : new Handler(callbackLooper);

        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; //耗时操作结束之后返回的结果
            }
        };

        mFuture = new FutureTask<Result>(mWorker) {
            @Override
            protected void done() { //异步任务完成或者中途退出,都会执行此方法
                try {
                    //FutureTask的get()方法是阻塞的,在结果计算完成之后,调用此方法,才会有数据
                    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);
                }
            }
        };
    }

从源码可以看书,总共做了两件事:

  • 创建处理消息的handler
  • 创建worker和future(这个是创建线程的三种方式之一)

简单提一下worker和future的理解,workerRunnable是对任务做了封装,做干活的;而futureTask是对任务的执行状态做了封装,就是任务的执行状态。

先看下mWorker的call方法,既然call是干活的,那么就是运行在子线程了。内部首先将mTaskInvoked置为的true,而mTaskInvoked的类型是AtomicBoolean,多个线程中,也就是告诉别人我以在处理了,你不能处理了。 然后看到了设置doInBackground的回调,是不是明了了,首先我们知道了doInBackground是在异步线程中调用的,所以我们复写的此方法才能进行耗时操作,但是不是更新UI。当doInBackground执行完毕之后,

最后看到了一个finally,里面是postResult方法。

    private Result postResult(Result result) {
        //通过构造方法中创建的handler创建消息
        @SuppressWarnings("unchecked")
        Message message = getHandler().obtainMessage(MESSAGE_POST_RESULT, new AsyncTaskResult<Result>(this, result)); 
        message.sendToTarget(); //发送消息
        return result;
    }

通过上述源码,了解的信息是:通过我们创建的mHandler发送了一条消息,至于消息是什么?标记是:MESSAGE_POST_RESULT,在哪里接收? 往下看:handleMessage方法

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

        @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: //异步过程中回调给UI层更新view
                    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; //将标记更改。
    }

如果流程没取消,就进入到了onPostExecute方法,等等,onPostExecute方法? 这不是就是我们获取结果,更新Ui的方法嘛。还有之前抛出的问题三:MESSAGE_POST_RESULT标记就是为了handler的处理区别消息类型,作用是发送执行结果给UI层

通过AsyncTask的构建,我们知道了,当任务执行起来之后,在子线程先执行doInBackfruond方法,最后如论结果如何,都通过之前创建的handler,发送消息,切换到主线程执行onPostExecute方法。

构造方法好了,是不是该执行任务了?

启动AsyncTask

在创建完AsyncTask之后,需要调用 execute方法开始执行任务,看下execute的源码:

    /**
     * This method must be invoked on the UI thread.
     * 翻译:此方法必须运行在UI主线程上。
     */
    @MainThread
    public final AsyncTask<Params, Progress, Result> execute(Params... params) {
        return executeOnExecutor(sDefaultExecutor, params);
    }
    /**
     * @param exec 线程池
     * @param params 输入的参数
     * @return
     */
    @MainThread
    public final AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec,
            Params... params) {
        if (mStatus != Status.PENDING) { //解释了一个AsyncTask实例只能执行一次的原因
            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); //开始运行(Future加入到线程池中)

        return this;
    }

从上述代码可以看出:

  • 此方法必须运行在主线程中(MainThread 和 方法描述都能看出)
  • 此方法内使用了线程池,该线程池是常量SERIAL_EXECUTOR线程池,实际上执行的是线程池execute方法。
  • 线程池执行方法,参数是AsyncTask构造方法中的FutureTask对象,那么是不是这个线程池执行了FutureTask任务呢?
  • 方法内做了判断,如果此任务已经在执行或者执行完毕了,但是又一次执行,会抛出异常。所以结论是:如果需要执行多个任务就需要创建相对应数量的AsyncTask对象来执行,不能重复执行任务。 例如:一个AsyncTask对象,只能执行一次,多次执行报错。
  • onPreExecute方法,看到了吗? 也就是说onPreExecute回调方法,会在execute执行任务之前就调用的。

通过上述结论,那么我们的之前抛出的问题2:枚举类型是干什么用的?就有了答案了。

继续分析,既然实际上执行的是线程池的execute方法,那么就有必要先看下 SERIAL_EXECUTOR的创建:

   //单任务线程池
    private static class SerialExecutor implements Executor {
        //双向链表(实际上存储的是FutureTask对象)
        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) { //是否有任务正在执行 (第一次进来mActive肯定是null)
                scheduleNext();
            }
        }

        protected synchronized void scheduleNext() {
            //由于THREAD_POOL_EXECUTOR是可以并发执行的线程池(最多同时运行5个),
            //所以将if改为while,即可并发执行任务
            if ((mActive = mTasks.poll()) != null) {
                THREAD_POOL_EXECUTOR.execute(mActive);
            }
        }
    }

SerialExecutor有两个变量:ArrayDeque 和 Runnable;
ArrayDeque :循环队列,就是可以无休止的存储数据,泛型是Runnable接口对象。Runnable对象表示正在执行的任务对象,不是FutureTask对象。为什么不是FutureTask对象呢?往下看分析。
execute方法内部代码逻辑:在循环队列尾部把新建的Runnable对象添加了进去。接下来是判断Runnable的mActive对象是否是null,如果是null,表明第一次进入此方法,直接执行sheduleNext();方法。

至于sheduleNext方法内部逻辑还是很简单的,首先队列出队赋值给mActive,判断是否是null,如果不是null,表明有任务可以执行,此时就调用THREAD_POOL_EXECUTOR.execute(mActive);,这? THREAD_POOL_EXECUTOR? 这是个异步线程池,原来SERIAL_EXECUTOR线程池并不执行任务,而是把任务添加到队列中,执行任务的事情交给了THREAD_POOL_EXECUTOR异步线程池。那么问题来了:
THREAD_POOL_EXECUTOR是异步线程池,能够同时执行几个线程,这里最多能并发执行多个线程吗?

再看sheduleNext()方法:

            if ((mActive = mTasks.poll()) != null) {
                THREAD_POOL_EXECUTOR.execute(mActive);
            }

可以看到这是个if,这是顺序执行的,就是execute一次只能执行一个任务,答案就是:一次只能执行一个任务,不能并发执行。但是if之后就没有了啊,队列未执行的任务就得不到执行了啊?这不行啊!别急,现在再看下任务入队的代码:

            mTasks.offer(new Runnable() { //入队操作(末尾插入)
                public void run() {
                    try {
                        r.run(); //执行任务
                    } finally { //执行完上一个任务之后,一定会执行下一个如果有的话
                        scheduleNext();
                    }
                }
            });

可以看到 r.run()任务执行之后,看到finally 了吗???无论如何都会再一次调用scheduleNext()方法,也就上一个任务执行完了,紧接着调用scheduleNext()方法,这样队列中的任务都会得到执行。

等等,这个r.run方法执行了,但是mWork内部的call方法什么时候执行呢? 看下FutureTask的源码:

    public void run() {
        if (state != NEW ||
            !U.compareAndSwapObject(this, RUNNER, null, Thread.currentThread()))
            return;
        try {
            Callable<V> c = callable;
            if (c != null && state == NEW) {
                V result;
                boolean ran;
                try {
                    result = c.call(); //此处回调workRunnable的call方法
                  ......
    }

通过上面的源码分析,初步了解了AsyncTask的运行流程,知道了onPreExecute, doInBackground, onPostExecute方法调用的时机,不对,还少一个onProgressUpdate(),= - = .先看下onProgressUpdate的源码:

    /**
     * Runs on the UI thread after {@link #publishProgress} is invoked.
     * The specified values are the values passed to {@link #publishProgress}.
     * 
     * 翻译:通过publishProgress调度之后,此方法运行在UI主线程,参数值通过publishProgress传递
     * @param values The values indicating progress.
     *
     * @see #publishProgress
     * @see #doInBackground
     */
    @SuppressWarnings({"UnusedDeclaration"})
    @MainThread
    protected void onProgressUpdate(Progress... values) {
    }

通过上述方法的描述翻译,可以确定两点:

  • onProgressUpdate方法是运行在UI主线程中的
  • 该方法的回调是通过publishProgress调度出发的。

所以我们有必要看下publishProgress的源码:

    @WorkerThread
    protected final void publishProgress(Progress... values) { //在doInBackground中调用
        if (!isCancelled()) {
            //给主线程发送消息
            getHandler().obtainMessage(MESSAGE_POST_PROGRESS,
                    new AsyncTaskResult<Progress>(this, values)).sendToTarget();
        }
    }

首先方法注释WorkerThread限定了 此方法运行在子线程中的,子线程?是不是串联起来了呢? publishProgress可以在doInBackground方法中手动调用,然后通过handler发送消息(MESSAGE_POST_PROGRESS),再看下handleMessage方法:

        @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: //异步过程中回调给UI层更新view
                    result.mTask.onProgressUpdate(result.mData);
                    break;
            }
        }

MESSAGE_POST_PROGRESS内部调用了onProgressUpdate方法,闭环了吧。
doInBackground方法内 —> publishProgress方法,发送消息 —–> onProgressUpdate方法 —>开发者自己处理逻辑

至此,AsyncTask的基本原理就分析完了,再来总结一下开头抛出的三个问题:
THREAD_POOL_EXECUTOR:负责执行任务;
SERIAL_EXECUTOR:将任务都添加到双向队列中,然后一个一个取出来,保证单任务执行
1. 线程池,还两个,这是干什么用的?
THREAD_POOL_EXECUTOR:负责执行任务;SERIAL_EXECUTOR:将任务都添加到双向队列中,然后一个一个取出来,保证单任务执行
2. 枚举类型是干什么用的?
枚举是用来标记AsyncTask对象的状态的。一个AsyncTask对象只能执行一次
3. 定义的两个handler标记,发送消息?发送的都是什么消息?
MESSAGE_POST_RESULT:回调任务执行完毕的结果;MESSAGE_POST_PROGRESS回调执行过程中的进度

本位内容感谢《Android开发艺术探索》

本文的内容希望能帮助到你,文中如果有错,欢迎之处,以便纠正,谢谢~

android开发交流群:543671130
这里写图片描述

猜你喜欢

转载自blog.csdn.net/lmq121210/article/details/81326243
今日推荐