并发编程 · 基础篇(中) · 三大分析法分析 Handler

小木箱成长营并发编程系列教程(排期中):

并发编程 · 基础篇(上) · android 线程那些事

并发编程 · 基础篇(下) · android 线程池那些事

并发编程 · 提高篇(上) · Java 并发关键字那些事

并发编程 · 提高篇(下) · Java 锁安全性那些事

并发编程 · 高级篇(上) · Java 内存模型那些事

并发编程 · 高级篇(下) · Java 并发 BATJ 面试之谈

并发编程 · 实战篇 · android 下载器实现

Tips: 关注微信公众号小木箱成长营,回复 Handler 可获得 Handler 免费思维导图

一、序言

Hello,我是小木箱,欢迎来到小木箱成长营并发编程系列教程,今天将分享并发编程 · 基础篇(中) · 三大分析法分析 Handler

三大分析法分析 Handler 主要分为三部分,第一部分是 5W2H 分析 Handler,第二部分是 MECE 分析 Handler,第三部分是 SCQA 分析 Handler。

首先,5W2H 分析 Handler 针对 Handler 提出了六个高价值问题。

然后,MECE 分析 Handler 分为两部分,第一部分是 Handler 常见 API,第二部分是 Handler 消息机制。

最后,SCQA 分析 Handler,还原企业面试现场,利用B 站平台,以 SCQA 形式解答 33 个 Handler 高频面试题。

其中,Handler 消息机制主要分为三部分,第一部分是 Handler 发送 Message,第二部分是 Looper 轮询读取,第三部分是 Handler 回收 Message。

image.png

如果学完小木箱的三大分析法分析 Handler,那么任何人都能通过 android Handler 相关技术面试。

二、5W2H 分析 Handler

5W2H 又叫七何分析法,5W2H 是二战的时候,美国陆军兵器修理部发明的思维方法论,便于启发性的理解深水区知识。

5W2H 是 What、Who、Why、Where、When、How much、How 首字母缩写之和,广泛用于企业技术管理、头脑风暴等。

今天小木箱尝试用 5W2H 分析法分析 Handler。

2.1 What: 怎么定义 Handler?

Android Handler is mainly used to update the main thread from background thread or other than main thread.

Google 官方文档是这么定义 Handler 的: 在 android 中,Handler 主要用于从后台线程向主线程同步 Message。

总结性的说: android 为了更好进行线程通讯,Handler 基于双向链表数据结构的 "优先级消息队列",提供了一套线程通讯机制。

2.2 Why: 为什么用 Handler?

为什么会有 Handler 存在呢?在 Android 中,线程分为两种,第一种是主线程,主线程主要用来创建和更新 UI 用途。

第二种是后台线程,后台线程主要用于处理耗时的操作,网络请求、文件读写和音视频播放等后台工作。

后台线程有效地提高应用程序性能,保证 UI 流程度。

Android 硬件规定手机每隔 16ms 会刷新一次,Android 为了不丢帧,主线程耗时不能超过 16ms。

因此,Android 不允许后台线程更新 UI。

为了将 Message 从一个线程传递到另一个线程,实现线程间的通信,Handler 承担了线程切换的职责。

除此之外,Handler 还可以发送 Message 实现定时任务。

2.3 When: 何时使用 Handler?

如果需要执行一个任务在指定的时间间隔或在特定的线程中执行任务时,那么我们考虑使用 Handler 机制。

比如: 领导要你实现一个 Android 消息轮询库。

2.4 Where: Android 框架使用 Handler 的地方?

在 Android 框架中,HandlerThread 、IntentService 、EventBus 、RxJava、 DataBinding 和 Dagger2 都是基于 Handler 实现的。

2.5 How : 怎样使用 Handler?

Handler 使用非常简单,创建了一个 Handler 实例并重写了 handleMessage 方法 ,然后在适当的时机调用 Handler 的 send 或者 post 系列方法就可以了

并发编程 · 基础篇(中) · 三大分析法分析 Handler_第1张图片

2.6 How Much: Handler 真的完美吗?

Handler 有两个缺陷,第一个是不支持跨进程,第二个是造成代码臃肿。

因为 Handler 是基于内存的,而进程之间的内存是隔离的,所以 Handler 不支持跨进程通信。

