Handler 消息机制详解

前言

  • 在 Android 中,Handler 是贯穿于整个应用的消息机制,在面试中出现的概率为:100%
  • 在这篇文章里,我将带你梳理 Handler 的使用攻略 & 设计原理。追求简单易懂又不失深度,如果能帮上忙,请务必点赞加关注!

延伸文章


目录

image

1. 概述

在 Android 中,很多地方是通过消息机制驱动的,例如线程间通信、四大组件的启动等等。消息机制中主要涉及 的类有:Handler & Looper & MessageQueue & Message,其中 Handler 可以说是消息机制提供给 Java 层的上层接口。

1.1 概念模型

问:Handler 怎么进行线程通信,原理是什么?

消息机制其实并不是 Android 系统独有的设计,在很多系统设计中都可以看到消息机制的身影,例如 IOS 的 runLoop、Web 的 Ajax 和 Spring 的消息队列等。在所有系统设计的消息机制里,都会有生产者与消费者的概念,如以下模型:

消息机制概念模型

其中消息缓冲区的具体实现可以是栈 & 队列,因为队列(特别是优先级队列)是最常见的,所以很多情况都会直接将消息缓冲区称为消息队列

1.2 架构图

【类图】

  • 里组合了 MessageQueue 消息队列,创建 Looper的同时也创建了 MessageQueue
  • MessageQueue 里组合了待处理的 Message链表
  • Message 持有用于处理消息的 Handler(target)
  • Handler 被创建时需要聚合 LooperMessageQueue,默认使用的是当前线程的 Looper
    image.png

流程如下
1、在主线程中新建handler对象;
2、在子线程调用handler发送消息;
3、handler会往自己所在LooperMessageQueue 中发送一条Message
4、Looper不断从MessageQueue中获取Message 发送给target Handler对象进行消息分发;

1.3 消息的享元模式

消息机制里需要频繁创建消息对象(Message),因此消息对象需要使用享元模式来缓存,以避免重复分配 & 回收内存。具体来说,Message 使用的是有容量限制的、无头节点的单链表的对象池:

Message的享元模式
Message 的数据结构

2. Handler 核心源码分析

这一节我们来分析 Handler 的核心源码:

2.1 启动消息循环

  • 问:Looper 如何在子线程中创建?(字节、小米)

要在哪个线程启动消息循环,就需要在该线程执行Looper.prepare() & Looper.loop()。只有调用Looper.loop()之后,消息循环才算真正运转起来了。具体来说,启动消息循环的分为两种情况:主线程消息循环 & 子线程消息循环,前者由 Framework 启动,而后者需要我们自己启动:

  • 主线程消息循环
主线程消息循环

可以看到,在应用启动时,Framework 已经为主线程开启了消息循环,后续我们熟悉的startActivity & startService都是通过主线程消息循环来驱动的。

  • 子线程消息循环
子线程消息循环

在子线程开启消息循环,我们需要自己调用Looper.prepare() & Looper.loop();。可以直接创建线程,或者使用 HandlerThread,后者主要考虑的多线程中获取 Looper 的同步问题,见 第 5.1 节

小结一下:创建 Handler 的代码需要放在Looper.prepare(); & Looper.loop();中间执行,这是因为创建 Handler 对象时需要聚合 Looper 对象(默认使用的是当前线程的 Looper),而只有执行Looper.prepare();之后,才会创建该线程私有的 Looper 对象,否则创建 Handler 会抛异常。

2.2 Looper 线程唯一性

  • 问:说一下 Looper、handler、线程间的关系。例如一个线程可以对应几个 Looper、几个Handler?
  • 问:ThreadLocal 的原理,以及在 Looper 是如何应用的?

每个线程只允许调用一次Looper.prepare(),否则会抛异常。这样设计是因为一个 Looper 对应了一个消息循环,而一个线程进行多个消息循环是没有意义的(一个线程不可能同时进行两个死循环)。那么,Handler 是如何保证 Looper 线程唯一的呢?

答:首先,Handler 主要利用了 ThreadLocal 在每个线程单独存储副本的特性,保证了一个ThreadLocal<Looper>在不同线程存取的Looper对象相互独立;其次,ThreadLocal 是 Looper 的一个static final变量,这样就保证了整个进程中 sThreadLocal对象不可变;第三,Looper.prepare()判断在一个线程里重复调用,则会抛出异常。

Looper 线程唯一性

关于 ThreadLocal 的原理分析,在这篇文章里,我们详细讨论:《Java | ThreadLocal 用法解析》,请关注!

2.3 消息发送

  • 问:Handler#post(Runnable) 是如何执行的?(字节、小米)
  • 问:Handler#sendMessage() 和 Handler#postDelay() 的区别?(字节)
  • 问:多个 Handler 发消息时,消息队列如何保证线程安全?
  • 问:为什么 MessageQueue 不设置消息上限?

消息发送的 API 非常多,最终它们都会调用到Handler#sendMessageAtTime(Message msg, long uptimeMillis),内部会交给MessageQueue#enqueueMessage(Message msg, long when)处理,梳理如下:

image
enqueueMessage

小结一下:

  • 每个消息的处理时间(when)不一样(SystemClock.uptimeMillis() + delayMill)
  • 消息入队时,根据消息的处理时间(when)插入排序,队头的消息就是最需要执行的消息
  • 当消息队列为空时(无消息时线程会阻塞),消息入队需要唤醒线程
  • 当消息队列不为空时(一般不需要唤醒),只有当开启同步屏障后第一个异步消息需要唤醒(开启同步屏障时会在队首插入一个占位消息,此时消息队列不为空,但是线程可能是阻塞的),关于同步屏障的内容见第 3 节

2.4 非延时 Message 什么时候执行?为什么?

答:** 非延时 Message 并非立即执行,只是放入 MessageQueue 等待调度而已,执行时刻不确定。**

  • 第一次 send 的 Message 在 enqueue 进 MessageQueue 的队首后,通知 Native 侧 wake
  • 后续发送的其他 Message 或 Runnable 挨个 enqueue 进队列
  • 接着执行主线程的其他 Message,比如日志的打印
  • 空闲后 wake 完毕并在 next() 的下一次循环里将队首 Message 移除和返回给Looper 去回调和执行
  • 之后 loop() 开始读取 MessageQueue 当前队首 Message 的下一次循环,当前时刻必然晚于 send 时候设置的when,所以队列里的 Message 挨个出队和回调

这些 Message 会按照 when(SystemClock.uptimeMillis() + delayMillis) 的先后排队进入 MessageQueue 中,当 Message 满足了条件会立即调用 wake,反之只是插入队列而已。

