Android多线程:AsyncTask的原理及其源码分析

前言

  • AsyncTaskAndroid开发中是十分常见的
  • 今天,我将全面讲解AsyncTask的源码,希望你们会喜欢

Carson带你学多线程系列
基础汇总
Android多线程:基础知识汇总
基础使用
Android多线程:继承Thread类使用(含实例教程)
Android多线程:实现Runnable接口使用(含实例教程)
复合使用
Android 多线程:AsyncTask使用教程(含实例讲解)
Android 多线程:AsyncTask原理及源码分析
Android多线程:HandlerThread使用教程(含实例讲解)
Android多线程:HandlerThread原理及源码分析
Android多线程:IntentService使用教程(含实例讲解)
Android多线程:IntentService的原理及源码分析
Android多线程:线程池ThreadPool全方位教学
相关使用
Android异步通信:这是一份全面&详细的Handler机制学习攻略
Android多线程:手把手教你全面学习神秘的Synchronized关键字
Android多线程:带你了解神秘的线程变量 ThreadLocal


目录

示意图

1. 简介

示意图

更详细了解,请看文章:Android 多线程:AsyncTask最详细使用教程

本文主要讲解其工作原理 & 源码分析


2. 工作原理

2.1 储备知识:线程

  • 简介
示意图
  • 与进程的区别
示意图

2.2 具体原理介绍

  • AsyncTask的实现原理 = 线程池 + Handler

其中:线程池用于线程调度、复用 & 执行任务;Handler 用于异步通信

  • 其内部封装了2个线程池 + 1个Handler,具体介绍如下:
示意图

3. 类 & 方法介绍

在进行源码分析前,先介绍AsyncTask中的类 & 核心方法

3.1 类定义

AsyncTask类属于抽象类,即使用时需 实现子类

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

// 类中参数为3种泛型类型
// 整体作用:控制AsyncTask子类执行线程任务时各个阶段的返回类型
// 具体说明:
    // a. Params:开始异步任务执行时传入的参数类型,对应excute()中传递的参数
    // b. Progress:异步任务执行过程中,返回下载进度值的类型
    // c. Result:异步任务执行完成后,返回的结果类型,与doInBackground()的返回值类型保持一致
// 注:
    // a. 使用时并不是所有类型都被使用
    // b. 若无被使用,可用java.lang.Void类型代替
    // c. 若有不同业务,需额外再写1个AsyncTask的子类
}

3.2 核心方法

  • AsyncTask 核心 & 常用的方法如下:
示意图
  • 方法执行顺序如下
示意图

4. 源码分析

  • 本次源码分析将根据 AsyncTask的使用步骤讲解

