前言

消息机制预计共分为两篇,本篇为第一篇,基础篇,消息机制是在面试中必问的知识点,虽然现在在实际工作中,已经没怎么使用到handler,如我们可以使用rxjava来做线程切换,但其实其内部核心仍然是handler,只是做了一层高级封装,下面我们就来深入分析handler消息机制原理


handler消息机制所涉及到的类大致有以下五个

  • handler
  • Looper
  • MessageQueue
  • Message
  • ThreadLocal

整个消息机制分为如下展开:

  • handler是如何将消息发送到消息队列的?
  • message是如何添加到messageQueue中的?
  • Looper是如何轮询消息队列的?
  • Looper取出消息后,是如何分发到handler的handleMessage进行处理的?

一. handler是如何将消息发送到消息队列messageQueue的?

我们最常使用的发送消息的sendMessage(),或者延迟发送sendMessageDelayed()最终都会走到enqueueMessage()方法

方法执行流程如下:

sendMessage() –> sendMessageDelayed() –> sendMessageAtTime() –> enqueueMessage() –> queue.enqueueMessage()

最终handler会调用messageQueue的enqueueMessage()方法,传入message,添加到消息队列中

sendMessage

1
2
3
public final boolean sendMessage(@NonNull Message msg) {
return sendMessageDelayed(msg, 0);
}

sendMessageDelayed

1
2
3
4
5
6
public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}

sendMessageAtTime

1
2
3
4
public boolean sendMessageAtTime(@NonNull Message msg, long uptimeMillis) {
...
return enqueueMessage(queue, msg, uptimeMillis);
}

enqueueMessage 注意这句话:msg.target = this; this就是当前发送消息的handler,赋值给msg,这样在取出消息的时候,就会分发给handler处理消息

1
2
3
4
5
6
private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
long uptimeMillis) {
msg.target = this; //这句很重要,最终处理消息是调用msg.target.handleMessage,这里的target就是
...
return queue.enqueueMessage(msg, uptimeMillis);
}

疑问:这里的queue是哪里来的,从后面Looper的分析中,我们知道,创建Looper的时候,会创建MessageQueue,为了验证,我们看下handler的构造方法:

1
2
3
4
5
6
7
8
9
public Handler(@Nullable Callback callback, boolean async) {
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread " + Thread.currentThread()
+ " that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;
}

创建handler的时候,会去获取当前线程的looper,然后从looper中获取messageQueue,后面handler发消息的时候,就是发送到这个messageQueue中

二.message是如何添加到messageQueue中的?

前置知识:messageQueue的数据结构 -> 单链表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
boolean enqueueMessage(Message msg, long when){
Message p = mMessages;
if (p == null || when == 0 || when < p.when) {//1
// New head, wake up the event queue if blocked.
msg.next = p;
mMessages = msg;
needWake = mBlocked;
}else {
// Inserted within the middle of the queue. Usually we don't have to wake
// up the event queue unless there is a barrier at the head of the queue
// and the message is the earliest asynchronous message in the queue.
needWake = mBlocked && p.target == null && msg.isAsynchronous();
Message prev;
for (;;) {//2
prev = p;
p = p.next;
if (p == null || when < p.when) {
break;
}
if (needWake && p.isAsynchronous()) {
needWake = false;
}
}
//3
msg.next = p; // invariant: p == prev.next
prev.next = msg;
}
}

整个messageQueue的数据结构是一个单链表,头结点是mMessages

分析1:判断message是否要作为头结点,p=null 说明消息队列中没有消息,when < p.when 说明消息要最先被执行,执行if中的代码,message就插入到头部

分析2:遍历链表的每个结点,比较when,找出插入位置(计算出prev和p)

分析3:for循环结束后,message会插入到prev和p之间

三. Looper是如何轮询消息队列的?

1.Looper是如何创建的?

主线程的Looper创建完成后,会存入主线程的threadLocal中