MessageQueue 会记录请求的时刻,按照时刻的先后顺序进行排队。
如果 MessageQueue 中积攒了很多 Message,或主线程被占用的话,Message 的执行会明显晚于请求的时刻。
比如在 onCreate() 里发送 message 的话,你会发现当 onResume() 执行完才会回调你的 Message。
原因在于 onCreate() 等操作也是由 Message 触发,其同步处理完 onResume() 之后,才有机会进入下一次循环去读取你的 Message。

2.5 延时 Message (postDelayed)什么时候执行?为什么?

image.png

答:由于唤醒时长的计算误差和回调的任务可能占用线程,导致延时执行 Message 不是时间到了就会执行,其执行的时刻必然晚于 Delay 的时刻。
延时 Message 执行的时刻 when 采用的是发送的时刻和 Delay 时长的累加,基于此排队进 MessageQueue
Delay Message 尚未抵达的时候,MessageQueue#next() 会将读取队列的时刻与 when 的差值,作为下一次通知 Native 休眠的时长。进行下一次循环前,next() 还存在其他逻辑,导致 wake up 的时刻存在滞后。此外由于 wake up 后线程存在其他 Message 占用,导致执行更加延后。

2.6 插队 Message(sendMessageAtFrontOfQueue()postAtFrontOfQueue())什么时候执行?

答:sendMessageAtFrontOfQueue()postAtFrontOfQueue()的 API 通过将 when 预设为 0 进而插入 Message 至队首,最终达到 Message 先得到执行的目的。
但需要注意的是,这将造成本来先该执行的 Message 被延后调度,对于存在先后关系的业务逻辑来说将可能造成顺序问题,谨慎使用!

2.7 异步 Message什么时候执行?

答:Handler 发送的 Message 都是同步的,意味着大家都按照 when 的先后进行排序,谁先到谁执行。
如果遇到优先级高的 Message 可以通过 FrontQueue 发送插队 Message即可。但如果是希望同步的队列停滞只执行指定 Message 的话,即 Message 异步执行,现有的 API 是不够的。
事实上 Android 提供了同步屏障的机制来实现这一需求,不过主要面向的是系统 App 或 系统,App 可以通过反射来使用。

结论:
可以通过异步 Handler,也可以通过异步 Message 两种方式向 MessageQueue 添加异步 Message
但都需要事先建立同步屏障,屏障的建立时间必须在阻拦的 Message 发出之前
可以建立多个同步屏障,将按照指定的时刻排队,通过计数 Token 进行识别
同步屏障使用完之后记得移除,否则后续的 Message 永远阻塞

和插队 Message 的区别:

插队 Message 只能确保先执行,完了后续的 Message 还得执行
异步 Message 则不同,同步屏障一旦建立将保持休眠,直到异步 Message 抵达。只有同步屏障被撤销,后续 Message 才可恢复执行

详细内容请看 万字复盘 Handler 中各式 Message 的使用和原理

2.8 消息获取

  • 问:消息队列无消息会怎么样?为什么 block 不会 ANR?
  • 问:Looper 死循环为什么不会 ANR?(B站)
  • 问:Handler内存泄漏的原因?

上一节我们说到,消息入队后 Looper 所在线程就会被唤醒(如果被阻塞),以继续消息循环。在消息循环中,Looper.loop()会死循环从 MessageQueue 获取队首的消息,因为消息已经按照处理时间(when)排序,所以每次获取的都是when最小的消息:
MessageQueue#next() 代码如下:

        .....//省略一些代码
        int pendingIdleHandlerCount = -1; // -1 only during first iteration
        // 1.如果nextPollTimeoutMillis=-1,一直阻塞不会超时。
        // 2.如果nextPollTimeoutMillis=0,不会阻塞,立即返回。
        // 3.如果nextPollTimeoutMillis>0,最长阻塞nextPollTimeoutMillis毫秒(超时)
        //   如果期间有程序唤醒会立即返回。
        int nextPollTimeoutMillis = 0;
        //next()也是一个无限循环
        for (;;) {
            if (nextPollTimeoutMillis != 0) {
                Binder.flushPendingCommands();
            }

            // 阻塞对应时间 
            nativePollOnce(ptr, nextPollTimeoutMillis);
           // 对MessageQueue进行加锁,保证线程安全
            synchronized (this) {
                //获取系统开机到现在的时间
                final long now = SystemClock.uptimeMillis();
                Message prevMsg = null;
                Message msg = mMessages; //当前链表的头结点

                //如果target==null,那么它就是屏障,需要循环遍历,一直往后找到第一个异步的消息
                if (msg != null && msg.target == null) {
                    // 同步屏障,找到下一个异步消息
                    do {
                        prevMsg = msg;
                        msg = msg.next;
                    } while (msg != null && !msg.isAsynchronous());
                }
                if (msg != null) {
                    //如果有消息需要处理,先判断时间有没有到,如果没到的话设置一下阻塞时间,
                    //场景如常用的postDelay
                    if (now < msg.when) {
                       // 下一个消息还没开始,等待两者的时间差
                       //计算出离执行时间还有多久赋值给nextPollTimeoutMillis,
                       //表示nativePollOnce方法要等待nextPollTimeoutMillis时长后返回
                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                    } else {
                        // 获得消息且现在要执行,标记MessageQueue为非阻塞
                        mBlocked = false;
                       //链表操作,获取msg并且删除该节点 
                        if (prevMsg != null) 
                            prevMsg.next = msg.next;
                        } else {
                            mMessages = msg.next;
                        }
                        msg.next = null;
                        msg.markInUse();
                        //返回拿到的消息
                        return msg;
                    }
                } else {

                    //没有消息,nextPollTimeoutMillis复位, 进入阻塞状态
                    nextPollTimeoutMillis = -1;
                }
                // 当调用Looper.quitSafely()时候执行完所有的消息后就会退出
                if (mQuitting) {
                    dispose();
                    return null;
                }
                .....//省略

    }

至于 Looper 死循环为什么不会 ANR?

消息队列中无消息怎么处理 block
nativePollOnce值为-1表示无限等待,让出cpu时间片给其线程,本线程等待
0表示无须等待直接返回
nativePollOnce -> epoll(linux) ->linux层的MessageQueue

msg -> 5s -> ANRmsg

ANR:
5 秒内没有响应输入事件,比如按键、屏幕触摸
10 秒内没有处理广播
本质:消息队列中其他消息耗时,按键或广播消息没有及时处理

