欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

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

程序员文章站 2024-03-02 14:41:52
...

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
Android进阶2:线程和线程池(1)—— AsycTask原理解析

相关标签: AsyncTask源码