若不熟悉,请务必看文章:Android 多线程:AsyncTask最详细使用教程

  • AsyncTask的使用步骤有3个:
  1. 创建 AsyncTask 子类 & 根据需求实现核心方法
  2. 创建 AsyncTask子类的实例对象(即 任务实例)
  3. 手动调用execute(()从而执行异步线程任务
  • 具体介绍如下
/**
  * 步骤1:创建AsyncTask子类
  * 注: 
  *   a. 继承AsyncTask类
  *   b. 为3个泛型参数指定类型;若不使用,可用java.lang.Void类型代替
  *   c. 根据需求,在AsyncTask子类内实现核心方法
  */

  private class MyTask extends AsyncTask<Params, Progress, Result> {

        ....

      // 方法1:onPreExecute()
      // 作用:执行 线程任务前的操作
      // 注:根据需求复写
      @Override
      protected void onPreExecute() {
           ...
        }

      // 方法2:doInBackground()
      // 作用:接收输入参数、执行任务中的耗时操作、返回 线程任务执行的结果
      // 注:必须复写,从而自定义线程任务
      @Override
      protected String doInBackground(String... params) {

            ...// 自定义的线程任务

            // 可调用publishProgress()显示进度, 之后将执行onProgressUpdate()
             publishProgress(count);
              
         }

      // 方法3:onProgressUpdate()
      // 作用:在主线程 显示线程任务执行的进度
      // 注:根据需求复写
      @Override
      protected void onProgressUpdate(Integer... progresses) {
            ...

        }

      // 方法4:onPostExecute()
      // 作用:接收线程任务执行结果、将执行结果显示到UI组件
      // 注:必须复写,从而自定义UI操作
      @Override
      protected void onPostExecute(String result) {

         ...// UI操作

        }

      // 方法5:onCancelled()
      // 作用:将异步任务设置为:取消状态
      @Override
        protected void onCancelled() {
        ...
        }
  }

/**
  * 步骤2:创建AsyncTask子类的实例对象(即 任务实例)
  * 注:AsyncTask子类的实例必须在UI线程中创建
  */
  MyTask mTask = new MyTask();

/**
  * 步骤3:手动调用execute(Params... params) 从而执行异步线程任务
  * 注:
  *    a. 必须在UI线程中调用
  *    b. 同一个AsyncTask实例对象只能执行1次,若执行第2次将会抛出异常
  *    c. 执行任务中,系统会自动调用AsyncTask的一系列方法:onPreExecute() 、doInBackground()、onProgressUpdate() 、onPostExecute() 
  *    d. 不能手动调用上述方法
  */
  mTask.execute();

  • 下面,我将根据上述使用步骤进行源码分析

步骤1:创建AsyncTask子类

在该步骤中,只需知道 “该类中复写的方法将在后续源码中调用” 即可

步骤2:创建AsyncTask子类的实例对象(即 任务实例)

/**
  * 具体使用
  */
  MyTask mTask = new MyTask();

/**
  * 源码分析:AsyncTask的构造函数
  */
  public AsyncTask() {
        // 1. 初始化WorkerRunnable变量 = 一个可存储参数的Callable对象 ->>分析1
        mWorker = new WorkerRunnable<Params, Result>() {
            // 在任务执行线程池中回调:THREAD_POOL_EXECUTOR.execute()
            // 下面会详细讲解
            public Result call() throws Exception {

                // 添加线程的调用标识
                mTaskInvoked.set(true); 

                Result result = null;
                try {
                    // 设置线程的优先级
                    Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
                    
                    // 执行异步操作 = 耗时操作
                    // 即 我们使用过程中复写的耗时任务
                    result = doInBackground(mParams);

                    Binder.flushPendingCommands();
                } catch (Throwable tr) {
                    
                    mCancelled.set(true);// 若运行异常,设置取消的标志
                    throw tr;
                } finally {
                    
                    // 把异步操作执行的结果发送到主线程
                    // 从而更新UI,下面会详细讲解
                    postResult(result); 
                }
                return result;
            }
        };

        // 2. 初始化FutureTask变量 = 1个FutureTask ->>分析2
        mFuture = new FutureTask<Result>(mWorker) {

            // done()简介:FutureTask内的Callable执行完后的调用方法
            // 作用:复查任务的调用、将未被调用的任务的结果通过InternalHandler传递到UI线程
            @Override
            protected void done() {
                try {

                    // 在执行完任务后检查,将没被调用的Result也一并发出 ->>分析3
                    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) {

                    //若 发生异常,则将发出null
                    postResultIfNotInvoked(null);
                }
            }
        };
    }

/**
  * 分析1:WorkerRunnable类的构造函数
  */
  private static abstract class WorkerRunnable<Params, Result> implements Callable<Result> {
        // 此处的Callable也是任务;
        // 与Runnable的区别:Callable<T>存在返回值 = 其泛型
        Params[] mParams;
    }

/**
  * 分析2:FutureTask类的构造函数
  * 定义:1个包装任务的包装类
  * 注:内部包含Callable<T> 、增加了一些状态标识 & 操作Callable<T>的接口
  */
  public FutureTask(Callable<V> callable) {
        if (callable == null)
            throw new NullPointerException();
        this.callable = callable;
        this.state = NEW;      
    }
    // 回到调用原处

/**
  * 分析3:postResultIfNotInvoked()
  */
  private void postResultIfNotInvoked()(Result result) {
        // 取得任务标记
        final boolean wasTaskInvoked = mTaskInvoked.get();

        // 若任务无被执行,将未被调用的任务的结果通过InternalHandler传递到UI线程
        if (!wasTaskInvoked) {
            postResult(result);
        }
    }

总结:

  • 创建了1个WorkerRunnable类 的实例对象 & 复写了call()方法
  • 创建了1个FutureTask类 的实例对象 & 复写了 done()

步骤3:手动调用execute(Params... params)

/**
  * 具体使用
  */
  mTask.execute();

/**
  * 源码分析:AsyncTask的execute()
  */
  public final AsyncTask<Params, Progress, Result> execute(Params... params) {

        return executeOnExecutor(sDefaultExecutor, params);
        // ->>分析1

    }

 /**
  * 分析1:executeOnExecutor(sDefaultExecutor, params)
  * 参数说明:sDefaultExecutor = 任务队列 线程池类(SerialExecutor)的对象
  */
  public final AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec,Params... params) {

        // 1. 判断 AsyncTask 当前的执行状态
        // PENDING = 初始化状态
        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)");
            }
        }

        // 2. 将AsyncTask状态设置为RUNNING状态
        mStatus = Status.RUNNING;

        // 3. 主线程初始化工作
        onPreExecute();

        // 4. 添加参数到任务中
        mWorker.mParams = params;

        // 5. 执行任务
        // 此处的exec = sDefaultExecutor = 任务队列 线程池类(SerialExecutor)的对象
        // ->>分析2
        exec.execute(mFuture);
        return this;
    }