根本原因不是线程在睡眠,而是消息队列被其他耗时消息阻塞,导致按键或广播消息没有及时处理

Handler内存泄漏的原因
MessageQueue持有Message,Message持有activity
delay多久,message就会持有activity多久
方法:静态内部类、弱引用

取到一个消息时,如果when还不到,则有限等待(nextPollTimeoutMills)nativePoll()
如果消息队列没有消息,则无限等待nativePoll(-1,),而消息入队时,会执行nativeWake()

quit 也会 nativeWake,唤醒Looper所在线程 => messagequeue返回null => Looper退出

2.9 消息分发

  • 问:Message.callback 与 Handler.callback 哪个优先?
  • 问:Handler.callback 和 handlemessage() 都存在,但 callback 返回 true,handleMessage() 还会执行么?(字节、小米)

获取需要执行的消息之后,将调用msg.target.dispatchMessage(msg);处理消息,具体如下:

public void dispatchMessage(Message msg) {
    if (msg.callback != null) {
        // 1\. 设置了Message.Callback(Runnable)
        handleCallback(msg);
    } else {
        if (mCallback != null) {
            // 2\. 设置了 Handler.Callback(Callback )
            if (mCallback.handleMessage(msg)) {
                return;
            }
        }
        // 3\. 未设置 Handler.Callback 或 返回 false
        handleMessage(msg);
    }
}
public interface Callback {
    public boolean handleMessage(Message msg);
}

可以看到,除了在Handler#handleMessage(...)中处理消息外,Handler 机制还提供了两个 Callback 来增加消息处理的灵活性。

具体来说,
若设置了Message.Callback则优先执行,
否则判断Handler.Callback的返回结果,如果返回false
则最后分发到Handler.handleMessage(...)

2.10 终止消息循环

quit:
mQuitting = true
removeAllMessage()
nativeWake() 唤醒,程序从nativePollOnce(-1)开始执行

主线程Looper不允许退出 quit() 抛异常 mQuitAllowed = false
ActivityThead#main looper.loop() 之后抛异常 

原因:是handler驱动的机制,所有的事件都需要Handler处理,例如LAUNCH_ACTIVITY等

3. Handler 同步屏障机制

同步屏障(SyncBarrier)是 Handler 用来筛选高低优先级消息的机制,即:当开启同步屏障时,高优先级的异步消息优先处理。

这其中有个小细节,估计很多人没有注意到,那就是消息机制的同步屏障是什么? 同步屏障与target == null有什么关系?与 target 不为 null 的区别在哪里?这篇文章就是要揭露同步屏障与 target 之间的微妙关系。

3.1 target 为何物

首先,看下 Message 类中 target 的定义出处:

//Message.java

Handler target;

从这里可以知道,Message 是持有 Handler 的, 所谓的 target 即为 Handler 对象。

让我们再看看 target 是哪里出现的?

我们知道,通过 Handle 发送消息的时候(如调用Handler#sendMessage()等 ),最终都是会调用 Handler#enqueueMessage()让消息入队,如下:

 private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        msg.target = this;
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }

看到没,当我们发送一个消息的时候,msg.target 就会被赋值为this, 而 this 即为我们的 Handler 对象。因此,通过这种方式传进来的消息的 target 肯定也就不为 null,并且 mAsynchronous 默认为 false,也就是说我们一般发送的消息都为同步消息。相对地,也应该有异步消息吧?的确,还有一种很容易被忽略的 异步消息,因为除了系统的源码外,我们一般很少会使用异步消息。那么什么是异步消息呢?

这里先说一下结论:满足target == null的消息就是异步消息。那么,如何发送一个异步消息呢?

简单来说有两种方式。

一种是直接设置消息为异步的:

Message msg = mMyHandler.obtainMessage();
msg.setAsynchronous(true);
mMyHandler.sendMessage(msg);

还有一个需要用到 Handler 的一个构造方法,不过该方法已被标记为@Hide了:

/**
  * @hide
  */
  public Handler(boolean async) {
     this(null, async);
  }

使用如下:

Handler mMyHandler = new Handler(true);
Message msg = mHandler.obtainMessage();
mMyHandler.sendMessage(msg);

参数 asynctrue 即为异步消息。

但需要注意的是,通过上面两种方式来发送的消息还不是异步消息,因为它们最终还是会进入 enqueueMessage(),仍然会给 target 赋值 ,导致 target 不为null。这与前面所说的同步消息无异。那么什么情况下会满足target == null 这个条件呢?

咱们今天的主角,同步屏障 (Sync Barrier) 就要登场啦。

3.2 同步屏障是什么

没错,发送异步消息的关键就是要消息开启一个同步屏障。屏障的意思即为阻碍,顾名思义,同步屏障就是阻碍同步消息,只让异步消息通过
如何开启同步屏障呢?如下而已:

// MessageQueue.java
   /**
     * @hide 默认是调用的时刻开始建立屏障
     */
    public int postSyncBarrier() {
        return postSyncBarrier(SystemClock.uptimeMillis());
    }

    // 同步屏障支持指定开始的时刻
    // 默认是调用的时刻,而 0 表示?
    private int postSyncBarrier(long when) {
        synchronized (this) {
            // 同步屏障可以建立多个,用计数的 Token 变量识别
            final int token = mNextBarrierToken++;

            // 初始化Message对象的时候,并没有给target赋值,因此 target==null
            //从消息池中获取一个屏障 Message,其 target 属性为空
            final Message msg = Message.obtain();
            msg.markInUse();
            msg.when = when; // 指定 when 属性为屏障的开始时刻
            // 将 Token 存入屏障 Message,用以识别对应的同步屏障
            msg.arg1 = token;

            // 按照 when 的先后
            // 找到屏障 Message 插入队列的适当位置
            // 所以,如果同步屏障的建立调用得晚
            // 那么在它之前的 Message 无法阻拦
            Message prev = null;
            Message p = mMessages;
            if (when != 0) {

                while (p != null && p.when <= when) {
                     //如果开启同步屏障的时间(假设记为T)T不为0,且当前的同步消息里有时间小于T,则prev也不为null
                    prev = p;
                    p = p.next;
                }
            }

            // 插入同步屏障
            // 根据prev是不是为null,将 msg 按照时间顺序插入到 消息队列(链表)的合适位置
            if (prev != null) {
                msg.next = p;
                prev.next = msg;
            } else {
                // 如果队列尚无 Message
                // 或队首的 Message 时刻
                // 都比屏障 Message 要晚的话
                // 将屏障 Message 插入队首
                msg.next = p;
                mMessages = msg;
            }
            
            // 返回上面的 Token 给调用端
            // 主要用于移除对应的屏障
            return token;
        }
    }