APP从framework层进入Java世界,就是通过执行activityThread的main方法,我们知道,启动一个线程,可以通过创建thread,然后调用start来开启线程,但是我们的主线程activityThread并不是继承自thread或者runnable,但是我们学过java,执行一个类的main方法就会启动线程,这也就是主线程的启动,下面我们来看main方法中关于looper的部分

1
2
3
4
public static void main(String[] args) {
Looper.prepareMainLooper();//创建主线程的Looper对象
Looper.loop();//开启死循环,有消息就处理,没消息就睡眠
}

主线程Looper的创建过程

prepareMainLooper()

1
2
3
public static void prepareMainLooper() {
prepare(false);
}

prepare()

1
2
3
4
5
6
7
8
9
10
11
private static void prepare(boolean quitAllowed) {
if (sThreadLocal.get() != null) {//1
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper(quitAllowed));//2
}
//3
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}

分析1 prepare方法只能调用一次,否则这里会抛出异常

分析2:创建Looper对象,存入当前线程的threadLocal

分析3:Looper的构造方法,在这里创建了MessageQueue消息队列

ThreadLocal

ThreadLocal是一个线程存储类,存储线程私有的数据,内部是一个map结构

前面的prepare()方法中,可以看到,新创建的Looper对象,存储到了ThreadLocal中

2.Looper如何轮询消息队列的?

给出loop()的核心方法,在死循环中获取消息,并分发消息给handler处理

1
2
3
4
5
6
7
8
public static void loop(){
final Looper me = myLooper();
final MessageQueue queue = me.mQueue;
for (;;) {
Message msg = queue.next();//1如果没有消息,就阻塞等待
msg.target.dispatchMessage(msg);//2 这里的target就是发送该消息的handler
}
}

分析1:queue.next()

会获取消息队列里面的一条消息,如果没有消息,就会进入nativePollOnce()阻塞等待,当一条消息插入到消息队列后,就会调用nativeWake() 唤醒,唤醒的地方就是nativePollOnce(),这也解释了一个面试题:为什么Looper的loop循环,不会卡主线程,因为主线程大部分时间都是处于阻塞等待(睡眠),这个时候,CPU会释放相关资源

分析2:msg.target.dispatchMessage(msg);

前面已经分析了,这里的target就是发送此消息的handler,下面来看dispatchMessage

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public void dispatchMessage(@NonNull Message msg){
//一般不会设置msg.callback,所以不会走这里
if (msg.callback != null) {
handleCallback(msg);
} else {
//一般也不会设置handler的mCallback,所以也不会走这里
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
//大多数时候,都是走的这里,执行我们重写的handleMessage方法
handleMessage(msg);
}
}

四 Message缓存池

1.Message消息池

一般我们都会从消息池中复用一条消息,而不是new

1
Message msg = Message.obtain()

接下来,我们来看obtain()方法

1
2
3
4
5
6
7
8
9
10
11
12
13
public static Message obtain() {
synchronized (sPoolSync) {
if (sPool != null) {//1
Message m = sPool;
sPool = m.next;
m.next = null;
m.flags = 0; // clear in-use flag
sPoolSize--;
return m;
}
}
return new Message();
}

解析1:sPool就是Message,相当于一个指针,其实消息复用池的数据结构就是一个单链表,sPool就是指针,将当前sPool的message对象返回,同时将sPool指向next(下一个消息)

五.常见面试题:

1.一个线程可以有几个handler?

答案:一个线程中可以有多个handler,我们可以自己创建handler来发送消息,同时我们知道ActivityThread内部有个mh,也就是handler,主要用于Android处理系统类的消息,也就是说一个线程中可以有多个handler,但是只有一个Looper

2.Looper的loop循环,为什么不会卡主线程?

3.什么是同步消息,异步消息,同步屏障?

https://blog.csdn.net/m0_46278918/article/details/116044174