android 多线程 — AsyncTask 源码简单分析

AsyncTask 的源码我们要是只看原理不难的,我就不跟着大众的思路去看了,先来看看 AsyncTask 类中都有哪些关键变量,这里只放重要的。

我研究的代码是 API 26 的

AsyncTask 声明的核心变量

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

    private static final String LOG_TAG = "AsyncTask";

    // 线程池设置参数
    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);

    // AsyncTask 里面的2个核心线程池对象
    public static final Executor THREAD_POOL_EXECUTOR;
    public static final Executor SERIAL_EXECUTOR = new SerialExecutor();
    private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;

    // 2个用于切换线程,发送数据的 handle
    private static InternalHandler sHandler;
    private final Handler mHandler;

    // 静态代码段
    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;
    }

再次说一下啊,不难看的,我一定让大家都看得懂,大家都懂了,我也就懂啦

AsyncTask 上来声明的全局变量不少,但是类型不多,看着我上面的注解都能明白,期中:

  • 先是一段线程池的参数设置,比如 CORE_POOL_SIZE 核心线程数,MAXIMUM_POOL_SIZE 最大线程数,KEEP_ALIVE_SECONDS 空闲线程存活时间,ThreadFactory 线程创建工厂,BlockingQueue 线程池任务队列,这几个不了解的去看看 java 的多线程,随便找个资料有个半小时足够了解了
  • 然后是 2个线程池对象 sDefaultExecutor 和 THREAD_POOL_EXECUTOR,至于为啥有2个线程池,现在只能说是历史原因,AsyncTask 历次改版多次,得考虑版本兼容把,所以不能删,后面会说清的
  • 再后面是2个 handle 对象,大家也会问为啥有2个了把,这个也是后面说
  • 静态代码块中,初始化了 THREAD_POOL_EXECUTOR 这个线程池

AsyncTask 的声明我们看完了,再来看看构造方法干啥了

    public AsyncTask() {
        this((Looper) null);
    }

    public AsyncTask(@Nullable Handler handler) {
        this(handler != null ? handler.getLooper() : null);
    }

    public AsyncTask(@Nullable Looper callbackLooper) {
        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 {
                    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);
                }
            }
        };
    }

  • mWorker 和 mFuture 不用管,是包装 runnbale 任务的,不影响我们理解执行过程
  • 有3个构造函数,我们平时 new AsyncTask 对象时大家传参数了嘛,都没有吧,所以大家说会执行哪个构造方法呢?
  • 那肯定是这个 AsyncTask(@Nullable Looper callbackLooper) 啦,而且参数传的是 null 值,然后里面会发生什么事呢
  • mHandler 就会= getMainHandler() 返回的参数了

getMainHandler 方法

    private static Handler getMainHandler() {
        synchronized (AsyncTask.class) {
            if (sHandler == null) {
                sHandler = new InternalHandler(Looper.getMainLooper());
            }
            return sHandler;
        }
    }

InternalHandler 类

    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:
                    result.mTask.onProgressUpdate(result.mData);
                    break;
            }
        }
    }

这 2 段代码很明确的告诉我们 mHandler = getMainHandler() 干啥了:

  • 会用 UI 线程的 looper 把 sHandler 这个对象 new 出来
  • 然后把 sHandler 的值赋 给 mHandler
  • 很显然这么写的话 mHandler 就是妥妥的打酱油啦,sHandler 才是主角啊,这点很重要啊,套路在此啊,膜拜套路大神啊........

好了我们知道了 AsyncTask 都有啥我们很重要的部件了,然后我们看看 AsyncTask 是怎么运行的,这当前得出入口的那个执行方法来啦

execute 方法

    public static void execute(Runnable runnable) {
        sDefaultExecutor.execute(runnable);
    }

execute 方法我们可以看到是使用了 sDefaultExecutor 这个线程池

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;
    }
  • executeOnExecutor 方法先判断了 AsyncTask 对象的状态,不是 running 运行中的话抛出异常中断执行
  • 然后刷新了 AsyncTask 对象的当前状态
  • 然后执行了我们 重写的 onPreExecute 方法
  • 把参数设置进去runnable 任务中 ,这个 mWorker 就是 AsyncTask 里面封装的 runnable 任务类型对象,有兴趣的自己去看,我这里不涉及到这个内容。
  • 最后执行用线程池执行包装了好几层的异步任务