可以看到,Message 对象初始化的时候并没有给 target 赋值,因此,target == null的 来源就找到了。上面消息的插入也做了相应的注释。这样,一条target == null 的消息就进入了消息队列。

那么,开启同步屏障后,所谓的异步消息又是如何被处理的呢?

如果对消息机制有所了解的话,应该知道消息的最终处理是在消息轮询器Looper#loop()中,而loop()循环中会调用
MessageQueue#next()从消息队列中进行取消息,来看看关键代码:

//MessageQueue.java
    Message next() {

        .....//省略一些代码
        int pendingIdleHandlerCount = -1; // -1 only during first iteration
        // 1.如果nextPollTimeoutMillis=-1,一直阻塞不会超时。
        // 2.如果nextPollTimeoutMillis=0,不会阻塞,立即返回。
        // 3.如果nextPollTimeoutMillis>0,最长阻塞nextPollTimeoutMillis毫秒(超时)
        //   如果期间有程序唤醒会立即返回。
        int nextPollTimeoutMillis = 0;
        //next()也是一个无限循环
        for (;;) {
            if (nextPollTimeoutMillis != 0) {
                Binder.flushPendingCommands();
            }

            // 阻塞对应时间 
            nativePollOnce(ptr, nextPollTimeoutMillis);
           // 对MessageQueue进行加锁,保证线程安全
            synchronized (this) {
                //获取系统开机到现在的时间
                final long now = SystemClock.uptimeMillis();
                Message prevMsg = null;
                Message msg = mMessages; //当前链表的头结点

                //关键!!!
                //如果target==null,那么它就是屏障,需要循环遍历,一直往后找到第一个异步的消息

                if (msg != null && msg.target == null) { // 队首是屏障 Message 的话
                    // 同步屏障,找到下一个异步消息
                    do {
                        prevMsg = msg;
                        msg = msg.next;
                    } while (msg != null && !msg.isAsynchronous());
                }

                // 没有建立同步屏障且队里有 Message
                // 或者
                // 建立了同步屏障下且找到了异步 Message
                if (msg != null) {
                    //如果有消息需要处理,先判断时间有没有到,如果没到的话设置一下阻塞时间,
                    //场景如常用的postDelay
                    if (now < msg.when) {// 如果当前时间尚早于目标执行时刻
                       // 下一个消息还没开始,等待两者的时间差
                       //计算出离执行时间还有多久赋值给nextPollTimeoutMillis,
                       //表示nativePollOnce方法要等待nextPollTimeoutMillis时长后返回
                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                    } else {
                        // 获得消息且现在要执行,标记MessageQueue为非阻塞
                        mBlocked = false;
                       //链表操作,获取msg并且删除该节点 
                        if (prevMsg != null) // Message 找到了并出队
                            prevMsg.next = msg.next;
                        } else {
                            mMessages = msg.next;
                        }
                        msg.next = null;
                        msg.markInUse();
                        //返回拿到的消息
                        return msg;
                    }
                } else {

                    //没有消息,nextPollTimeoutMillis复位, 进入阻塞状态
                    nextPollTimeoutMillis = -1;
                }
                // 当调用Looper.quitSafely()时候执行完所有的消息后就会退出
                if (mQuitting) {
                    dispose();
                    return null;
                }
                .....//省略

    }

先看同步屏障的部分:

if (msg != null && msg.target == null) {
    // 同步屏障,找到下一个异步消息
    do {
        prevMsg = msg;
        msg = msg.next;
    } while (msg != null && !msg.isAsynchronous());
}

从上面可以看出,如果遇到同步屏障,那么会循环遍历整个链表找到标记为异步消息的Message,即isAsynchronous返回true,其他的消息会直接忽视。
当消息队列开启同步屏障的时候(即标识为msg.target == null),消息机制在处理消息的时候,优先处理异步消息。这样,同步屏障就起到了一种过滤和优先级的作用。

下面用示意图简单说明:

image.png

如上图所示,在消息队列中有同步消息和异步消息(黄色部分)以及一道墙----同步屏障(红色部分)。有了同步屏障的存在,msg_2 和 msg_M 这两个异步消息可以被优先处理,而后面的 msg_3 等同步消息则不会被处理。那么这些同步消息什么时候可以被处理呢?那就需要先移除这个同步屏障,即调用removeSyncBarrier()

有了同步屏障,那么唤醒的判断条件就必须再加一个:MessageQueue中有同步屏障且处于阻塞中,此时插入在所有异步消息前插入新的异步消息。这个也很好理解,跟同步消息是一样的。如果把所有的同步消息先忽视,就是插入新的链表头且队列处于阻塞状态,这个时候就需要被唤醒了。看一下源码:

//MessageQueue.java
boolean enqueueMessage(Message msg, long when) {
        ...
        // 鉴于多线程往 Handler 里发送 Message 的情况
        // 在向队列插入 Message 前需要上锁
        synchronized (this) {
            ...
            msg.markInUse(); // Message 标记正在使用
            msg.when = when; // 更新 when 属性
            Message p = mMessages; // 拿到队列的 Head 
            boolean needWake;
            // 如果队列为空
            // 或者 Message 需要插队(sendMessageAtFrontOfQueue)
            // 又或者 Message 执行时刻比 Head 的更早
            // 该 Message 插入到队首
            if (p == null || when == 0 || when < p.when) {
                // New head, wake up the event queue if blocked.
                msg.next = p;
                mMessages = msg;
                // 线程是否因为没有可执行的 Message 正在 block 或 wait
                // 是的话,唤醒
                needWake = mBlocked;
            } else {
                // 如果队列已有 Message,Message 优先级又不高,同时执行时刻并不早于队首的 Message

            /**
            * 1
            */
                // 如果线程正在 block 或 wait,或建立了同步屏障(target 为空),并且 Message 是异步的,则唤醒
                needWake = mBlocked && p.target == null && msg.isAsynchronous();
                Message prev;
                // 遍历队列,找到 Message 目标插入位置
                for (;;) {
                    prev = p;
                    p = p.next;
                    // 如果已经遍历到队尾了,或 Message 的时刻比当前 Message 要早
                    // 找到位置了,退出遍历
                    if (p == null || when < p.when) {
                        break;
                    }
                    
                /**
                * 2
                */
                // 如果找到一个异步消息,说明前面有延迟的异步消息需要被处理,不需要被唤醒
                    if (needWake && p.isAsynchronous()) {
                        needWake = false;
                    }
                }
                // 将 Message 插入队列的目标位置
                msg.next = p;
                prev.next = msg;
            }

            // 需要唤醒的话,唤醒 Native 侧的 MessageQueue
            if (needWake) {
                nativeWake(mPtr);
            }
        }
        return true;
    }

同样,这个方法我之前讲过,把无关同步屏障的代码忽视,
看到注释1处的代码:** 如果插入的消息是异步消息,且有同步屏障,同时MessageQueue正处于阻塞状态,那么就需要唤醒。
而如果这个异步消息的插入位置不是所有异步消息之前,那么不需要唤醒,如注释2。**

那我们如何发送一个异步类型的消息呢?有两种办法:

使用异步类型的Handler发送的全部Message都是异步的给Message标志异步
Handler有一系列带Boolean类型的参数的构造器,这个参数就是决定是否是异步:

//Handler.java
public Handler(@NonNull Looper looper, @Nullable Callback callback, boolean async) {
    mLooper = looper;
    mQueue = looper.mQueue;
    mCallback = callback;
    // 这里赋值
    mAsynchronous = async;
}

在发送消息的时候就会给Message赋值:

//Handler.java
private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
        long uptimeMillis) {
    msg.target = this;
    msg.workSourceUid = ThreadLocalWorkSource.getUid();
 // 赋值
    if (mAsynchronous) {
        msg.setAsynchronous(true);
    }
    return queue.enqueueMessage(msg, uptimeMillis);
}

