Handler 源码解析(Java 层)

代码星冰乐

专注成就未来

首页 归档 关于

Handler 源码解析(Java 层)

Mar 19, 2019 | ChanghuiN | Android | 阅读
文章目录
  1. 1. Looper
    1. 1.1. Looper 数据结构
    2. 1.2. Looper.loop()
  2. 2. Message
    1. 2.1. Message 数据结构
    2. 2.2. Message 消息缓存池
  3. 3. MessageQueue
    1. 3.1. 插入队列
    2. 3.2. MessageQueue.next()
    3. 3.3. 同步消息隔离
    4. 3.4. IdleHandler
  4. 4. Handler
    1. 4.1. post/sendMessage
    2. 4.2. dispatchMessage

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


由于版权原因,请阅读原文 --> Handler 源码解析(Java 层)

关注我们

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

作 者:ChanghuiN
原文链接: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
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
33
34
35
36
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
private static Looper sMainLooper; // guarded by Looper.class

final MessageQueue mQueue;

// sThreadLocal
private static void prepare(boolean quitAllowed) {
if (sThreadLocal.get() != null) { throw Exception ... }
sThreadLocal.set(new Looper(quitAllowed));
}
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}

// sMainLooper
public static void prepareMainLooper() {
prepare(false);
synchronized (Looper.class) {
if (sMainLooper != null) { throw Exception ...}
sMainLooper = myLooper();
}
}
public static Looper getMainLooper() {
synchronized (Looper.class) {
return sMainLooper;
}
}

// mQueue
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
public static @NonNull MessageQueue myQueue() {
return myLooper().mQueue;
}
  • sThreadLocal:静态常量,保证一个线程只有一个 Looper;
  • sMainLooper:静态变量,在 prepareMainLooper 中赋值当前线程 Looper;
  • mQueue:变量,Looper 构造函数中初始化,因为一个线程只有一个 Looper,所以也同样只有一个 mQueue。

通过以上分析,我们可以总结出一下特性:

  1. Looper、MessageQueue 是线程唯一的;
  2. 一个进程只有一个 sMainLooper;
  3. 根据 ThreadLocal 的特性,可通过 myLooper 方法获取当前线程的 Looper。

Looper.loop()

1
2
3
4
5
6
7
8
9
10
11
public static void loop() {
final Looper me = myLooper();
final MessageQueue queue = me.mQueue;
for (;;) {
Message msg = queue.next();
...
msg.target.dispatchMessage(msg);
...
msg.recycleUnchecked();
}
}

Looper.loop() 方法虽然看起来很多,其实他主要就做了三件事:

  1. 从消息队列中获取下一个消息;
  2. msg.target 就是 handler,通过 dispatchMessage 方法把消息分发下去,这个方法下面会有说到;
  3. 消息回收,放到消息缓存池里。这里需要注意的是 Message 对象并没有释放,会缓存起来。

Message

Message 数据结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public int what, arg1, arg2;
public Object obj;
public Messenger replyTo;
int flags;
long when; // 消息发送时间
Bundle data;
Handler target;
Runnable callback;

Message next;

private static final Object sPoolSync = new Object();
private static Message sPool;
private static int sPoolSize = 0;

private static final int MAX_POOL_SIZE = 50;
  • 看到 next 变量,我们会想到单链表,其实 Message 就相当于单链表的 node,MessageQueue 就是一个单链表了,会持有表头的引用;
  • what、arg1、arg2、obj、data 就是我们发送的一些信息;
  • 值得注意的是 target,他是 Handler 类型,就是本消息的 Handler,会在 Handler 发送消息的时候赋值;
  • 后面的四个对象,都是和消息缓存池有关的。

Message 消息缓存池

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
33
34
35
public static Message obtain() {
synchronized (sPoolSync) {
if (sPool != null) {
Message m = sPool;
sPool = m.next;
m.next = null;
m.flags = 0; // clear in-use flag
sPoolSize--;
return m;
}
}
return new Message();
}