同时,如果编写大量的 if-else 块来处理 Message 不同的情况,Handler 会造成代码变得很臃肿。

三、MECE 分析 Handler

MECE 全称Mutually Exclusive Collectively Exhaustive,中文意思是“相互独立,完全穷尽”。

对于一个重大意义话题,MECE 能够做到不重、不遗漏的分类,而且 MECE 能够根据分类有效把握问题的核心。

接下来,小木箱按照 API、消息机制、高级应用和设计缺陷分类分析 Handler, 如果听完小木箱分析 Handler,那么 Handler 不再是任何人面试的拦路虎。

3.1 Handler 常见 API

Handler 常见 API 有四个,第一个是 Message,第二个是 MessageQueue,第三个是 ThreadLocal,最后一个是 Looper。

3.1.1 Message

首先,我们来说说第一个 API,Message,Message 要说的东西不多。

按照 MECE 原则,Message 内容主要分为三大类: Message 消息定义、Message 数据结构和 Message 创建方式

Message 消息定义

首先,我们说说 Message 消息定义,Message 是消息的载体,内部参数可以看如下源码:

并发编程 · 基础篇(中) · 三大分析法分析 Handler_第2张图片

Message 数据结构

然后,我们说说 Message 数据结构,Message 数据结构是单链表结构,Message 会通过 next 持有了下一个 Message 引用。

并发编程 · 基础篇(中) · 三大分析法分析 Handler_第3张图片

Message 创建方式

最后,我们说说 Message 创建方式,Message 有两种,第一种创建方式是构造函数创建 Message,第二种是通过 Message.obtain 方法创建 Message

并发编程 · 基础篇(中) · 三大分析法分析 Handler_第4张图片

但是不建议使用构造函数创建 Message,有两个原因,第一个原因是构造函数创建 Message 对象不能被重用,导致浪费内存。 第二个原因是构造函数创建 Message 破坏 Handler "优先级消息队列" ,构造函数创建的 Message 不会被添加到 Handler 的 "优先级消息队列" 中,因为 Handler 没有地方接收 Message。

并发编程 · 基础篇(中) · 三大分析法分析 Handler_第5张图片

反之,通过 Message.obtain 方法创建 Message 会存储在消息池 recycle 中。

3.1.2 MessageQueue

说完 Message,我们说一说第二个 API,MessageQueue,按照 MECE 原则,Message 内容主要分为三部分。

第一部分是 MessageQueue 定义,第二部分是 MessageQueue 流程,第三部分是 MessageQueue 思考。

MessageQueue 定义

首先,我们来说说 MessageQueue 的定义,Handler 的 MessageQueue "优先级消息队列" 作用是存放 Message 对象,Handler 的 MessageQueue 数据结构不是队列,Handler 的 MessageQueue 是双向链表数据结构,为什么这么设计呢?因为 Handler 的 MessageQueue 可以从头部和尾部同时进行查找操作,查找速度比单链表更快。

思考: 不同线程的多个 Handler 往 MessageQueue 中添加数据,那么 MessageQueue 是如何确保线程安全的?

并发编程 · 基础篇(中) · 三大分析法分析 Handler_第6张图片

如上图代码所示,MessageQueue 是基于消息循环实现的,MessageQueue 使用双重检查锁和 wait/notify 机制来确保线程安全。

当一个线程调用 MessageQueue.enqueueMessage 方法添加消息时,MessageQueue 会先检查锁。

如果锁没有被占用,MessageQueue 会获取锁,然后将消息添加到消息队列中,最后释放锁。

如果另一个线程正在使用锁,那么 MessageQueue 会阻塞,直到获得锁,然后才能继续添加消息。

另外,MessageQueue 还使用 wait/notify 机制来通知另一个线程,当消息被添加到消息队列时,另一个线程将被唤醒。

MessageQueue 流程

然后,我们来说说 MessageQueue 流程。

MessageQueue 流程我们可以看看如下流程图:

image.png

实例化 Handler 的时候。

首先,Handler 会创建一个 MessageQueue。

然后,MessageQueue 会调用 Looper 的 loop 方法。

接着,启动一个消息循环来处理 Message。

最后,如果没有 Message,那么主线程会释放 CPU 然后回调 native 的 Looper 睡眠方法进入休眠状态。

如果有 Message,那么会回调 native 的唤醒方法唤醒 CPU。