但是异步类型的Handler构造器是标记为hide,我们无法使用,所以我们使用异步消息只有通过给Message设置异步标志:

// Message.java
public void setAsynchronous(boolean async) {
    if (async) {
        flags |= FLAG_ASYNCHRONOUS;
    } else {
        flags &= ~FLAG_ASYNCHRONOUS;
    }
}

对原理进行简单的总结:

  • 同步屏障的建立:按照调用的时刻 when 在合适的位置放入一个屏障 Message(target 属性为 null)来实现,同时得到标识屏障的计数 token 存入屏障 Message
    读取队列的时候发现存在屏障 Message 的话,会遍历队列并返回最早执行的异步 Message
  • 同步屏障的移除:按照 token 去队列里找到匹配的屏障 Message 进行出队操作,如果出队后队首存在 Message 且非另一个同步屏障的话,立即唤醒 looper 线程

3.3 同步屏障使用场景

似乎在日常的应用开发中,很少会用到同步屏障。那么,同步屏障在系统源码中有哪些使用场景呢?Android 系统中的 UI 更新相关的消息即为异步消息,需要优先处理。

比如,在 View 更新时,draw、requestLayout、invalidate 等很多地方都调用了ViewRootImpl#scheduleTraversals(),如下:

//ViewRootImpl.java

    void scheduleTraversals() {
        if (!mTraversalScheduled) {
            mTraversalScheduled = true;
            //开启同步屏障
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
            //发送异步消息
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            if (!mUnbufferedInputDispatch) {
                scheduleConsumeBatchedInput();
            }
            notifyRendererOfFramePending();
            pokeDrawLockIfNeeded();
        }
    }

postCallback()最终走到了ChoreographerpostCallbackDelayedInternal()

image.png

这里就开启了同步屏障,并发送异步消息,由于 UI 更新相关的消息是优先级最高的,这样系统就会优先处理这些异步消息。

最后,当要移除同步屏障的时候需要调用ViewRootImpl#unscheduleTraversals()