void recycleUnchecked() {
flags = FLAG_IN_USE;
what = 0;
arg1 = 0;
arg2 = 0;
obj = null;
replyTo = null;
sendingUid = -1;
when = 0;
target = null;
callback = null;
data = null;

synchronized (sPoolSync) {
if (sPoolSize < MAX_POOL_SIZE) {
next = sPool;
sPool = this;
sPoolSize++;
}
}
}
  • 事实上缓存池的数据结构也是一个链表,sPool 为链表头引用,最大容量为 50;
  • 回收消息时,会把消息里面所有参数重置,并把当前消息设为链表头;
  • 获取消息时,返回当前链表头,并把 next 置空。

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
29
30
31
32
33
34
35
36
boolean enqueueMessage(Message msg, long when) {
synchronized (this) {
msg.markInUse();
msg.when = when;
Message p = mMessages;
boolean needWake;
if (p == null || when == 0 || when < p.when) {
// 作为表头,如果队列是阻塞状态则需要唤醒
msg.next = p;
mMessages = msg;
needWake = mBlocked;
} else {
// 根据时间顺序,插入链表中间
needWake = mBlocked && p.target == null && msg.isAsynchronous();
Message prev;
for (;;) {
prev = p;
p = p.next;
if (p == null || when < p.when) {
break;
}
if (needWake && p.isAsynchronous()) {
needWake = false;
}
}
msg.next = p; // 插入消息
prev.next = msg;
}

// We can assume mPtr != 0 because mQuitting is false.
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}

主要作为插入队列的方法,有下列几个特性:

  • 把消息加入消息队列,如果当前表头为空,则把消息作为表头引用;如果不为空,则会根据时间的顺序,插入到对应的时间中;
  • nativeWake 是调用底层在管道中写操作以唤醒,在队列不是阻塞的状态下是不需要唤醒的;
  • 另外注意其中用了 synchronized 关键字,说明消息队列的插入是线性安全的,删除也是线性安全的,之后我们会说到。

MessageQueue.next()

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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
for (;;) {
nativePollOnce(ptr, nextPollTimeoutMillis);
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 {
// Got a message.
mBlocked = false;
if (prevMsg != null) {
prevMsg.next = msg.next;
} else {
mMessages = msg.next;
}
msg.next = null;
if (DEBUG) Log.v(TAG, "Returning message: " + msg);
msg.markInUse();
return msg;
}
} else {
// 没有更多消息的时候,nextPollTimeoutMillis 会置为 1。
nextPollTimeoutMillis = -1;
}

...
}

// 如果目前没有消息,已经处在空闲状态,则执行 idler.queueIdle
for (int i = 0; i < pendingIdleHandlerCount; i++) {
final IdleHandler idler = mPendingIdleHandlers[i];
mPendingIdleHandlers[i] = null; // release the reference to the handler

boolean keep = false;
try {
keep = idler.queueIdle();
} catch (Throwable t) {
Log.wtf(TAG, "IdleHandler threw exception", t);
}

if (!keep) {
synchronized (this) {
mIdleHandlers.remove(idler);
}
}
}
...
}

此方法会从消息队列中读取下一个消息返回,主要做了以下操作:

  • 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
33
private 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 时,会优先获取异步消息并返回。
因此想要使用同步消息隔离有两个条件:

  1. 消息为异步消息,即 msg.isAsynchronous() 返回 false;
  2. 需要获取当前队列并运行 postSyncBarrier() 函数。

IdleHandler

Handler 还提供了消息队列空闲状态通知。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
private 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public final boolean post(Runnable r){
return sendMessageDelayed(getPostMessage(r), 0);
}

public final boolean sendMessage(Message msg){
return sendMessageDelayed(msg, 0);
}

private static Message getPostMessage(Runnable r) {
Message m = Message.obtain();
m.callback = r;
return m;
}

private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
  • sendMessage 和 post 最本质的区别是之后处理任务时的优先级,post 会处理 Runnable 中的任务,而 sendMessage 会回调给 handler 处理;
  • 他们最终都会走 enqueueMessage 方法,并设置当前 Handler 为 msg.target。

dispatchMessage

1
2
3
4
5
6
7
8
9
10
11
12
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}

任务执行时就会运行这个函数,主要是一个优先级的问题:

  1. callback 优先级最高,也就是 post 发送的消息
  2. mCallback.handleMessage(msg),优先级第二
  3. handleMessage(msg),优先级第三

关注我们

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

分享
Android源码解析
我理解的零拷贝Nacos 配置中心的调研
微信关注我们
分类
  • Android8
  • Go4
  • Java59
  • Kafka,Java1
  • Kotlin2
  • Linux1
  • MapReduce1
  • Python2
  • Raft1
  • Redis1
  • ThreadPoolExecutor1
  • go1
  • 工具1
  • 总结8
  • 旅游日记1
标签
Nginx ChanghuiN haifeiWu Android Java 设计模式 hexo Kotlin 算法 MySQL 源码解析 Python Redis golang web Kafka 配置中心 总结 性能优化 旅游日记 Shell Go 问题排查 译文 Docker Spring Boot 工具 学习笔记 WebFlux 性能测试 go 散列表 源码 netty Raft
最近文章
  • Kafka的日志复制机制
  • 从20到21
  • go 并发编程
  • 【译】了解Linux CPU负载-您何时应该担心?
  • Zookeeper 与分布式锁
  • 基于Redis的分布式锁到底安全吗?
  • 【译】Raft 学生指南
  • ThreadPoolExecutor 的简单梳理
  • MapReduce 的简单实现
  • 使用 Map 实现策略模式
福利专区
    免费SSL证书
      阿里云红包
        腾讯云专属福利
        Copyright © 2021 代码星冰乐. Powered by ChanghuiN. 版权所有 晋ICP备15001365号
        特别感谢: 云服务器服务商 、 CDN 服务商