一、概述
1.android消息机制的含义:
Android消息机制,其实指的就是 Handler 的运行机制,而 Handler 要正常运作,又需要底层的 MessageQueue , Looper , ThreadLocal 共同配合
因此所谓的消息机制,其实主要讲的就是 Handler ,MessageQueue ,Looper ,ThreadLocal 四者的关系
2.概述Handler工作流程:
-
首先是构造 Handler ,这时候会通过 ThreadLocal 找到当前线程的 Looper ,并将Handler与之绑定
-
然后(在另一线程)Handler 发出 Message,这个 Message 会加入 MessageQueue 中
-
然后 Handler 绑定的 Looper 会循环从 MessageQueue 中取出 Message
-
最后将取出的这个 Message 交由 Handler 的 handleMessage 等方法执行
3.延申-为什么规定只能在主线程更新UI(个人理解):
-
因为Android的UI控件并不是线程安全的,实际上控件是线程安全的也没有用,准确来说问题是整个UI绘制流程不是线程安全的。而要保证对UI控件的访问是线程安全的,那控件的逻辑无疑需要变的更加复杂,难以理解。
-
同时如果实现这种保证,从之前JAVA的学习中我们可以知道,很可能需要用到 阻塞式同步,这会导致线程的挂起和唤醒。
-
对于一个面向用户的终端系统,UI线程毫无疑问是不能够被挂起的。
-
因此,当时而言(目前也是),单线程的UI模型,是最好最方便的解决此问题的方法。
二、ThreadLocal 分析
概念解释:
- ThreadLocal 是 JAVA 中设计用来在多线程场景下,访问线程隔离变量的工具。
1.ThreadLocal 使用方法
如下所示,在需要每个线程独享变量的时候,可以使用 ThreadLocal
2.ThreadLocal 源码浅析
ThreadLocal 中实际是通过它的静态内部类:ThreadLocalMap 来进行数据存储的
- 需要注意,不是说 ThreadLocal 持有一个 Map 的对象,实际是每个 Thread 都会持有一个 Map 对象
ThreadLocalMap
- 是一个以数组的方式实现的哈希表,类似于一个 HashMap
- 这篇重点是讲消息机制,就不对 ThreadLocal 展开了,这个讲的很清楚:Java并发编程之ThreadLocal详解
实现方案对比:
- 最简单的实现方案:ThreadLocal 中持有一个线程安全的 ThreadLocalMap 对象,然后以 Thread 做为key值,隔离不同线程的变量值。(也是我一开始以为的方案)
- 实际JDK里的实现方案:ThreadLocal 不持有 ThreadLocalMap 对象,而是 map 的访问入口,每个 Thread 持有一个 ThreadLocalMap 对象,ThreadLocal 做为 key 值
JDK实现的优点:
- 空间占用小:方案一里,每个 ThreadLocal对象都会有一个 ThreadLocalMap ,而每一个线程又会在这些map里占用一个Entry。 方案二里 ,一个线程只会有一个 ThreadLocalMap ,ThreadLocalMap 中的 Entry 数量是 ThreadLocal 变量的数量。毫无疑问前者占用空间要多一些。
- 便于管理:方案一里,ThreadLocalMap 分散到不同的 ThreadLocal中,线程结束后,要回收这些内存无疑是比较麻烦的。而方案二里,线程结束了,ThreadLocalMap 自然也都跟着销毁了。
3.消息机制中的 ThreadLocal
Handler中:
Looper中:
浅析(个人理解):
- 在 Android 中,ThreadLocal 刚好符合消息机制的使用场景,所以被拿来使用了。
- 利用 ThreadLocal ,每一个线程都可以绑定一个 Looper( 构造方法中绑定当前线程,且这里的ThreadLocal 对象是静态变量),利用 Looper 中的循环,就可以在不同的线程间不停的 发送-接收消息了。
三、MessageQueue 分析(重要)
概念解释
- MessageQueue 是消息机制中的消息队列,负责插入消息和读取消息,所有消息在队列中按执行时间从早到晚排好序
实现
- 虽然名字是叫 Queue ,但是它实际上是一个单链表
- (其实 Message 就是一个链表节点,它有一个 Next 方法
1. messageQueue.next 方法
Message next() {
...
int nextPollTimeoutMillis = 0;//距离继续下次轮询的时间
//nextPollTimeoutMillis=-1:表示一直阻塞
//nextPollTimeoutMillis=0:表示不需要阻塞
//nextPollTimeoutMillis>0:表示阻塞的时间
for (;;) {
//开启一个无限循环
if (nextPollTimeoutMillis != 0) {
//不为0说明需要阻塞
Binder.flushPendingCommands();//阻塞前调用此方法,可以释放一些对象的引用,防止阻塞期间一直持有待处理对象。
}
nativePollOnce(ptr, nextPollTimeoutMillis);//调用本地方法,内部会根据nextPollTimeoutMillis的值来阻塞当前线程,这里也是nativeWake(mPtr),即唤醒线程回调的地方。
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 {
//消息到执行时间,取出一条消息
mBlocked = false;