/**
  * 分析2:exec.execute(mFuture)
  * 说明:属于任务队列 线程池类(SerialExecutor)的方法
  */
  private static class SerialExecutor implements Executor {
        // SerialExecutor = 静态内部类
        // 即 是所有实例化的AsyncTask对象公有的

        // SerialExecutor 内部维持了1个双向队列;
        // 容量根据元素数量调节
        final ArrayDeque<Runnable> mTasks = new ArrayDeque<Runnable>();
        Runnable mActive;

        // execute()被同步锁synchronized修饰
        // 即说明:通过锁使得该队列保证AsyncTask中的任务是串行执行的
        // 即 多个任务需1个个加到该队列中;然后 执行完队列头部的再执行下一个,以此类推
        public synchronized void execute(final Runnable r) {
            // 将实例化后的FutureTask类 的实例对象传入
            // 即相当于:向队列中加入一个新的任务
            mTasks.offer(new Runnable() {
                public void run() {
                    try {
                        r.run();
                    } finally {
                        scheduleNext();->>分析3
                    }
                }
            });
            // 若当前无任务执行,则去队列中取出1个执行
            if (mActive == null) {
                scheduleNext();
            }
        }
        // 分析3
        protected synchronized void scheduleNext() {
            // 1. 取出队列头部任务
            if ((mActive = mTasks.poll()) != null) {

                // 2. 执行取出的队列头部任务
                // 即 调用执行任务线程池类(THREAD_POOL_EXECUTOR)->>继续往下看
                THREAD_POOL_EXECUTOR.execute(mActive);
                
            }
        }
    }

总结:

  • 执行任务前,通过 任务队列 线程池类(SerialExecutor)将任务按顺序放入到队列中;

通过同步锁 修饰execute()从而保证AsyncTask中的任务是串行执行的

  • 之后的线程任务执行是 通过任务线程池类(THREAD_POOL_EXECUTOR) 进行的。

继续往下分析:THREAD_POOL_EXECUTOR.execute()

/**
  * 源码分析:THREAD_POOL_EXECUTOR.execute()
  * 说明:
  *     a. THREAD_POOL_EXECUTOR实际上是1个已配置好的可执行并行任务的线程池
  *     b. 调用THREAD_POOL_EXECUTOR.execute()实际上是调用线程池的execute()去执行具体耗时任务
  *     c. 而该耗时任务则是步骤2中初始化WorkerRunnable实例对象时复写的call()
  * 注:下面先看任务执行线程池的线程配置过程,看完后请回到步骤2中的源码分析call()
  */

    // 步骤1:参数设置
        //获得当前CPU的核心数
        private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
        //设置线程池的核心线程数2-4之间,但是取决于CPU核数
        private static final int CORE_POOL_SIZE = Math.max(2, Math.min(CPU_COUNT - 1, 4));
        //设置线程池的最大线程数为 CPU核数*2+1
        private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;
        //设置线程池空闲线程存活时间30s
        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());
            }
        };

        //初始化存储任务的队列为LinkedBlockingQueue 最大容量为128
        private static final BlockingQueue<Runnable> sPoolWorkQueue =
                new LinkedBlockingQueue<Runnable>(128);

    // 步骤2: 根据参数配置执行任务线程池,即 THREAD_POOL_EXECUTOR
    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);
        // 设置核心线程池的 超时时间也为30s
        threadPoolExecutor.allowCoreThreadTimeOut(true);
        THREAD_POOL_EXECUTOR = threadPoolExecutor;
    }

    // 请回到步骤2中的源码分析call()

至此,我们回到步骤2中的源码分析call()

/**
  * 步骤2的源码分析:AsyncTask的构造函数
  */
    public AsyncTask() {
        // 1. 初始化WorkerRunnable变量 = 一个可存储参数的Callable对象
        mWorker = new WorkerRunnable<Params, Result>() {

            public Result call() throws Exception {

                // 添加线程的调用标识
                mTaskInvoked.set(true); 

                Result result = null;
                try {
                    // 设置线程的优先级
                    Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
                    
                    // 执行异步操作 = 耗时操作
                    // 即 我们使用过程中复写的耗时任务
                    result = doInBackground(mParams);

                    Binder.flushPendingCommands();
                } catch (Throwable tr) {
                    
                    mCancelled.set(true);// 若运行异常,设置取消的标志
                    throw tr;
                } finally {
                    
                    // 把异步操作执行的结果发送到主线程
                    // 从而更新UI ->>分析1
                    postResult(result); 
                }
                return result;
            }
        };

        .....// 省略
    }