//ViewRootImpl.java
    void unscheduleTraversals() {
        if (mTraversalScheduled) {
            mTraversalScheduled = false;
            //移除同步屏障
            mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
            mChoreographer.removeCallbacks(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
        }
    }

3.4 总结

同步屏障的设置可以方便地处理那些优先级较高的异步消息。当我们调用Handler.getLooper().getQueue().postSyncBarrier() 并设置消息的setAsynchronous(true)时,target 即为 null ,也就开启了同步屏障。当在消息轮询器 Looper 在loop()中循环处理消息时,如若开启了同步屏障,会优先处理其中的异步消息,而阻碍同步消息。

4. IdleHandler 机制

  • 问:IdleHandler 是什么?怎么使用,能解决什么问题?

答: 当MessageQueue为空或者目前没有需要执行的Message时会回调的接口对象。
IdleHandler看起来好像是个Handler,但他其实只是一个有单方法的接口,也称为函数型接口:

public static interface IdleHandler {
    boolean queueIdle();
}

MessageQueue中有一个List存储了IdleHandler对象,当MessageQueue没有需要被执行的Message时就会遍历回调所有的IdleHandler。所以IdleHandler主要用于在消息队列空闲的时候处理一些轻量级的工作。
IdleHandler的调用是在next方法中:

//MessageQueue.java
 next() {
    // 如果looper已经退出了,这里就返回null
    final long ptr = mPtr;
    if (ptr == 0) {
        return null;
    }

    // IdleHandler的数量
    int pendingIdleHandlerCount = -1; 
    // 阻塞时间
    int nextPollTimeoutMillis = 0;
    for (;;) {
        if (nextPollTimeoutMillis != 0) {
            Binder.flushPendingCommands();
        }
        // 阻塞对应时间 
        nativePollOnce(ptr, nextPollTimeoutMillis);
  // 对MessageQueue进行加锁,保证线程安全
        synchronized (this) {
            final long now = SystemClock.uptimeMillis();
            Message prevMsg = null;
            Message msg = mMessages;
            if (msg != null && msg.target == null) {
                // 同步屏障,找到下一个异步消息
                do {
                    prevMsg = msg;
                    msg = msg.next;
                } while (msg != null && !msg.isAsynchronous());
            }
            if (msg != null) {
                if (now < msg.when) {
                    // 下一个消息还没开始,等待两者的时间差
                    nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                } else {
                    // 获得消息且现在要执行,标记MessageQueue为非阻塞
                    mBlocked = false;
                    // 一般只有异步消息才会从中间拿走消息,同步消息都是从链表头获取
                    if (prevMsg != null) {
                        prevMsg.next = msg.next;
                    } else {
                        mMessages = msg.next;
                    }
                    msg.next = null;
                    msg.markInUse();
                    return msg;
                }
            } else {
                // 没有消息,进入阻塞状态
                nextPollTimeoutMillis = -1;
            }

            // 当调用Looper.quitSafely()时候执行完所有的消息后就会退出
            if (mQuitting) {
                dispose();
                return null;
            }

            // 当队列中的消息用完了或者都在等待时间延迟执行同时给pendingIdleHandlerCount<0
            // 给pendingIdleHandlerCount赋值MessageQueue中IdleHandler的数量
            if (pendingIdleHandlerCount < 0
                    && (mMessages == null || now < mMessages.when)) {
                pendingIdleHandlerCount = mIdleHandlers.size();
            }
            // 没有需要执行的IdleHanlder直接continue
            if (pendingIdleHandlerCount <= 0) {
                // 执行IdleHandler,标记MessageQueue进入阻塞状态
                mBlocked = true;
                continue;
            }

            // 把List转化成数组类型
            if (mPendingIdleHandlers == null) {
                mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
            }
            mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
        }

        // 执行IdleHandler
        for (int i = 0; i < pendingIdleHandlerCount; i++) {
            final IdleHandler idler = mPendingIdleHandlers[i];
            mPendingIdleHandlers[i] = null; // 释放IdleHandler的引用
            boolean keep = false;
            try {
                keep = idler.queueIdle();
            } catch (Throwable t) {
                Log.wtf(TAG, "IdleHandler threw exception", t);
            }
            // 如果返回false,则把IdleHanlder移除
            if (!keep) {
                synchronized (this) {
                    mIdleHandlers.remove(idler);
                }
            }
        }

        // 最后设置pendingIdleHandlerCount为0,防止再执行一次
        pendingIdleHandlerCount = 0;

        // 当在执行IdleHandler的时候,可能有新的消息已经进来了
        // 所以这个时候不能阻塞,要回去循环一次看一下
        nextPollTimeoutMillis = 0;
    }
}

代码很多,可能看着有点乱,我梳理一下逻辑,然后再回去看源码就会很清晰了:

  1. 当调用next方法的时候,会给pendingIdleHandlerCount赋值为-1;
  2. 如果队列中没有需要处理的消息的时候,就会判断pendingIdleHandlerCount是否为<0,如果是则把存储IdleHandlerlist的长度赋值给pendingIdleHandlerCount;
  3. 把list中的所有IdleHandler放到数组中。这一步是为了不让在执行IdleHandler的时候List被插入新的IdleHandler,造成逻辑混乱;
  4. 然后遍历整个数组执行所有的IdleHandler
  5. 最后给pendingIdleHandlerCount赋值为0。然后再回去看一下这个期间有没有新的消息插入。因为pendingIdleHandlerCount的值为0不是-1,所以IdleHandler只会在空闲的时候执行一次;
  6. 同时注意,如果IdleHandler返回了false,那么执行一次之后就被丢弃了。

建议读者再回去把源码看一遍,这样逻辑会清晰很多。

5. 主线程为什么不用初始化Looper?

答:因为应用在启动的过程中就已经初始化主线程Looper了。

每个java应用程序都是有一个main方法入口,Android是基于Java的程序也不例外。Android程序的入口在ActivityThread的main方法中:

//ActivityThread.java
public static void main(String[] args) {
    ...
 // 初始化主线程Looper
    Looper.prepareMainLooper();
    ...
    // 新建一个ActivityThread对象
    ActivityThread thread = new ActivityThread();
    thread.attach(false, startSeq);

    // 获取ActivityThread的Handler,也是他的内部类H
    if (sMainThreadHandler == null) {
        sMainThreadHandler = thread.getHandler();
    }

    ...
    Looper.loop();
 // 如果loop方法结束则抛出异常,程序结束
    throw new RuntimeException("Main thread loop unexpectedly exited");
}

main方法中先初始化主线程Looper,新建ActivityThread对象,然后再启动Looper,这样主线程的Looper在程序启动的时候就跑起来了。我们不需要再去初始化主线程Looper。

6. 为什么主线程的Looper是一个死循环,但是却不会ANR?

答: 因为当Looper处理完所有消息的时候会进入阻塞状态,当有新的Message进来的时候会打破阻塞继续执行。

1、Looper上的阻塞,前提是没有输入事件,MsgQ不一定为空(有可能有延时的Message),但没有可执行的Message,Looper空闲状态,线程调用nativePollOnce(waitTime),进入阻塞,释放CPU执行权,等待唤醒。
2、UI耗时导致卡死,前提是有输入事件,MsgQ不为空,Looper正常轮询,线程没有阻塞,但是该事件执行时间过长(5秒?),而且与此期间其他的事件(按键按下,屏幕点击..)都没办法处理(卡死),然后就ANR异常了。

Looper的死循环,是循环执行各种事务,包括UI绘制事务。Looper死循环说明线程没有死亡,如果Looper停止循环,线程则结束退出了。Looper的死循环本身就是保证UI绘制任务可以被执行的原因之一。同时UI绘制任务有同步屏障,可以更加快速地保证绘制更快执行。

7. Handler如何保证MessageQueue并发访问安全?

答:循环加锁,配合阻塞唤醒机制。

我们可以发现MessageQueue其实是“生产者-消费者”模型,Handler不断地放入消息,Looper不断地取出,这就涉及到死锁问题。如果Looper拿到锁,但是队列中没有消息,就会一直等待,而Handler需要把消息放进去,锁却被Looper拿着无法入队,这就造成了死锁。Handler机制的解决方法是循环加锁。在MessageQueue的next方法中:

//MessageQueue.java
next() {
   ...
    for (;;) {
  ...
        nativePollOnce(ptr, nextPollTimeoutMillis);
        synchronized (this) {
            ...
        }
    }
}

我们可以看到他的等待是在锁外的,当队列中没有消息的时候,他会先释放锁,再进行等待,直到被唤醒。这样就不会造成死锁问题了。

那在入队的时候会不会因为队列已经满了然后一边在等待消息处理一边拿着锁呢?这一点不同的是MessageQueue的消息没有上限,或者说他的上限就是JVM给程序分配的内存,如果超出内存会抛出异常,但一般情况下是不会的。

8. Handler是如何切换线程的?

答: 使用不同线程的Looper处理消息。

前面我们聊到,代码的执行线程,并不是代码本身决定,而是执行这段代码的逻辑是在哪个线程,或者说是哪个线程的逻辑调用的。每个Looper都运行在对应的线程,所以不同的Looper调用的dispatchMessage方法就运行在其所在的线程了。

9. Android应用程序的消息处理机制

  1. Android应用程序的消息处理机制由消息循环、消息发送和消息处理三个部分组成的。
  2. Android应用程序的主线程在进入消息循环过程前,会在内部创建一个Linux管道(Pipe),这个管道的作用是使得Android应用程序主线程在消息队列为空时可以进入空闲等待状态,并且使得当应用程序的消息队列有消息需要处理时唤醒应用程序的主线程。
  3. Android应用程序的主线程进入空闲等待状态的方式实际上就是在管道的读端等待管道中有新的内容可读,具体来说就是是通过Linux系统的Epoll机制中的epoll_wait函数进行的。
  4. 当往Android应用程序的消息队列中加入新的消息时,会同时往管道中的写端写入内容,通过这种方式就可以唤醒正在等待消息到来的应用程序主线程。
    E. 当应用程序主线程在进入空闲等待前,会认为当前线程处理空闲状态,于是就会调用那些已经注册了的IdleHandler接口,使得应用程序有机会在空闲的时候处理一些事情。

10. Handler的阻塞唤醒机制是怎么回事?

答: Handler的阻塞唤醒机制是基于Linux的阻塞唤醒机制。

这个机制也是类似于handler机制的模式。在本地创建一个文件描述符,然后需要等待的一方则监听这个文件描述符,唤醒的一方只需要修改这个文件,那么等待的一方就会收到文件从而打破唤醒。和Looper监听MessageQueue,Handler添加message是比较类似的。具体的Linux层知识读者可通过这篇文章详细了解(传送门

11. Looper、Handler、MessageQueue、Message的关系

Looper、Handler、MessageQueue、Message概括来说就是:Looper负责的就是创建一个MessageQueue,然后进入一个无限循环体不断从该MessageQueue中读取消息Message,然后回调相应的消息处理函数,而消息的创建者就是一个或多个Handler,执行完成一个消息后则继续循环。

首先我们要调用Looper的prepare方法,然后调用Looper的loop方法。
(一)prepare()方法

// Looper.java
public static final void prepare() {
        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        sThreadLocal.set(new Looper(true));

sThreadLocal是一个ThreadLocal对象,可以在一个线程中存储变量。可以看到,将一个Looper的实例放入了ThreadLocal,并且先判断了sThreadLocal.get是否为null,否则抛出异常。这也就说明了Looper.prepare()方法不能被调用两次,同时也保证了一个线程中只有一个Looper实例。
(二)构造函数
上面的代码执行了Looper的构造函数,我们看一下其代码:

private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);
        mRun = true;
        mThread = Thread.currentThread();
}

复制代码在构造函数中,创建了一个消息队列MessageQueue,并将其赋值给其成员字段mQueue,这样Looper也就与MessageQueue通过成员字段mQueue进行了关联。
(三)loop()方法

public static void loop() {
        final Looper me = myLooper();
        if (me == null) {
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }
        
        final MessageQueue queue = me.mQueue;
        
        // Make sure the identity of this thread is that of the local process,
        // and keep track of what that identity token actually is.
        Binder.clearCallingIdentity();
        final long ident = Binder.clearCallingIdentity();
 
        for (;;) {
            Message msg = queue.next(); // might block
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }
 
            // This must be in a local variable, in case a UI event sets the logger
            Printer logging = me.mLogging;
            if (logging != null) {
                logging.println(">>>>> Dispatching to " + msg.target + " " +
                        msg.callback + ": " + msg.what);
            }
 
            msg.target.dispatchMessage(msg);
 
            if (logging != null) {
                logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
            }
 
            // Make sure that during the course of dispatching the
            // identity of the thread wasn't corrupted.
            final long newIdent = Binder.clearCallingIdentity();
            if (ident != newIdent) {
                Log.wtf(TAG, "Thread identity changed from 0x"
                        + Long.toHexString(ident) + " to 0x"
                        + Long.toHexString(newIdent) + " while dispatching to "
                        + msg.target.getClass().getName() + " "
                        + msg.callback + " what=" + msg.what);
            }
 
            msg.recycle();
        }
}