MessageQueue 思考

最后,我们说说对 MessageQueue 思考。

思考: MessageQueue.next 方法 会因为发现了延迟 Message,而进行阻塞。那么为什么后面加入的非延迟 Message 没有被阻塞呢

MessageQueue.next 方法会获取 "优先级消息队列" (queue 的 next 方法)中的下一条 Message,如果发现存在延迟 Message,会处理延迟 Message,然后再处理非延迟 Message。

消息队列处理 Message 是有优先级的,因此,加入的非延迟 Message 不会被阻塞。

并发编程 · 基础篇(中) · 三大分析法分析 Handler_第7张图片

3.1.3 ThreadLocal

说完 MessageQueue,我们说一说第三个 API,ThreadLocal,按照 MECE 原则,ThreadLocal 内容主要分为三部分。

第一部分是 ThreadLocal 作用,第二部分是 ThreadLocal 原理,第三部分是 ThreadLocal 和 Looper 关系。

ThreadLocal 作用

ThreadLocal 是 Java 中一个用于线程内部存储数据的工具类,作用是隔离线程。

我们看一下如下测试用例:

并发编程 · 基础篇(中) · 三大分析法分析 Handler_第8张图片

输出结果:

当前线程: main: true

当前线程: 小木箱: false

当前线程: 小石头: null

ThreadLocal 原理

因为线程访问 ThreadLocal 时候,当前线程的 ThreadLocalMap,会把 ThreadLocal 变量作为 key,传进来的泛型作为 value 进行存储。

并发编程 · 基础篇(中) · 三大分析法分析 Handler_第9张图片

因为线程隔离导致每个线程可以访问其线程局部变量,而其他线程无法访问该线程的局部变量。

也就是说,每个线程都可以独立地改变自己的副本,而不会影响其他线程所对应的副本。

image.png

ThreadLocal 和 Looper 关系

因为一个 Looper 只能保证只有一个 MessageQueue,在线程使用 Looper.prepare 方法创建 loop 的时候,Looper 会从 ThreadLocal 中获取。

如果 ThreadLocal 里面有 Looper 就会抛出异常,那么保证一个线程只有一个 Looper 或 MessageQueue 即可

并发编程 · 基础篇(中) · 三大分析法分析 Handler_第10张图片

3.1.4 Looper

说完 ThreadLocal,我们说一说第四个 API,Looper,Looper 内容主要分为两部分。

第一部分是 Looper 定义,第二部分是 Looper 生命周期。

Looper 定义

Looper 可以实现线程之间的 Message 传递。

Handler 的 Looper 接收从其他线程通过 dispatchMessage 发送 Message,并将 Message 放入一个 MessageQueue 中,然后在当前线程中按顺序处理(handleMessage)Message。

并发编程 · 基础篇(中) · 三大分析法分析 Handler_第11张图片

Looper 生命周期

按照 MECE 原则,Looper 生命周期分为 Looper 创建、Looper 启动和 Looper 终止。

Looper 创建

Looper 创建方式有两种,第一种是主线程 ActivityThread 创建 Looper,第二种是自己创建 Looper。

主线程 ActivityThread 创建 Looper,使用的是prepareMainLooper方法,通过prepareMainLooper方法可以在任何地方获取到主线程的 Looper,主线程的 Looper 不能退出。

并发编程 · 基础篇(中) · 三大分析法分析 Handler_第12张图片

自己创建 Looper,使用的是prepare方法,最终会调到prepare(boolean quitAllowed)方法,prepare(boolean quitAllowed)方法是 private,外部不能直接调用,区别是主线程创建的 Looper 不能退出,而自己创建的可以退出。

Looper 的内部维护了 MessageQueue,初始化 Looper,即初始了 MessageQueue

并发编程 · 基础篇(中) · 三大分析法分析 Handler_第13张图片

Looper 启动

Looper 启动的启动调用的是 loop 方法,prepareloop方法是配套使用的,两者必须成对存在,loop 的流程如下:

首先,获取当前线程的 Looper 对象,没有则抛异常。

然后,进入一个死循环: 不断调用 MessageQueue 的 next 方法来获取 Message。

最后,调用 message 的目标 handler 的dispatchMessage方法来处理 Message。

Looper 启动机制如下:

image.png


Looper的loop方法源码如下:

并发编程 · 基础篇(中) · 三大分析法分析 Handler_第14张图片

Looper 终止

Loop 常用的方法有两种,第一种是 quit,第二种是 quitSafely。

quit 和 quitSafely 区别在于 quit 会直接退出 Looper,而 quitSafely 首先设定一个 mQuitting 标记,然后把 MessageQueue 中的已有 Message 处理完毕,最后安全退出。

详细代码如下:

并发编程 · 基础篇(中) · 三大分析法分析 Handler_第15张图片

quitquitSafely方法最终都调用了quit(boolean safe)方法,quit(boolean safe)方法先判断是否能退出,然后再执行退出逻辑。

如果 mQuitting==true,那么这里会直接 return 掉。

mQuitting 变量只有在 quit 方法,才会被重新赋值。

因此一旦 looper 退出,就无法正常运行 looper。

当执行退出逻辑,CPU 会唤醒 MessageQueue,然后 MessageQueue 的 next 方法、Looper 的 loop 方法伴随退出,导致线程终止,详细流程图如下:

image.png

3.2 Handler 消息机制

Handler 消息机制主要分为三个流程,第一个流程是 Handler 发送 Message,第二个流程是 Looper 轮询读取,第三个流程是 Handler 回收 Message

3.2.1 Handler 发送 Message

首先,我们说说第一个流程 Handler 发送 Message,Handler 发送 Message 的方式有两种,第一种是sendMessage,第二种是post

3.2.1 Handler\_sendMessage & post

sendMessage直接发送 Message 实例对象,而post方法,发送的是一个 Runnable,Runnable 会被封装进一个 Message,发送的本质上也是一个 Message

并发编程 · 基础篇(中) · 三大分析法分析 Handler_第16张图片

sendMessage 或者 post等系列发送方法会调用到 Handler 的 enqueueMessage 方法,而 Handler 中的enqueueMessage方法最终调用到 MessageQueue 的enqueueMessage方法。

并发编程 · 基础篇(中) · 三大分析法分析 Handler_第17张图片

Handler 发送 Message 整体流程图参考如下:

并发编程 · 基础篇(中) · 三大分析法分析 Handler_第18张图片

3.2.2 MessageQueue_enqueueMessage

说完 Handler 的sendMessage & post方法,我们再说说sendMessage & post方法的底层实现 enqueueMessage,MessageQueue enqueueMessage 等待队列详细流程参考如下:

image.png

MessageQueue 的 enqueueMessage 等待队列源码分析如下,总共可以分为五个步骤:

并发编程 · 基础篇(中) · 三大分析法分析 Handler_第19张图片

第一步,如果 Message 中的 Handler 为空,那么抛非法参数异常。

第二步,MessageQueue 同步锁处理,如果当前线程已经消亡,那么抛非法参数异常并返回 false。

第三步,对 Message 的 when 重新赋值,记录正确的时间。

第四步,将新 Message 插入链表,如果 messageQueue 是空或者正在等待下个延迟 Message,那么需要 CPU 唤醒 MessageQueue。

第五步,根据 Message 的 when,找到在链表中插入位置进行插入,MessageQueue 维护 "优先级消息队列" ,确保 Message 是升序的。

3.2.3 MessageQueue_next

Message 存放到 "优先级消息队列" 之后,要对 Message 进行读取,Looper 的Loop方法会从 MessageQueue 中循环读 Message,loop方法调用了queue.next方法

并发编程 · 基础篇(中) · 三大分析法分析 Handler_第20张图片

next方法目的是获取 MessageQueue 中的一个 Message,next方法有一个死循环,如果 "优先级消息队列" 中没有 Message,那么 next 方法会一直阻塞。

如果 "优先级消息队列" 中有新 Message 到来,那么 next 方法会被唤醒,next 方法会返回新的 Message,并将 Message 从 "优先级消息队列" 中移除。

并发编程 · 基础篇(中) · 三大分析法分析 Handler_第21张图片

3.2.2 Looper 轮询读取

3.2.2.1 从 Java 层观测 Handler

然后,我们再说说第二个流程 Looper 轮询读取,Looper 的 loop 方法是一个 for 死循环,loop 方法做了三件事。

第一件事是调用 MessageQueue 的 next 方法取 Message。