/**
  * 分析1:postResult(result)
  */
   private Result postResult(Result result) {

        @SuppressWarnings("unchecked")

        // 创建Handler对象 ->> 源自InternalHandler类—>>分析2
        Message message = getHandler().obtainMessage(MESSAGE_POST_RESULT,
                new AsyncTaskResult<Result>(this, result));
        // 发送消息到Handler中
        message.sendToTarget();
        return result;

    }


/**
  * 分析2:InternalHandler类
  */
    private static class InternalHandler extends Handler {

        // 构造函数
        public InternalHandler() {
            super(Looper.getMainLooper());
            // 获取的是主线程的Looper()
            // 故 AsyncTask的实例创建 & execute()必须在主线程使用
        }

        @Override
        public void handleMessage(Message msg) {

            AsyncTaskResult<?> result = (AsyncTaskResult<?>) msg.obj;

            switch (msg.what) {
                // 若收到的消息 = MESSAGE_POST_RESULT
                // 则通过finish() 将结果通过Handler传递到主线程
                case MESSAGE_POST_RESULT:
                    result.mTask.finish(result.mData[0]); ->>分析3
                    break;

                // 若收到的消息 = MESSAGE_POST_PROGRESS
                // 则回调onProgressUpdate()通知主线程更新进度的操作
                case MESSAGE_POST_PROGRESS:
                    result.mTask.onProgressUpdate(result.mData);
                    break;
            }
        }
    }
/**
  * 分析3:result.mTask.finish(result.mData[0])
  */
  private void finish(Result result) {
        // 先判断是否调用了Cancelled()
            // 1. 若调用了则执行我们复写的onCancelled()
            // 即 取消任务时的操作
            if (isCancelled()) {
                onCancelled(result);
            } else {

            // 2. 若无调用Cancelled(),则执行我们复写的onPostExecute(result)
            // 即更新UI操作
                onPostExecute(result);
            }
            // 注:不管AsyncTask是否被取消,都会将AsyncTask的状态变更为:FINISHED
            mStatus = Status.FINISHED;
        }

总结

  • 任务线程池类(THREAD_POOL_EXECUTOR)实际上是1个已配置好的可执行并行任务的线程池
  • 调用THREAD_POOL_EXECUTOR.execute()实际上是调用线程池的execute()去执行具体耗时任务
  • 而该耗时任务则是步骤2中初始化 WorkerRunnable实例对象时复写的call()内容
  • call()方法里,先调用 我们复写的doInBackground(mParams)执行耗时操作
  • 再调用postResult(result), 通过 InternalHandler 类 将任务消息传递到主线程;根据消息标识(MESSAGE_POST_RESULT)判断,最终通过finish()调用我们复写的onPostExecute(result),从而实现UI更新操作

至此,关于AsyncTask的源码 分析完毕,附上一份最终总结:

示意图

9. 总结

  • 本文介绍了多线程中的AsyncTask的 工作原理 & 源码分析,总结如下:
示意图
示意图
  • 下一篇文章我将对讲解Android多线程的相关知识,感兴趣的同学可以继续关注Carson_Ho的简书

Carson带你学多线程系列
基础汇总
Android多线程:基础知识汇总
基础使用
Android多线程:继承Thread类使用(含实例教程)
Android多线程:实现Runnable接口使用(含实例教程)
复合使用
Android 多线程:AsyncTask使用教程(含实例讲解)
Android 多线程:AsyncTask原理及源码分析
Android多线程:HandlerThread使用教程(含实例讲解)
Android多线程:HandlerThread原理及源码分析
Android多线程:IntentService使用教程(含实例讲解)
Android多线程:IntentService的原理及源码分析
Android多线程:线程池ThreadPool全方位教学
相关使用
Android异步通信:这是一份全面&详细的Handler机制学习攻略
Android多线程:手把手教你全面学习神秘的Synchronized关键字
Android多线程:带你了解神秘的线程变量 ThreadLocal


欢迎关注Carson_Ho的简书

不定期分享关于安卓开发的干货,追求短、平、快,但却不缺深度


请点赞!因为你的鼓励是我写作的最大动力!

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

推荐阅读更多精彩内容