编程进阶网编程进阶网
  • 基础组成体系
  • 程序编程原理
  • 异常和IO系统
  • 六大设计原则
  • 设计模式导读
  • 创建型设计模式
  • 结构型设计模式
  • 行为型设计模式
  • 设计模式案例
  • 面向对象思想
  • 基础入门
  • 高级进阶
  • JVM虚拟机
  • 数据集合
  • Java面试题
  • C语言入门
  • C综合案例
  • C标准库
  • C语言专栏
  • C++入门
  • C++综合案例
  • C++专栏
  • HTML
  • CSS
  • JavaScript
  • 前端专栏
  • Swift
  • iOS入门
  • 基础入门
  • 开源库解读
  • 性能优化
  • Framework
  • 方案设计
  • 媒体音视频
  • 硬件开发
  • Groovy
  • 常用工具
  • 大厂面试题
  • 综合案例
  • 网络底层
  • Https
  • 网络请求
  • 故障排查
  • 专栏
  • 数组
  • 链表
  • 栈
  • 队列
  • 树
  • 递归
  • 哈希
  • 排序
  • 查找
  • 字符串
  • 其他
  • Bash脚本
  • Linux入门
  • 嵌入式开发
  • 代码规范
  • Markdown
  • 开发理论
  • 开发工具
  • Git管理
  • 百宝箱
  • 开源协议
  • 技术招聘
  • 测试经验
  • 职场提升
  • 技术模版
  • 关于我
  • 目标清单
  • 学习框架
  • 育儿经验
  • 我的专栏
  • 底层能力
  • 读书心得
  • 随笔笔记
  • 职场思考
  • 中华历史
  • 经济学故事
  • 基础组成体系
  • 程序编程原理
  • 异常和IO系统
  • 六大设计原则
  • 设计模式导读
  • 创建型设计模式
  • 结构型设计模式
  • 行为型设计模式
  • 设计模式案例
  • 面向对象思想
  • 基础入门
  • 高级进阶
  • JVM虚拟机
  • 数据集合
  • Java面试题
  • C语言入门
  • C综合案例
  • C标准库
  • C语言专栏
  • C++入门
  • C++综合案例
  • C++专栏
  • HTML
  • CSS
  • JavaScript
  • 前端专栏
  • Swift
  • iOS入门
  • 基础入门
  • 开源库解读
  • 性能优化
  • Framework
  • 方案设计
  • 媒体音视频
  • 硬件开发
  • Groovy
  • 常用工具
  • 大厂面试题
  • 综合案例
  • 网络底层
  • Https
  • 网络请求
  • 故障排查
  • 专栏
  • 数组
  • 链表
  • 栈
  • 队列
  • 树
  • 递归
  • 哈希
  • 排序
  • 查找
  • 字符串
  • 其他
  • Bash脚本
  • Linux入门
  • 嵌入式开发
  • 代码规范
  • Markdown
  • 开发理论
  • 开发工具
  • Git管理
  • 百宝箱
  • 开源协议
  • 技术招聘
  • 测试经验
  • 职场提升
  • 技术模版
  • 关于我
  • 目标清单
  • 学习框架
  • 育儿经验
  • 我的专栏
  • 底层能力
  • 读书心得
  • 随笔笔记
  • 职场思考
  • 中华历史
  • 经济学故事
  • 02.Android虚拟机知识
  • 03.App核心概念分析
  • 04.Android消息机制
  • 05.Android程序UI框架
  • 06.Android应用窗口
  • 07.WMS机制深入分析
  • 08.PMS机制深入分析
  • 09.AMS机制深入分析
  • 11.四大组件原理分析
  • 12.四大组件原理分析
  • 13.View绘制流程分析
  • 14.View渲染流程分析
  • 16.Android绘制原理
  • 17.Android事件传输
  • 18.Android事件拦截
  • 21.View自定义控件反思
  • 22.ViewGroup自定义控件
  • 25.IPC跨进程通信实践
  • 29.Binder机制的理解

04.Android消息机制