第二件事是通过 Message 的 target,也就是 Handler 去 dispatchMessage 分发 Message。

第三件事是 handleMessage 回收 Message。

其中,MessageQueue 的 next 方法也是一个死循环,首先会调用linux 的 epoll 机制

其中,nextPollTimeoutMillis表示阻塞的时间,-1表示无限时间,直到有事件发生为止,0表示不阻塞

如果没有 Message 或者处理时间未到,next 方法会阻塞,nativePollOnce 函数的睡眠时间是 Java 层透传的

3.2.2.2 从 native 层观测 Handler

核心实现在 Pollinner 类中,native 有四种状态: 初始化、休眠、唤醒和消亡。

我们着重说一下休眠和唤醒。


先说说休眠状态,如果监听文件描述符没有发生 1 和 0 读写事件,那么当前线程会在 epoll wait 中进入休眠状态。

然后说说唤醒状态,如果当前线程有新的 Message 需要处理,那么 CPU 被唤醒,然后延续之前调用路径,回溯到 Java 层。

3.2.3 Handler 回收 Message

3.2.2.1 Looper\_loop

Looper 和线程是一对一的关系,Looper 调用的 dispatchMessage 方法会运行在不同的线程,所以 Message 的处理就会被切换到 Looper 所在线程。

Looper 的 loop 方法调用了msg.target.dispatchMessage(msg) 方法,msg.target 就是发送该 Message 的 Handler,这样 Message 最终会回调到 Handler 的dispatchMessage方法。

并发编程 · 基础篇(中) · 三大分析法分析 Handler_第22张图片

3.2.3.2 Looper\_dispatchMessage

msg.target.dispatchMessage(msg) 方法,msg.target 就是发送该 Message 的 Handler,Message 最终会回调到 Handler 的dispatchMessage方法中。

并发编程 · 基础篇(中) · 三大分析法分析 Handler_第23张图片

3.2.3.3 Looper\_handleCallback

如果 Message 的 callback 不为 null 就通过handleCallBack来处理 Message,Message 的 callback 是一个 Runnable 对象,实际上是 Handler 的post系列方法传递的 Runnable 参数。

并发编程 · 基础篇(中) · 三大分析法分析 Handler_第24张图片

3.2.3.4 Callback

如果 mCallback 不为 null 调用 mCallback 的handleMessage处理 Message,Callback 是个接口。

并发编程 · 基础篇(中) · 三大分析法分析 Handler_第25张图片

3.2.3.5 new Handler(callback)

通过 Callback 可以采用如下方式来创建 Handler。

并发编程 · 基础篇(中) · 三大分析法分析 Handler_第26张图片

3.2.3.6 Handler_handleMessage

最后,调用 Handler 的handleMessage方法来处理 Message

Handler 回收 Message 流程总结图如下:

并发编程 · 基础篇(中) · 三大分析法分析 Handler_第27张图片

3.2.4 Handler 机制总结

Handler 机制主要分为三个流程,第一个流程是 Handler 发送 Message,第二个流程是 Looper 轮询读取,第三个流程是 Handler 回收 Message。

image.png

首先,我们说说第一个流程 Handler 发送 Message,Handler 发送 Message 有两种方式,第一种方式是 Handler 的 sendMessage,第二种方式是 Handler 的 post 发送 Message。

Handler 的 sendMessage 可以发送定时和不定时 Message 两种。

Handler 的 post 本质也是 sendMessage 形式,只不过传入的 Runnable 参数包装成 Message 的 Callback。

Message 对象传给 MessageQueue 的 enqueueMessage 进行优先级入队,enqueueMessage 作用是维护一个 Message 链表,enqueueMessage 根据 Message 的 when 时间正序排序,延迟 Message 是延时时间+当前时间进行精准计算。

然后,就第二个流程 Looper 轮询读取,Looper 的 loop 方法是一个 for 死循环,loop 方法做了三件事。

第一件事是调用 MessageQueue 的 next 方法取 Message。

第二件事是通过 Message 的 target,也就是 Handler 去 dispatchMessage 分发 Message。

第三件事是 handleMessage 处理 Message。

其中,MessageQueue 的 next 方法也是一个死循环,首先会调用linux 的 epoll 机制

如果没有 Message 或者处理时间未到,next 方法会阻塞,nativePollOnce 函数的睡眠时间是 Java 层透传的,核心实现在 Pollinner 类中,native 有两种状态: 休眠和唤醒。