上面有几行代码是关键代码:
1. final Looper me = myLooper();
myLooper()方法直接返回了sThreadLocal存储的Looper实例,如果me为null则抛出异常,也就是说looper方法必须在prepare方法之后运行。

final Looper me = myLooper();
if (me == null) {
            throw new RuntimeException("No Looper; Looper.prepare() 
            wasn't called on this thread.");
        }
public static Looper myLooper() {
    return sThreadLocal.get();
}

2. final MessageQueue queue = me.mQueue;
拿到该looper实例中的消息队列mQueue。变量me是通过静态方法myLooper()获得的当前线程所绑定的Looper,me.mQueue是当前线程所关联的消息队列。
3. for (;;)
进入了循环。我们发现for循环没有设置循环终止的条件,所以这个for循环是个无限循环。
4. Message msg = queue.next(); // might block
取出一条消息,如果没有消息则阻塞。我们通过消息队列MessageQueue的next方法从消息队列中取出一条消息,如果此时消息队列中有Message,那么next方法会立即返回该Message,如果此时消息队列中没有Message,那么next方法就会阻塞式地等待获取Message。
5. msg.target.dispatchMessage(msg);
msg的target属性是Handler,该代码的意思是让Message所关联的Handler通过dispatchMessage方法让Handler处理该Message。
6. msg.recycle();
释放消息占据的资源。

(四)Looper主要作用
1.与当前线程绑定,保证一个线程只会有一个Looper实例,同时一个Looper实例也只有一个MessageQueue。
2.loop()方法,不断从MessageQueue中去取消息,交给消息Message的target属性,即Handler的dispatchMessage去处理。

到此,这个流程已经解释完毕,让我们首先总结一下:

  1. 首先Looper.prepare()在本线程中保存一个Looper实例,然后该实例中保存一个MessageQueue对象;因为Looper.prepare()在一个线程中只能调用一次,所以MessageQueue在一个线程中只会存在一个。
  2. 在Lopper构造函数中,创建了一个消息队列MessageQueue,并将其赋值给其成员字段mQueue,这样Looper也就与MessageQueue通过成员字段mQueue进行了关联。
  3. Looper.loop()会让当前线程进入一个无限循环,不端从MessageQueue的实例中读取消息,然后回调msg.target.dispatchMessage(msg)方法。
  4. Handler的构造方法,会首先得到当前线程中保存的Looper实例,进而与Looper实例中的MessageQueue想关联。
  5. Handler的sendMessage方法,会给msg的target赋值为handler自身,然后加入MessageQueue中。
  6. 在构造Handler实例时,我们会重写handleMessage方法,也就是msg.target.dispatchMessage(msg)最终调用的方法。

nativePollOnce()和wait()都不会占用cpu的时间,那为什么MessageQueue采用的是nativePollOnce()而不是wait()?

12. Handler 应用场景

12.1 HandlerThread

Handler 都是在 Looper 所在线程创建的,但是有时候我们需要在其他线程中创建 Looper 所在线程的 Handler,就需要考虑同步问题,使用 HandlerThread 可以简化这种同步处理:

既然涉及多个线程的通信,会有同步的问题,Android为了简化Handler的创建过程,提供了HandlerThread类

wait - notifyAll - 避免prepare之前调用getLooper()

【重点 锁的机制】

12.2 view.post(Runnable)

view.post(Runnable)什么时候执行?
post方法中Runnable 接口的回调中可以直接获取到View
ActivityonResume 方法比post方法中Runnable 接口的回调先执行,post方法中Runnable 接口是在View 的绘制(主要是View 的onMeasureonLayoutonDraw 方法)之后才会被回调;
View没有被添加到Window里的时候,执行post方法,Runnable接口不会被回调。

先看view.post源码:

public boolean post(Runnable action) {
        final AttachInfo attachInfo = mAttachInfo;
        if (attachInfo != null) {
            return attachInfo.mHandler.post(action);
        }

        // Postpone the runnable until we know on which thread it needs to run.
        // Assume that the runnable will be successfully placed after attach.
        getRunQueue().post(action);
        return true;
}

这里attachInfo 是为空的,所以不会执行 attachInfo.mHandler.post(action)这行代码,我们看 getRunQueue().post(action)这行代码,getRunQueue() 方法得到的是一个HandlerActionQueue 类型的对象,我们点击HandlerActionQueue 的post方法查看;

public void post(Runnable action) {
postDelayed(action, 0);
}
HandlerActionQueuepost方法又调用了自己的postDelayed方法,这里的参数 0 表示延时所有的事件,我们往下看HandlerActionQueuepostDelayed方法:

public void postDelayed(Runnable action, long delayMillis) {
        final HandlerAction handlerAction = new HandlerAction(action, delayMillis);

        synchronized (this) {
            if (mActions == null) {
                mActions = new HandlerAction[4];
            }
            mActions = GrowingArrayUtils.append(mActions, mCount, handlerAction);
            mCount++;
        }
}

从这个方法看出:HandlerAction表示要执行的任务,要执行的任务HandlerAction保存在数组长度为 4 的mActions数组中,mCount 表示数组mActions的下标,每次都加 1 ;这个postDelayd方法并没有马上执行任务,而是保存了任务,那么执行任务的语句在哪里呢?
ViewRootImplperformTraversals方法,我们来看看performTraversals方法:

private void performTraversals() {
        //12
        // cache mView since it is used so much below...
        final View host = mView;
        ......
        if (mFirst) {
            ......
            //13、看这里!!
            host.dispatchAttachedToWindow(mAttachInfo, 0);
            ......
        } else {
            ......
        }
        ......
        if (mFirst || windowShouldResize || insetsChanged ||
                viewVisibilityChanged || params != null || mForceNextWindowRelayout) {
            mForceNextWindowRelayout = false;
            ......
            if (!mStopped || mReportNextDraw) {
                    ......
                    //14、
                    // Ask host how big it wants to be
                    performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
                    ......
            }
        } else {
            ......
        }
        ......
        if (didLayout) {
            //15、
            performLayout(lp, mWidth, mHeight);
            ......
        }
        ......
        if (!cancelDraw && !newSurface) {
            ......
            //16、
            performDraw();
        } else {
            ......
        }
        ......
}

注释12 就是上面注释8 中说的底部容器DecorView
注释13 表示DecorView关联AttachInfodispatchAttachedToWindow方法是在ViewGroup里实现,该方法会遍历DecorView的子元素进行 关联AttachInfo,我们看一下ViewGroupdispatchAttachedToWindow方法:

    @Override
    void dispatchAttachedToWindow(AttachInfo info, int visibility) {
        ......
        for (int i = 0; i < count; i++) {
            final View child = children[i];
            
            //17、
            child.dispatchAttachedToWindow(info,
                    combineVisibility(visibility, child.getVisibility()));
        }
        ......
    }

这个方法是很重要的:
子元素关联了AttachInfo,然后将之前 View.post保存的任务添加到AttachInfo内部的Handler,所以View没有被添加到Window里的时候,执行post方法,Runnable接口没有被回调;
我们看注释17 的代码,它是ViewdispatchAttachedToWindow方法;

void dispatchAttachedToWindow(AttachInfo info, int visibility) {
        ......
        // Transfer all pending runnables.
        if (mRunQueue != null) {
            //18、
            mRunQueue.executeActions(info.mHandler);
            mRunQueue = null;
        }
        ......
 }

注释18 的代码表示调用了HandlerActionQueueexecuteActions方法,我们来看看executeActions方法;

public void executeActions(Handler handler) {
        synchronized (this) {
            final HandlerAction[] actions = mActions;
            for (int i = 0, count = mCount; i < count; i++) {
                final HandlerAction handlerAction = actions[i];

                //19、
                handler.postDelayed(handlerAction.action, handlerAction.delay);
            }

            //20、
            mActions = null;
            mCount = 0;
        }
}

注释19 行的代码我们先留下悬念;
注释20 是将mActions置空,从第二次调用View.post开始,Runnable会被添加到AttachInfo 内部的Handler,而不是HandlerActionViewonMeasureonLayoutonDraw 方法也不会被调用;

我们在回过头来看,注释19 的代码,它的延时时间为 0 啊,而且比 ViewonMeasureonLayoutonDraw 方法先被调用啊,为什么从上面的demo日志看出最终Runnable 接口等ViewonMeasureonLayoutonDraw 方法调用完之后再调用?
是因为注释10 的代码先比注释19 的代码先被调用,注释10 表示开启了同步消息屏障,Android中它有一个异步消息优先级比较高的权利,保障 View 绘制完后再给其他消息执行,所以在 View.post方法中的Runnable 接口的回调能直接获取View 的宽。

ActivityonResume 方法是在ActivitymakeVisible方法先被调用的,而ViewpostRunnable 接口是在View 绘制完才会被回调的,所以ActivityonResume 方法先比 View.post 方法中Runnable 接口先被调用。
详细请看对Android中View的post方法进行探索

12.3 IntentService

处理完 service 自动停止 内存释放

12.4 Fragment 生命周期管理

attach -> commit
Glide生命周期管理 RequestManagerFragment 双重检查(避免连续两次with()重复创建Fragment,因为commit会发到Handle消息队列的)

Handler是贯穿于Android的消息管理机制

所有的代码都是在Handler上运行的(loop()死循环)

转:Android | 面试必问的 Handler,你确定不看看?

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

推荐阅读更多精彩内容