目录介绍

  • 01.Handler必备知识点
    • 1.1 消息机制的背景
    • 1.2 Handler消息类型
    • 1.3 Handler四要素
  • 03.子线程的消息处理
    • 3.1 子线程中定义Handler
    • 3.2 不建议在子线程访问UI
    • 3.3 子线程更新UI方式
    • 3.9 解决handler内存泄露
  • 04.消息机制源码分析
    • 4.1 Handler源码分析
    • 4.2 Message源码分析
    • 4.3 MessageQueue源码
    • 4.4 Looper源码分析
  • 05.消息机制设计思想
    • 5.1 为何要设计消息机制
    • 5.2 消息机制核心思想
  • 06.Handler同步屏障
    • 6.1 什么是同步屏障
  • 07.消息机制问题和思考
    • 7.1 发送消息Delay可靠吗
    • 7.2 Looper停止App就退出吗

00.问题答疑思考

  • 请说一下Android消息机制?子线程中是否可以new一个handler对象?会出现什么问题,为什么?如何提高Message的优先级?
  • 阻塞唤醒原理。这里一般是会问为何loop()方法是死循环却不会占用cpu时间片 or 为何next()方法阻塞却不会卡死。更深一点会问到Linux的IO多路复用epoll原理。
  • Looper从消息队列中取出消息后,如何传递给handler?消息队列MessageQuee底层怎么实现的?handler里面的nativePollOnce 为什么不会anr?
  • Handler、Thread和HandlerThread的差别 ?HandlerThread是怎么实现的?
  • Looper.loop()为什么不会阻塞主线程,IdleHandler(闲时机制)是做什么用的?
  • Handler机制,IdleHandler执行时机。Handler#postDelay(runnable, 20s) 一个消息,然后把手机时间调整为1分钟后,刚才的runnable会不会执行。
  • ThreadLocal是如何做到线程间的不共享数据的,ThreadLocalMap里面的key和value是什么。实现及如何保证Local属性?
  • 说下handler的流程,异步消息是什么?Android中哪些场景会发送异步消息?我们在代码中可以手动发异步消息吗?
  • 什么是消息同步屏障?Android中是如何实现消息同步屏障的?同步屏障的目的主要是为了什么?
  • Choreographer是什么?Choreographer发出异步消息后如何执行任务?

01.Handler必备知识点

1.1 消息机制的背景

  • Android应用程序与传统的PC应用程序一样,都是消息驱动的。
    • 也就是说,在Android应用程序主线程中,所有函数都是在一个消息循环中执行的。
  • Android应用程序其它线程,也可以像主线程一样,拥有消息循环。
    • Android应用程序主线程是一个特殊的线程,因为它同时也是UI线程以及触摸屏、键盘等输入事件处理线程。
  • 主要搞清楚那些知识点:
    • 线程与消息的关系;线程的消息队列创建;线程的消息循环;线程的消息发送;线程的消息处理;消息在异步任务的应用

1.2 Handler消息类型

  • Handler的Message种类分为三种:1.普通消息;2.异步消息;3.屏障消息
  • 普通消息
    • 普通消息又称为同步消息,我们平时发的消息基本都是同步消息。
  • 异步消息
    • Handler的构造方法有个async参数,默认的构造方法此参数是false,只要我们在构造handler对象的时候,把该参数设置为true就可以。
    • 在创建Message对象时,直接调用Message的setAsynchronous()方法。
  • 普通消息和异步消息区别
    • 在一般情况下,异步消息和同步消息没有什么区别,但是一旦开启了同步屏障以后就有区别了。

1.3 Handler四要素

  • 作用:
    • 跨线程通信。当子线程中进行耗时操作后需要更新UI时,通过Handler将有关UI的操作切换到主线程中执行。
  • 四要素:
    • Message(消息):需要被传递的消息,其中包含了消息ID,消息处理对象以及处理的数据等,由MessageQueue统一列队,最终由Handler处理。
    • MessageQueue(消息队列):用来存放Handler发送过来的消息,通过 Handler 发送的消息并非是立即执行的,需要存入一个消息队列中来依次执行,内部通过单链表的数据结构来维护消息列表,等待Looper的抽取。
    • Handler(处理者):负责Message的发送及处理。通过 Handler.sendMessage() 向消息池发送各种消息事件;通过 Handler.handleMessage() 处理相应的消息事件。
    • Looper(消息泵):通过Looper.loop()不断地从MessageQueue中抽取Message,按分发机制将消息分发给目标处理者。Looper 不断从 MessageQueue 中获取消息并将之传递给消息处理者(即是消息发送者 Handler 本身)进行处理。
  • 具体流程
    • Handler.sendMessage()发送消息时,会通过MessageQueue.enqueueMessage()向MessageQueue中添加一条消息;
    • 通过Looper.loop()开启循环后,不断轮询调用MessageQueue.next();
    • 调用目标Handler.dispatchMessage()去传递消息,目标Handler收到消息后调用Handler.handlerMessage()处理消息。
    • image
      image