先说说休眠状态,如果监听文件描述符没有发生 1 和 0 读写事件,那么当前线程会在 epoll wait 中进入休眠状态。

然后说说唤醒状态,如果当前线程有新的 Message 需要处理,那么 CPU 被唤醒,然后延续之前调用路径,回溯到 Java 层。

最后,对新 Message 进行外理。

image.png

看完源码,有两点可以深度反思,第一点是获取 Message 只能通过 Message 的 obtain 获取,不要直接 new,因为 Message.obtain 会从缓存里面去取。

第二点是可以使用 IdleHandler 在 "优先级消息队列" 空闲时提前做一些操作。

实际开发中,点击消息中心图标,会跳到 h5 页面。

在 url 后面加密拼接 uid 和 phoneNumber,加密操作是同步的,所以可以通过 IdleHandler 提前做这一操作,并且返回 false,表示只做一次。

3.3 Handler 高级应用

3.3.1 Handler 同步屏障机制

Handler 发送的 Message 会存放到 MessageQueue 中,MessageQueue 维护了一个优先级队列。

优先级队列存储了单链表的 Message 按照时间大小进行升序,Looper 则按顺序,每次从优先级队列中取出一个 Message 进行分发,处理完一个就处理下一个,有没有办法让指定 Message 优先消费?

有! 同步屏障机制! 下面又得搬出 5W2H 分析法分析同步屏障机制。

3.3.1.1 What: 同步屏障机制定义

Android 的 Handler 同步屏障机制是一种用来防止多线程间数据竞争的机制,同步屏障机制可以保证多个线程之间的数据同步。

当一个线程的数据在另一个线程中被修改时,Handler 会发出一个信号,以便其他线程可以检查这些数据。

如果数据发生变化,那么 Handler 会把这些变化发送给其他线程,以确保所有线程都拥有最新的数据。

3.3.1.2 Who: 同步屏障机制原理分析
MessageQueue_next

如果当前 msg 不为空并且 msg.target 的 Handler 对象为空,那么执行同步屏障并且在消息队列中查询下一个异步消息,循环遍历查询下一个异步消息,通过循环体执行相关链表的工作。

并发编程 · 基础篇(中) · 三大分析法分析 Handler_第28张图片

在这里我们提出了第一个问题。

发送消息的一系列方法会给 msg.target 对象赋值,msg.target 什么时候赋值为空的呢?

其实 target 属性为空的 Message 就是同步屏障,同步屏障可以使得异步 Message 优先被处理,通过 MessageQueue 的postSyncBarrier可以添加一个同步屏障。

在异步消息处理完之后,同步屏障并不会被移除,需要我们手动移除。

并发编程 · 基础篇(中) · 三大分析法分析 Handler_第29张图片

如果不移除同步屏障,那么同步屏障会一直在那里,同步消息就永远无法被执行。下面我们跟一下同步屏障源码:

MessageQueue_postSyncBarrier

post 和 get 方法最终都会走 MessageQueue 的postSyncBarrier的方法,MessageQueue 的postSyncBarrier的方法中没有给 msg.target 对象赋值。

但是postSyncBarrier方法 target 属性为空的同步屏障 Message,同步屏障 Message 是特殊的 Message,不被消费,作为特殊标识短暂的存储在 MessageQueue 中。

遇到 target 为 null 的 Message,说明是同步屏障,循环遍历找出一条异步消息,然后处理。在同步屏障没移除前,只会处理异步消息,处理完所有的异步消息后,就会处于堵塞。如果想恢复处理同步消息,需要调用 removeSyncBarrier() 移除同步屏障

并发编程 · 基础篇(中) · 三大分析法分析 Handler_第30张图片

MessageQueue_enqueueMessage

在这里我们提出了第二个问题。

如何将把同步屏障消息变成异步消息?

有两种方式:第一种是在 Handler 的构造方法中,传入 async 为 true,那么这个时候发送的 Message 就都是异步的的消息,第二种是给 Message 通过setAsynchronous 方法标志为异步。

第一种通过 msg.setAsynchronous 方法设置为 true,可以把一个同步屏障消息变成异步消息

并发编程 · 基础篇(中) · 三大分析法分析 Handler_第31张图片

