| |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
-> 移动开发 -> 【面试官爸爸】继续给我讲讲Handler? -> 正文阅读 |
|
[移动开发]【面试官爸爸】继续给我讲讲Handler? |
熟悉的黑影一团黑影缓缓向我逼近 他走路不紧不慢,每一步都坚定而沉稳,像沉重的鼓槌一下一下敲在我的心上。稀疏的头顶上闪耀着高 P 的光芒,锐利的眼神仿佛一下看穿了我的心虚... 少倾,他坐在我对面
什么是 Handler?Handler 是 Android 的一种消息处理机制,与 Looper,MessageQueue 绑定,可以用来进行线程的切换。常用于接收子线程发送的数据并在主线程中更新 UI Handler 线程通信的原理
“切换线程”其实是“线程通信”的一种。为了保证主线程不被阻塞,我们常常需要在子线程执行一些耗时任务,执行完毕后通知主线程作出相应的反应,这个过程就是线程间通信。 Linux 里有一种进程间通信的方式叫消息队列,简单来说当两个进程想要通信时,一个进程将消息放入队列中,另一个进程从这个队列中读取消息,从而实现两个进程的通信。 Handler 就是基于这一设计而实现的。在 Android 的多线程中,每个线程都有一个自己的消息队列,线程可以开启一个死循环不断地从队列中读取消息。 当 B 线程要和 A 线程通信时,只需要往 A 的消息队列中发送消息,A 的事件循环就会读取这一消息从而实现线程间通信
Android 的事件循环和消息队列是通过 Looper 类来实现的 Looper.prepare() 是一个静态方法。它会构建出一个 Looper,同时创建一个 MessageQueue 作为 Looper 的成员变量。MessageQueue 是存放消息的队列 当调用 Looper.loop() 方法时,会在线程内部开启一个死循环,不断地从 MessageQueue 中读取消息,这就是事件循环 每个 Handler 都与一个 Looper 绑定,Looper 包含 MessageQueue
Looper 是存放在线程中的。但如何把 Looper 存放在线程中就引入了 Android 消息机制的另一个重点 --- ThreadLocal 前面我们提到。Looper.prepare() 方法会创建出一个 Looper,它其实还做了一件事,就是将 Looper 放入线程的局部变量 ThreadLocal 中。
那么问题来了,什么是 ThreadLocal 呢?ThreadLocal 又称线程的局部变量。它最大的神奇之处在于,一个 ThreadLocal 实例在不同的线程中调用 get 方法可以取出不同的值。 用一个例子来表示这种用法:
ThreadLocal 的核心是 set 方法,它的作用总结成一句话就是: ThreadLocal.set 可以将一个实例变成线程的成员变量 看一下源码
方法很简单,就是根据当前线程获取线程的一个 map 对象,然后把 value 放入 map 中,达到将 value 变成线程的成员变量的目的 多个 Theadlocal 将多个变量变成线程的成员变量。于是线程就用 ThreadlLocalMap 来管理,key 就是 threadLocal 知道了它 set 方法的奥秘,get 方法也就很简单啦
和 set 方法差不多,区别就是一个将 value 写入 map,一个从 map 中读取 value。哼哼
因为 Looper 要放在线程中的,每个线程只需要一个事件循环,只需要一个 Looper。事件循环是个死循环,多余的事件循环毫无意义。ThreadLocal.set 可以将 Looper 设置为线程的成员变量 同时为了方便在不同线程中获取到 Looper,Android 提供了一个静态对象 Looper.sThreadLocal。这样在线程内部调用 sThreadLocal.get 就可以获取线程对应的 Looper 对象 综上所述,使用 ThreadLocal 作为 Looper 的设置和获取工具是十分方便合理哒
当然不会!如果事件循环中没有消息要处理但仍然执行循环,相当于无意义的浪费 CPU 资源!Android 是不允许这样的 为了解决这个问题,在 MessageQueue 中,有两个 native 方法, nativePollOnce 表示进行一次轮询,来查找是否有可以处理的消息,如果没有就阻塞线程,让出 CPU 资源 nativeWake 表示唤醒线程 所以这两个方法的调用时机也就显而易见了
在 MessageQueue 类中,
首先要明确一下概念。ANR 是应用在特定时间内无法响应一个事件时抛出的异常。 典型例子的是在主线程中执行耗时任务。当一个触摸事件来临时,主线程忙于处理耗时任务而无法在 5s 内响应触摸事件,此时就会抛出 ANR。 但 Looper 死循环是事件循环的基石,本身就是 Android 用来处理一个个事件的。正常情况下,触摸事件会加入到这个循环中被处理。但如果前一个事件太过耗时,下一个事件等待时间太长超出特定时间,这时才会产生 ANR。所以 Looper 死循环并不是产生 ANR 的原因。 消息队列
这个就要看 MessageQueue 的 enqueueMessage 方法了 enqueueMessage 是消息的入队方法。Handler 在进行线程间通信时,会调用 sendMessage 将消息发送到接收消息的线程的消息队列中,消息队列调用 enqueueMessage 将消息入队。
消息入队分为 3 步: ① 将入队的时间绑定在 when 属性上 ② 遍历链表,通过比较 when 找到插入位置 ③ 将 msg 插入到链表中 这就是消息的排序方式
根据上个问题,最容易想到的是修改 Message 的 when 属性。这确实不失为一种方法,但 Android 为我们提供了更科学简单的方式,异步消息和同步屏障。 在 Android 的消息机制中,消息分为同步消息、异步消息和同步屏障三种。(没错,同步屏障是 target 属性为 null 的特殊消息)。通常我们调用 sendMessage 方法发送的是同步消息。异步消息需要和同步屏障配合使用,来提升消息的优先级。 同步屏障理解起来其实很简单。刚才说同步屏障是一种特殊的消息,当事件循环检测到同步屏障时,之后的行为不再像之前那样根据 when 的值一个个取消息,而是遍历整个消息队列,查找到异步消息取出并执行。 这个特殊的消息在消息队列中像一个标志,事件循环探测到它时就改变原来的行为,转而去查找异步消息。表现上看起来像一个屏障一样拦住了同步消息。所以形象地称为同步屏障。 源码实现非常非常简单:
同步屏障是用来“拦住”同步消息,处理异步消息的。如果同步屏障不移除,消息队列里的异步消息会一个一个被取出处理,知道异步消息被取完。如果此时队列中没有异步消息了,则线程会阻塞,队列中的同步消息永远不会执行。所以同步屏障要及时移除。
同步屏障的核心作用是提高消息优先级,保证 Message 被优先处理。Android 为了避免卡顿,应用在了 view 绘制中。具体可以看之前关于 view 绘制的总结~
内存泄漏归根到底其实是生命周期“错位”导致的:一个对象本来应该在一个短的生命周期中被回收,结果被一个长生命周期的对象引用,导致无法回收。 Handler 的内存泄漏其实是内部类持有外部类引用导致的。 形成方式有两种: (1)匿名内部类持有外部类引用
Handler 在发送消息时,message.target 属性就是 handler 本身。message 被发送到消息队列中,被线程持有,线程是一个无比“长”生命周期的对象,导致 activity 无法被及时回收从而引起内存泄漏。 解决办法是在 activity destory 时及时移除 runnable (2)非静态内部类持有外部类引用
解决方案是用静态内部类,并将外部引用改为弱引用
HandlerThread 顾名思义就是 Handler+Thread 的结合体,它本质上是一个 Thread。 我们知道,子线程是需要我们通过 Looper.prepare()和 Looper.loop()手动开启事件循环的。HandlerThread 其实就帮我们做了这件事,它是一个实现了事件循环的线程。我们可以在这个线程中做一些 IO 耗时操作。 IdleHandler 虽然叫 Handler,其实和同步屏障一样是一种特殊的”消息"。不同于 Message,它是一个接口
Idle 是空闲的意思。与同步屏障不同,同步屏障是提高异步消息的优先级使其优先执行,IdleHandler 是事件循环出现空闲的时候来执行。 这里的“空闲”主要指两种情况 (1)消息队列为空 (2)消息队列不为空但全部是延时消息,也就是 msg.when > now 利用这一特性,我们可以将一些不重要的初始化操作放在 IdleHandler 中执行,以此加快 app 启动速度;由于 View 的绘制是事件驱动的,我们也可以在主线程的事件循环中添加一个 IdleHandler 来作为 View 绘制完成的回调,等等。 但应该注意的是,如果主线程中一直有任务执行,IdleHandler 被执行的时机会无限延后,使用的时候要注意哦~ 为了帮助兄弟们以后更好的复习,我帮大家整理了 Handler 面试知识点的脑图。复习的时候配合脑图食用,分分钟掌握 Handler 基础知识。如果需要其他知识点脑图,欢迎关注我的公众号领取~ 结尾本篇是【面试官爸爸】系列第三篇,后续我还会继续更新这个系列,包括面试最常考的 Activity 启动,编译打包流程及优化,Java 基础,设计模式,组件化等面试常问的问题。如果不想错过,欢迎点赞,收藏,关注我!球球兄弟萌辣,这个对我真的很重要!!
|
|
移动开发 最新文章 |
Vue3装载axios和element-ui |
android adb cmd |
【xcode】Xcode常用快捷键与技巧 |
Android开发中的线程池使用 |
Java 和 Android 的 Base64 |
Android 测试文字编码格式 |
微信小程序支付 |
安卓权限记录 |
知乎之自动养号 |
【Android Jetpack】DataStore |
|
上一篇文章 下一篇文章 查看所有文章 |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 | -2024/11/23 10:37:05- |
|
网站联系: qq:121756557 email:121756557@qq.com IT数码 |