重点来了,我们来看看 sDefaultExecutor 这个线程池对象,sDefaultExecutor 的类型是 AsyncTask 的内部类 SerialExecutor

    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 直接实现了 Executor 线程池这个根接口
  • execute 运行方法首先往任务列表里添加了一个任务进去,Runnable 对象
  • 然后判断当前是不是有任务正在执行,没有的话就拿到一个任务去线程池执行
  • 大家注意这时用的是 THREAD_POOL_EXECUTOR 这个线程池

我们来看看往任务队列中添加任务时的 Runnable 对象。run 方法里首先执行的是后台任务,然后就是在任务完成后拿到下一个任务去 线程池里去执行,循环就是这么跑起来的。

总结下核心思路

源码看到上面就算完事了,线程池的执行思路已经很清楚了,总结下:

  • AsyncTask 的2个线程池,SerialExecutor 类型的 sDefaultExecutor 对象负责存储,分发任务;ThreadPoolExecutor 类型的 THREAD_POOL_EXECUTOR 对象负责执行任务
  • sDefaultExecutor 线程池会给任务对象加点料,既在任务结束时添加获取下一个任务去执行的代码,然后把这个加了料的任务抛给 THREAD_POOL_EXECUTOR 线程池对象去执行,这样一个串行循环就跑起来了,一个执行完了再去取下一个任务执行。
  • ThreadPoolExecutor 类型的这线程池空有多个核心线程,其实每次都是在单线程在跑,要不怎么串行的起来,浪费了,为啥会这样恩,历史原因呗

AsyncTask 的历史

这里借鉴《开发艺术探索》的内容:

  • 1.6 之前 AsyncTask 是串型执行的
  • 1.6 时改成并行执行的
  • 3.0 时改回串行执行了,因为并发执行在 刷新 UI 时可能会有问题。

但是 AsyncTask 还是有并行执行的方法

            DownloadTask downloadTask = new DownloadTask();
            downloadTask2.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, urls);

3.0 开始提供了 executeOnExecutor 方法,重点在于替换 AsyncTask 里面的默认线程池对象,使用 AsyncTask 的常量 AsyncTask.THREAD_POOL_EXECUTOR 这个线程池。

AsyncTask 如何在 UI 线程刷新数据

这点大家肯定会想到 handle 了吧,在 AsyncTask 构造方法里初始化了 mWorker 对象,这个就是 Runnable 的包装类型,里面在执行完后异步任务后拿到数据时用到了 postResult(result) 这个方法

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

postResult(result) 方法

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

postResult 方法里面用 handle 对象生成了一个 message 对象,然后发射出去,重点在于用的哪个 handle 对象,大家还记得上面我说的谁是主角的套路了吧,

    private Handler getHandler() {
        return mHandler;
    }

那当然就是 mHandler 啦,要不然给 mHandler 做这么多戏干嘛,然后我们顺着 mHandler 的InternalHandler 类型可以获得最终想要的地方

    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:
                    result.mTask.onProgressUpdate(result.mData);
                    break;
            }
        }
    }
    private void finish(Result result) {
        if (isCancelled()) {
            onCancelled(result);
        } else {
            onPostExecute(result);
        }
        mStatus = Status.FINISHED;
    }

没啥说的了吧,其实我们仔细看的话 很多源码都能看的懂的,即便是又不懂的地方先放下,其实核心的逻辑思路大半都能看懂,其实这些的话就能应付面试啦.


2019.4.22 补充

AsyncTask 开启线程的方法 asyncTask.execute() 默认是也是开启一个线程和一个队列的,不过也可以通过asyncTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, 0)开启一个含有5个新线程的线程池,也就是说有个5个队列了,假如说你执行第6个耗时任务时,除非前面5个都还没执行完,否则任务是不会阻塞的,这样就可以大大减少耗时任务延迟的可能性,这也是它的优点所在。当你想多个耗时任务并发的执行,那你更应该选择AsyncTask。

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

推荐阅读更多精彩内容