Handler(Looper looper, Callback callback, boolean async)

第二种如果满足 Handler 的 mAsynchronous 属性为 true,那么同步屏障消息会在 Handler 的两个构造方法中重新赋值

并发编程 · 基础篇(中) · 三大分析法分析 Handler_第32张图片

因此,同步屏障消息设置为异步消息。

3.3.1.3 How: 同步屏障机制测试用例

假设当前 Message CrazCodingBoy 分发给 Handler 后执行了耗时操作,那么本该到点消费的 Message CrazCodingBoy 被阻塞了,Message CrazCodingBoy 在其他 Message sendMessage 方法之后,等其他 Message 消费完再消费当前 Message CrazCodingBoy,详细测试用例参考如下:

并发编程 · 基础篇(中) · 三大分析法分析 Handler_第33张图片

输出结果:

count: 10

3.3.1.4 When: 同步屏障机制使用场景
ViewRootImpl 快速响应 UI 刷新

在进行 UI 绘制的时候,以下是 ViewRootImpl 中执行 UI 绘制的方法使用到了同步屏障

并发编程 · 基础篇(中) · 三大分析法分析 Handler_第34张图片

绘制消息放入优先级消息队列之前,首先先放入了一个同步屏障,然后在发送异步绘制消息,最后使得界面绘制的消息会比其他消息优先执行,避免了因为 MessageQueue 中消息太多导致绘制消息被阻塞导致画面卡顿,当绘制完成后,就会将同步屏障移除

3.3.1.5 How Much: 同步屏障机制应用价值

Handler 同步屏障机制还可以用于多线程编程中的并发编程,帮助开发者实现多线程之间的同步,提高程序的性能。

3.3.1.6 Why: 同步屏障机制使用原因

为了保证消息的顺序性和正确性,避免消息的乱序处理和重复处理。

同步屏障机制能够保证在消息到达 Handler 之前,所有的消息都已经处理完毕,而不会出现消息重复处理的情况。

这样,就能够保证每一条消息只处理一次,从而保证 Handler 处理消息的正确性和顺序性。

3.3.2 IdleHandler 应用

IdleHandler 定义

IdleHandler 是一种 Android 中的回调机制,IdleHandler 可以让 Android 开发应用处于空闲状态时执行特定的操作。

IdleHandler 可以用来执行一些定期的任务。

IdleHandler 源码分析

Android IdleHandler 使用 Handler.post(Runnable) 方法来将一个 Runnable 对象放入 MessageQueue 的 next 优先级消息队列中。

当应用程序空闲时,Runnable 对象就会被执行。

当任务完成后,可以使用 Handler.removeCallbacks(Runnable)来从 MessageQueue 的 next 优先级消息队列中移除 Runnable 对象。

并发编程 · 基础篇(中) · 三大分析法分析 Handler_第35张图片

IdleHandler 测试用例

并发编程 · 基础篇(中) · 三大分析法分析 Handler_第36张图片

输出结果:

queueIdle: 空闲时做一些轻量级别耗时操作

IdleHandler 应用场景

最典型的两个案例是: IdleHandler 可以获取 View 宽高和网络连接检测

3.3.3 Looper 活学活用

Looper 高级使用有两个,第一个是

第一个是通过 LoopergetMainLooper方法获取主线程 Looper,可以判断当前线程是否在主线程

第二个是将 Runnable post 到主线程执行

并发编程 · 基础篇(中) · 三大分析法分析 Handler_第37张图片

3.3.4 HandlerThread

参考小木箱成长营的并发编程 · 基础篇(上) · android 线程那些事#2.4.1 HandlerThread

3.4 Handler 设计缺陷

3.4.1 Crash 现场还原

并发编程 · 基础篇(中) · 三大分析法分析 Handler_第38张图片

空指针异常是原因多线程并发,当主线程执行到 sendEnptyMessage 时,子线程的 Handler 没有创建。

因此,我们获取到 Handler,再去消费 Message 就可以了,测试用例我们让主线程休眠再执行,可以解决 Crash 问题。

子线程使用 Handler,有两点需要注意

第一点是必须调用Looper.prepare()创建当前线程的 Looper,并调用Looper.loop()开启消息循环