03.子线程的消息处理

3.1 子线程中定义Handler

  • 直接在子线程中创建handler,看看会出现什么情况?
    • 运行后可以得出在子线程中定义Handler对象出错,难道Handler对象的定义或者是初始化只能在主线程中?
    • 其实不是这样的,错误信息中提示的已经很明显了,在初始化Handler对象之前需要调用Looper.prepare()方法。
  • 为何会报错?
    • Handler的工作是依赖于Looper的,而Looper(与消息队列)又是属于某一个线程(ThreadLocal是线程内部的数据存储类,通过它可以在指定线程中存储数据,其他线程则无法获取到),其他线程不能访问。
    • Handler就是间接跟线程是绑定在一起了。因此要使用Handler必须要保证Handler所创建的线程中有Looper对象并且启动循环。因为子线程中默认是没有Looper的,所以会报错。
  • 如何正确运行。在这里问一个问题,在子线程中可以吐司吗?
    • 答案是可以的,只不过又条件。需要在子线程中调用Looper.prepare()。
    • 在子线程中,如果手动为其创建Looper,那么在所有的事情完成以后应该调用quit方法来终止消息循环,否则这个子线程就会一直处于等待的状态,而如果退出Looper以后,这个线程就会立刻终止,因此建议不需要的时候终止Looper。(Looper.myLooper().quit()😉

3.2 不建议在子线程访问UI

  • 这是因为Android的UI控件不是线程安全的,如果在多线程中并发访问可能会导致UI控件处于不可预期的状态,那么为什么系统不对UI控件的访问加上锁机制呢?缺点有两个:
    • ①首先加上锁机制会让UI访问的逻辑变得复杂
    • ②锁机制会降低UI访问的效率,因为锁机制会阻塞某些线程的执行。
    • 所以最简单且高效的方法就是采用单线程模型来处理UI操作。
  • 为什么说子线程不能更新UI?
    • 子线程是不能直接更新UI的。Android实现View更新有两组方法,分别是invalidate和postInvalidate。
    • 前者在UI线程中使用,后者在非UI线程即子线程中使用。换句话说,在子线程调用 invalidate 方法会导致线程不安全。
    • 熟悉View工作原理的人都知道,invalidate 方法会通知 view 立即重绘,刷新界面。
    • 作一个假设,现在用 invalidate 在子线程中刷新界面,同时UI线程也在用 invalidate 刷新界面,这样会不会导致界面的刷新不能同步?这就是invalidate不能在子线程中使用的原因。

3.3 子线程更新UI方式

  • 主要有这些方法
    • 第一种:主线程中定义Handler,子线程通过mHandler发送消息,主线程Handler的handleMessage更新UI
    • 第二种:用Activity对象的runOnUiThread方法
    • 第三种:创建Handler,传入getMainLooper,使用handler.post更新UI
    • 第四种:View.post(Runnable r)
  • 相似处分析
    • 跟进去看源码,发现其实它们的实现原理都还是一样,最终都是通过Handler发送消息来实现的。

3.9 解决handler内存泄露

  • 为何会内存泄漏
    • 通过内部类的方式创建mHandler对象,此时mHandler会隐式地持有一个外部类对象引用这里就是Activity,当执行post/send方法时,该方法会将你的Handler装入一个Message,并把这条Message推到MessageQueue中,MessageQueue是在一个Looper线程中不断轮询处理消息。
    • 那么当这个Activity退出时消息队列中还有未处理的消息或者正在处理消息,而消息队列中的Message持有mHandler实例的引用,mHandler又持有Activity的引用,所以导致该Activity的内存资源无法及时回收,引发内存泄漏。
  • 解决Handler内存泄露主要2点
    • 有延时消息,要在Activity销毁的时候移除Messages。需在onDestroy()函数中调用mHandler.removeCallbacksAndMessages(null)
    • 匿名内部类导致的泄露改为匿名静态内部类,并且对上下文或者Activity使用弱引用。注意:每次使用前注意判空。

04.消息机制源码分析

4.1 消息机制流程图

  • 整个消息机制的流程图
    • image
      image

4.1 Handler源码分析

  • Handler构造方法源码
    • 如果构造函数没有传入 Looper 参数,则会默认使用当前线程关联的 Looper 对象,mQueue 需要依赖于从 Looper 对象中获取。
    • 如果 Looper 对象为 null ,则会直接抛出异常,注意这里是在ActivityThread的main方法中调用 Looper.prepare()。最后可以看到默认是创造同步消息。
  • Handler发送消息源码【send/post】

    Handler#sendMessage,sendEmptyMessage,sendEmptyMessageDelayed,post,postAtTime等,各种发送消息方法 Handler#sendMessageAtTime(),发送消息的形式有多种形式,其最终调用的都是 sendMessageAtTime() 方法。 这需要一个已初始化的MessageQueue类型的全局变量mQueue,否则程序无法继续走下去。 mQueue 变量是在构造函数中进行初始化的,且 mQueue 是成员常量,这说明 Handler 与 MessageQueue 是一一对应的关系,不可更改 Handler#enqueueMessage(),在这个方法中对message附值target是当前handler对象,然后再调用到enqueueMessage分发消息 可以看到在这个方法中默认是同步消息,因为mAsynchronous默认是false。 需要注意 msg.target = this 这句代码,target 对象指向了发送消息的主体,即 Handler 对象本身。 即由 Handler 对象发给 MessageQueue 的消息最后还是要交由 Handler 对象本身来处理。

  • Handler处理消息源码【loop轮训中取消息处理】

    Handler#dispatchMessage(),分发处理handler消息 如果 msg.callback 不为 null ,则调用 callback 对象的 run() 方法,该 callback 实际上就是一个 Runnable 对象,对应的是 Handler 对象的 post() 方法 如果 msg.callback 为 null,由于mCallback默认为空,则会调用handleMessage方法处理消息。 Handler#handleMessage(),这里是一个空实现。外部开发自己去实现该方法处理消息

  • 使用 postDelayed 后消息队列会发生什么变化?
    • post delay的Message并不是先等待一定时间再放入到MessageQueue中,而是直接进入并阻塞当前线程,然后将其delay的时间和队头的进行比较,按照触发时间进行排序,如果触发时间更近则放入队头,保证队头的时间最小、队尾的时间最大。
    • 此时,如果队头的Message正是被delay的,则将当前线程堵塞一段时间,直到等待足够时间再唤醒执行该Message,否则唤醒后直接执行。

4.2 Message源码分析

  • Message的作用
    • 大概意思是,Message类被用于发送给Handler,其可以存储一些描述信息和任意数据类型的对象。
  • 如何处理Message创建和销毁
    • 此时我们不禁会抱有一个疑问,在应用运行期间,系统岂不是会不断地创建Message、处理Message、销毁Message?
    • Android的消息机制并不会大量重复创建Message对象,而是重复利用在消息池中已存在的Message对象。
  • 看一下消息体Message创建流程

    Message#obtain(),如果消息链中没有消息时,就会new一个新的Message对象并返回;如果消息链中有可用对象,就会获取到当前链表的mPool,而后将mPool指向下一个Message对象。 Looper#loop()#msg.recycleUnchecked(),这个每一条Message被处理后最终都会调用Message.recycleUnchecked()方法 Message#recycleUnchecked(),这个主要是做消息重制状态的操作。 recycleUnchecked()方法有两个作用:首先,清空消息状态,同时设置flags = FLAG_IN_USE,表明该消息已被使用,这个flags在obtain()方法中会被设置为0; 然后,如果消息池的大小未超过最大容纳数量,就将自身添加到链表的表头。也就是说,Message并不是在创建时而是在回收时被添加到消息池中的。

4.3 MessageQueue源码

  • MessageQueue,主要包含2个操作:插入和读取。
    • 读取操作会伴随着删除操作,插入和读取对应的方法分别为enqueueMessage和next,其中enqueueMessage的作用是往消息队列中插入一条消息,而next的作用是从消息队列中取出一条消息并将其从消息队列中移除。
  • MessageQueue创建源码

    Handler#(),在handler构造函数中,拿到Looper对象,然后通过Looper对象去获取mQueue Looper#(),在Looper私有构造函数中,通过new去创建一个mQueue消息队列对象。这说明 Looper 与 MessageQueue 是一一对应的关系

  • MessageQueue插入消息【Handler发送消息】

    Handler#sendMessageAtTime(),通过handler发送消息,最终会调用queue.enqueueMessage(msg, uptimeMillis),将消息插入到消息队列中。 MessageQueue#enqueueMessage(),handler发送消息最终会执行到这里,传递了一个消息体message,还有一个消息延迟时间 if (msg.target == null),如果msg的target为空(target是指对应的handler),则会直接抛出异常; 如果 mMessages 为空,说明消息队列是空的,或者 mMessages 的触发时间要比新消息晚,则将新消息插入消息队列的头部; 如果 mMessages 不为空,则寻找消息列队中第一条触发时间比新消息晚的非空消息,并将新消息插到该消息前面。按照时间将所有的Message排序; 到此,一个按照处理时间进行排序的消息队列就完成了,后边要做的就是从消息队列中依次取出消息进行处理 因为存在多个线程往同一个 Loop 线程的 MessageQueue 中插入消息的可能,所以 enqueueMessage() 内部需要进行同步。

  • MessageQueue取出消息【Looper轮训处理消息】

    Looper#loop(),这里可以知道Looper轮训器不断轮训处理消息,这里会调用到mQueue.next()取出即将要处理的message MessageQueue#next(),next() 方法是一个无限循环的方法,如果消息队列中没有消息,则该方法会一直阻塞,当有新消息来的时候 next() 方法会返回这条消息并将其从单链表中删除。

  • 如何理解延迟消息
    • 这个所谓的延时呢,不是延时发送消息,而是延时去处理消息,看MessageQueue#enqueueMessage()源码,我们在发消息都是马上插入到消息队列当中。
    • MessageQueue是按照Message触发时间的先后顺序排列的【也就是说会改变链表的顺序】,队头的消息是将要最早触发的消息。

4.4 Looper源码分析

  • 定位到 ActivityThread 类的 main() 方法
    • 看到 main() 函数的方法签名,可以知道该方法就是一个应用的起始点,即当应用启动时,系统就自动在主线程做好了Looper的初始化操作。
  • Looper初始化源码分析【ActivityThread类main入口】

    Looper#prepareMainLooper(),这个方法主要是初始化当前线程的Looper工作 Looper#prepare(false),sThreadLocal 对象又是通过 prepare(boolean) 来进行赋值的,且该方法只允许调用一次,一个线程只能创建一个 Looper 对象,否则将抛出异常。 sThreadLocal.set(new Looper(quitAllowed)) 当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。 Looper#Looper(false),可以看到在Looper这个构造方法中,创建了一个MessageQueue消息队列 Looper 类的构造函数也是私有的,且在构造函数中还初始化了一个线程常量 mThread,这都说明了 Looper 只能关联到一个线程,且关联之后不能改变。 mQueue 又是在 Looper 类的构造函数中初始化的,且 mQueue 是 Looper 类的成员常量,这说明 Looper 与 MessageQueue 是一一对应的关系。

  • Looper轮训源码分析【loop方法入口】

    Looper#loop(),这个方法开启了一些消息循环。不断的判断MessageQueue中的消息是否为空,如果为空则直接return掉,然后执行queue.next()方法。

  • Looper死循环为何不阻塞应用卡死
    • 这里就涉及到Linux pipe/epoll机制,简单说就是在主线程的MessageQueue没有消息时,便阻塞在loop的queue.next()中的nativePollOnce()方法里,此时主线程会释放CPU资源进入休眠状态,直到下个消息到达或者有事务发生,通过往pipe管道写端写入数据来唤醒主线程工作。
    • 这里采用的epoll机制,是一种IO多路复用机制,可以同时监控多个描述符,当某个描述符就绪(读或写就绪),则立刻通知相应程序进行读或写操作,本质同步I/O,即读写是阻塞的。
    • 所以说,主线程大多数时候都是处于休眠状态,并不会消耗大量CPU资源。

05.消息机制设计思想

5.1 为何要设计消息机制

5.2 消息机制核心思想

  • 不管在哪,谈到消息机制,都会有这三个要素:
    • 消息队列
    • 消息循环(分发)
    • 消息处理
  • 消息队列 ,是 消息对象 的队列,基本规则是 FIFO。
    • 消息循环(分发),基本是通用的机制,利用死循环不断的取出消息队列头部的消息,派发执行
  • 消息处理,这里不得不提到 消息 有两种形式:
    • Enrichment 自身信息完备
    • Query-Back 自身信息不完备,需要回查
    • 这两者的取舍,主要看系统中 生成消息的开销 和 回查信息的开销 两者的博弈。

06.Handler同步屏障

6.1 什么是同步屏障

  • 什么场景用到同步屏障
    • 在系统源码中就有使用。Android系统中的UI更新相关的消息即为异步消息,需要优先处理。那么handler机制中如何优先消息了,这个就需要了解同步屏障。
  • 屏幕刷新机制
    • 16ms左右刷新UI,而是60hz的屏幕,即1s刷新60次。屏幕会在每次刷新的时候发出一个 VSYNC 信号,通知CPU进行绘制计算。具体到我们的代码中,可以认为就是执行onMeasure()、onLayout()、onDraw()这些方法。
  • 消息机制的同步屏障
    • 同步屏障消息就是在消息队列中插入一个屏障,在屏障之后的所有普通消息都会被挡着,不能被处理。
    • 不过异步消息却例外,屏障不会挡住异步消息,因此可以认为,屏障消息就是为了确保异步消息的优先级,设置了屏障后,只能处理其后的异步消息,同步消息会被挡住,除非撤销屏障。
  • 一句话概括
    • 其实就是阻碍同步消息,只让异步消息通过。

07.消息机制问题和思考

7.1 发送消息Delay可靠吗

  • 不靠谱的,引起不靠谱的原因有如下
    • 发送的消息太多,Looper负载越高,任务越容易积压,进而导致卡顿
    • 消息队列有一些消息处理非常耗时,导致后面的消息延时处理
    • 大于Handler Looper的周期时基本可靠(例如主线程>50ms)
    • 对于时间精确度要求较高,不要用handler的delay作为即时的依据
  • 如何优化保证可靠性
    • 消息精简,从数量上处理
    • 队列优化,重复消息过滤
    • 互斥消息取消
    • 复用消息
  • 消息空闲IdleHandler
    MessageQueue.IdleHandler ideHandler =new MessageQueue.IdleHandler() {
            @Override
            public boolean queueIdle() {
                return false;
            }
        };
    Looper.myQueue().addIdleHandler(ideHandler);
  • 使用独享的Looper(HandlerThread)
    HandlerThread handlerThread = new HandlerThread("A-Thread");
    handlerThread.start();
    Handler handler = new Handler(handlerThread.getLooper());

7.2 Looper停止App就退出吗

  • looper如果停止了,那么app会退出吗,先做个实验看一下。代码如下所示
    • 可以发现调用这句话,是会让app退出的。会报错崩溃日志是:java.lang.IllegalStateException: Main thread not allowed to quit.
    • 第一种:Looper.getMainLooper().quit();
    • 第二种:Looper.getMainLooper().quitSafely();
  • 崩溃分析:二者最终都调用了MessageQueue中的quit方法,MessageQueue的quit方法源码如下:
    • Looper在ActivityThread初始化时,调用prepare()-->new Looper(false)--->new MessageQueue(false)--->mQuitAllowed设置为false。这个时候直接推出执行到崩溃。
  • 通过观察MessageQueue#quit()源码我们可以发现:
    • 当我们调用Looper的quit方法时,实际上执行了MessageQueue中的removeAllMessagesLocked方法,该方法的作用是把MessageQueue消息池中所有的消息全部清空,无论是延迟消息(延迟消息是指通过sendMessageDelayed或通过postDelayed等方法发送的需要延迟执行的消息)还是非延迟消息。
    • 当我们调用Looper的quitSafely方法时,实际上执行了MessageQueue中的removeAllFutureMessagesLocked方法,通过名字就可以看出,该方法只会清空MessageQueue消息池中所有的延迟消息,并将消息池中所有的非延迟消息派发出去让Handler去处理,quitSafely相比于quit方法安全之处在于清空消息之前会派发所有的非延迟消息。
    • 无论是调用了quit方法还是quitSafely方法只会,Looper就不再接收新的消息。即在调用了Looper的quit或quitSafely方法之后,消息循环就终结了,这时候再通过Handler调用sendMessage或post等方法发送消息时均返回false,表示消息没有成功放入消息队列MessageQueue中,因为消息队列已经退出了。
贡献者: yangchong211
上一篇
03.App核心概念分析
下一篇
05.Android程序UI框架