Handler 源码解析(Java 层)
作 者:
原文链接:https://www.hchstudio.cn/article/2019/556f/
版权声明:非特殊声明均为本站原创作品,转载时请注明作者和原文链接。
从很早开始就认识到 Handler 了,只不过那时修为尚浅,了解的不够深刻,也没有应用自如。不过随着工作时间的增长,对 Handler 又有了更深层次的认识,于是有了这篇博客,希望尽可能的总结出多的知识点。
Handler 在 Java 层源码主要有 4 个类:Looper、MessageQueue、Message、Handler。我归纳了他们的几个主要知识点:
- Looper:sThreadLocal、Looper.loop();
- Message:数据结构、消息缓存池;
- MessageQueue:enqueueMessage、next、管道等待、同步消息隔离、idleHandler;
- Handler:send/post、dispatchMessage 消息处理优先级。
Looper
Looper 数据结构
1 | static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>(); |
- sThreadLocal:静态常量,保证一个线程只有一个 Looper;
- sMainLooper:静态变量,在 prepareMainLooper 中赋值当前线程 Looper;
- mQueue:变量,Looper 构造函数中初始化,因为一个线程只有一个 Looper,所以也同样只有一个 mQueue。
通过以上分析,我们可以总结出一下特性:
- Looper、MessageQueue 是线程唯一的;
- 一个进程只有一个 sMainLooper;
- 根据 ThreadLocal 的特性,可通过 myLooper 方法获取当前线程的 Looper。
Looper.loop()
1 | public static void loop() { |
Looper.loop() 方法虽然看起来很多,其实他主要就做了三件事:
- 从消息队列中获取下一个消息;
- msg.target 就是 handler,通过 dispatchMessage 方法把消息分发下去,这个方法下面会有说到;
- 消息回收,放到消息缓存池里。这里需要注意的是 Message 对象并没有释放,会缓存起来。
Message
Message 数据结构
1 | public int what, arg1, arg2; |
- 看到 next 变量,我们会想到单链表,其实 Message 就相当于单链表的 node,MessageQueue 就是一个单链表了,会持有表头的引用;
- what、arg1、arg2、obj、data 就是我们发送的一些信息;
- 值得注意的是 target,他是 Handler 类型,就是本消息的 Handler,会在 Handler 发送消息的时候赋值;
- 后面的四个对象,都是和消息缓存池有关的。
Message 消息缓存池
1 | public static Message obtain() { |
- 事实上缓存池的数据结构也是一个链表,sPool 为链表头引用,最大容量为 50;
- 回收消息时,会把消息里面所有参数重置,并把当前消息设为链表头;
- 获取消息时,返回当前链表头,并把 next 置空。
MessageQueue
插入队列
1 | boolean enqueueMessage(Message msg, long when) { |
主要作为插入队列的方法,有下列几个特性:
- 把消息加入消息队列,如果当前表头为空,则把消息作为表头引用;如果不为空,则会根据时间的顺序,插入到对应的时间中;
- nativeWake 是调用底层在管道中写操作以唤醒,在队列不是阻塞的状态下是不需要唤醒的;
- 另外注意其中用了 synchronized 关键字,说明消息队列的插入是线性安全的,删除也是线性安全的,之后我们会说到。
MessageQueue.next()
1 | for (;;) { |
此方法会从消息队列中读取下一个消息返回,主要做了以下操作:
- nativePollOnce 函数会调用底层管道操作函数,nextPollTimeoutMillis 为 -1 时,会阻塞,为 0 时不会阻塞,大于 0 时,会阻塞相应的时间;
- 如果有同步消息隔离,则会优先查找异步消息;
- 获取当前时间队列的消息,并返回;
- 如果队列没有任何消息,则会执行 idler.queueIdle,通知监听者当前队列处于空闲状态。
同步消息隔离
上面我们有提到了同步消息隔离,这里我们介绍一下。同步隔离,有时候也可以叫异步消息,说的是一个知识点。在源码中主要用于优先更新 UI。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
29
30
31
32
33private IdleHandler[] mPendingIdleHandlers;
public int postSyncBarrier() {
return postSyncBarrier(SystemClock.uptimeMillis());
}
private int postSyncBarrier(long when) {
// 向消息队列中加入一个 handler 为空的消息
synchronized (this) {
final int token = mNextBarrierToken++;
final Message msg = Message.obtain();
msg.markInUse();
msg.when = when;
msg.arg1 = token;
Message prev = null;
Message p = mMessages;
if (when != 0) {
while (p != null && p.when <= when) {
prev = p;
p = p.next;
}
}
if (prev != null) { // invariant: p == prev.next
msg.next = p;
prev.next = msg;
} else {
msg.next = p;
mMessages = msg;
}
return token;
}
}
如上 postSyncBarrier 函数中会向消息队列中加入一个 handler(即 Message 的 target) 为空的消息作为标识。在我们上面 MessageQueue.next() 的函数中,当 msg.target == null 时,会优先获取异步消息并返回。
因此想要使用同步消息隔离有两个条件:
- 消息为异步消息,即 msg.isAsynchronous() 返回 false;
- 需要获取当前队列并运行 postSyncBarrier() 函数。
IdleHandler
Handler 还提供了消息队列空闲状态通知。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16private final ArrayList<IdleHandler> mIdleHandlers = new ArrayList<IdleHandler>();
public void addIdleHandler(@NonNull IdleHandler handler) {
if (handler == null) {
throw new NullPointerException("Can't add a null IdleHandler");
}
synchronized (this) {
mIdleHandlers.add(handler);
}
}
public void removeIdleHandler(@NonNull IdleHandler handler) {
synchronized (this) {
mIdleHandlers.remove(handler);
}
}
IdleHandler 的源码比较简单,就是一个 ArrayList,然后进行增加删除操作。注意,这个也是线性安全的。
Handler
post/sendMessage
1 | public final boolean post(Runnable r){ |
- sendMessage 和 post 最本质的区别是之后处理任务时的优先级,post 会处理 Runnable 中的任务,而 sendMessage 会回调给 handler 处理;
- 他们最终都会走 enqueueMessage 方法,并设置当前 Handler 为 msg.target。
dispatchMessage
1 | public void dispatchMessage(Message msg) { |
任务执行时就会运行这个函数,主要是一个优先级的问题:
- callback 优先级最高,也就是 post 发送的消息
- mCallback.handleMessage(msg),优先级第二
- handleMessage(msg),优先级第三

作 者:
原文链接:https://www.hchstudio.cn/article/2019/556f/
版权声明:非特殊声明均为本站原创作品,转载时请注明作者和原文链接。