第二点是必须在使用结束后调用 Looper 的quit方法退出当前线程,否则,如果不退出当前线程,线程的 Looper 处理完所有的消息后,会处于阻塞状态,因为线程是重量级的,如果一直阻塞,会影响应用性能。

实际开发过程中,我们可以搭建一套容灾体系,使用并发编程 · 基础篇(上) · android 线程那些事#4.5 UncaughtException 兜底方案全局捕获 Handler 触发的非法参数异常和空指针异常。设计流程图如下:

并发编程 · 基础篇(中) · 三大分析法分析 Handler_第39张图片

3.4.2 后台线程弹 Toast

并发编程 · 基础篇(中) · 三大分析法分析 Handler_第40张图片

输出结果:

1: "Can't toast on a thread that has not called Looper.prepare()"

2: 正常运行

分析源码我们发现,在后台线程弹吐司时候,必须初始化后台线程的 Looper,否则会报异常

并发编程 · 基础篇(中) · 三大分析法分析 Handler_第41张图片

3.4.3 后台线程弹 Dialog

同理在后台线程弹对话框时候,必须初始化后台线程的 Looper,不然也会报异常,因为创建 Handler,需要先创建 Looper 并开启消息循环,主线程默认创建了并开启消息循环,而后台线程并没有。

并发编程 · 基础篇(中) · 三大分析法分析 Handler_第42张图片

那么主线程是如何创建 Looper 的呢?我们分析一下源码

并发编程 · 基础篇(中) · 三大分析法分析 Handler_第43张图片

ActivityThread 通过 ApplicationThread 和 AMS 进行进程间通信的方式完成 ActivityThread 的请求后,会回调 ApplicationThread 中的 Binder 方法,然后 ApplicationThread 会向 H 发送消息,H 收到消息后会将 ApplicationThread 中的逻辑切换到 ActivityThread 中去执行,即切换到主线程去执行

四、SCQA 分析 Handler

SCQA 模型是麦肯锡芭芭拉·明托在《金字塔原理》中提出的“结构化表达”模板工具,常用于方案、文案、广告、演讲、讲故事、写作和面试等。

SCQA 是 Situation、Complication、Question 和 Answer4 个英文单词简称。分别是:

  • S(Situation)背景—由⼤家都熟悉的情景、事实引⼊
  • C(Complication)代表冲突—指的是实际情况和我们的要求有冲突⼊
  • Q(Question)代表问题—怎么办
  • A(Answer)代表答案—我们的解决⽅案

我们在面试的时候一般有场景题、矛盾题和定义题。分别对应着 SCQA、CQA 和 QA 三种模型。小木箱总结了 33 个高频 Handler 面试题,答案后期将同步到B 站,感兴趣可以提前关注一下。

并发编程 · 基础篇(中) · 三大分析法分析 Handler_第44张图片

五、结语

三大分析法分析 Handler 主要分为三部分,第一部分是 5W2H 分析 Handler,第二部分是 MECE 分析 Handler,第三部分是 Handler 设计缺陷,第四部分是 SCQA 分析 Handler。

首先,5W2H 分析 Handler 针对 Handler 提出了六个高价值问题。

然后,MECE 分析 Handler 分为两部分,第一部分是 Handler 常见 API、第二部分是 Handler 消息机制、第三部分是 Handler 高级应用和 Handler 设计缺陷。

最后,SCQA 视频分享了 33 个 Handler 高频面试题。

其中,Handler 消息机制主要分为三部分,第一部分是 Handler 发送 Message、第二部分是 Looper 轮询读取和第三部分是 Handler 回收 Message。

本文写作目的是用最平滑的语言,帮助大家从原理到实现学习 Handler。

企业面试中,Handler 算是送分题,作为一名高级 Android 开发,连 Handler 都答不会,基本不太可能通过面试,当然怎样利用 Handler 完善 Android 容灾体系,文章没有过多的讲解,感兴趣可以听一下第 12 期字节跳动技术沙龙录播课。

下一节,小木箱将带大家学习并发编程 · 基础篇(下) · android 线程池那些事。

我是小木箱,如果大家对我的文章感兴趣,那么欢迎关注小木箱的公众号小木箱成长营。小木箱成长营,一个专注移动端分享的互联网成长社区。

参考资料

https://juejin.cn/post/693260...

https://juejin.cn/post/692408...

本文由mdnice多平台发布

你可能感兴趣的:(后端)