三流码奴的自我救赎

0%

保姆级Handler机制解读

什么是 Handler

先来看看文档中是怎么说的:

A Handler allows you to send and process Message and Runnable objects associated with a thread’s MessageQueue. Each Handler instance is associated with a single thread and that thread’s message queue. When you create a new Handler it is bound to a Looper. It will deliver messages and runnables to that Looper’s message queue and execute them on that Looper’s thread.

意思是说Handler是一个用于处理Message和执行Runnable的工具。每个Thread都通过Looper维护一个MessageQueue。每实例化一个Handler都需要绑定一个LooperHandler主要功能就是向MessageQueue中发送MessageRunnable,并在Message被处理的时候执行其中的Runnable

可以看到Handler的背后是一整套的消息流水线机制,而Handler只是我们利用这套机制的入口。

Handler有什么用

通过上面的描述我们大概可以脑补出整套消息流水线机制的引用关系:

  • Handler绑定一个Looper
  • LooperThread维护
  • Looper内部维护一个MessageQueue

不难发现实际上Handler是间接绑定了Thread的。也就是说不同的Handler可以向不同的线程发送Message,在Message被处理的时候执行回调方法,回到主线程。或者可以直接向主线程中发送消息,或延迟消息进行delay操作。

这就是为什么要有Handler

  • Android系统中需要一个切换线程的工具
  • 同时需要在某些情况下进行delay操作

这也就是整套消息流水线机制的作用。

食用消息流水线机制的正确姿势

想要发送Message共有3种用法:

  • 实例化默认Handler并向Message添加Runnable
  • 实例化默认Handler并通过构造函数传入接口Handler.Callback实例
  • 继承Handler重写handleMessage()方法

当这3种实现方式同时存在的时候,遵循优先级Message.callback > Callback.handleMessage > handleMessage

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
* Handle system messages here.
*/
public void dispatchMessage(@NonNull Message msg) {
// 如果msg有callback则直接处理msg的callback
if (msg.callback != null) {
handleCallback(msg);
} else {
// 如果已通过构造函数设置mCallback则只处理该callback
if (mCallback != null) {
// mCallback处理成功直接返回
if (mCallback.handleMessage(msg)) {
return;
}
}
// 最后才通过Handler自己的回调处理Message
handleMessage(msg);
}
}

下面来分别看看上面说的3种方式分别如何实现

向Message添加Callback

1
2
3
4
5
6
val h = Handler(Looper.getMainLooper())
val msg = Message.obtain(h)
// Message的setCallback(Runnable)方法被标记为对App不可见,需要通过反射调用
val method = msg.javaClass.getMethod("setCallback", Runnable::class.java)
method.invoke(msg, { Log.e("handler test", "succeed!") })
h.sendMessageDelayed(msg, 2000L)

向Handler添加Callback

1
2
3
4
5
6
7
8
9
val h = Handler(
Looper.getMainLooper(),
// 通过构造函数给Handler设置Handler.Callback
Handler.Callback {
Log.e("handler test", "succeed!")
true
})
val msg = Message.obtain(h)
h.sendMessageDelayed(msg, 2000L)

继承Handler重写handleMessage()

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
fun postMessage() {
val h = CustomizedHandler(
Looper.getMainLooper(),
WeakReference(this)
)
val msg = Message.obtain(h)
h.sendMessageDelayed(msg, 2000L)
}

companion object {
// 静态内部类,非静态内部类会持有外部类(Activity)的引用,导致内存泄漏,后面会详细讲到
class CustomizedHandler(
looper: Looper,
// Activity弱引用
private val activity: WeakReference<Activity>
) : Handler(looper) {
// 重写
override fun handleMessage(msg: Message) {
super.handleMessage(msg)
activity.get()?.let {
Log.e("handler test", "succeed!")
}
}
}
}

机制实现原理

从上面的示例种我们可以看出,如果想通过Handler发送一个Message并回调处理,需要:

  • 创建Message实例
  • 通过Looper创建Handler实例
  • 通过Handler发送Message
  • 根据不同情况回调不同处理方法

先上一张网上找的图:
Handler

结论先行

整个消息流水线机制主要分为3部分:

  • Handler负责发送Message和回调handleMessage方法。
  • Message 是消息,知道自己应该什么时候被处理和应该回调至哪个Handler。同时Message本身是一个链表结构,自己指向下一个Message形成一个MessageQueue
  • MessageQueue可以理解为是对上面Message链表的封装,提供了一些方法对整个队列进行操作。
  • Looper可以理解为MessageQueue的驱动器,通过Looper不断循环读取MessageQueue中的第一个节点,进而处理消息。

总体流程是:

  1. HandlerMessage设置delay,同时将自己(Handler.this)设置为Messagetarget,并将其添加至MessageQueue中。
  2. Looper不断循环从MessageQueue中取出Message,并根据前面提到的优先级决定是否回调Message.target对应的Handler
  3. 若回调至Handler则由HandlerCallbackhandleMessage(Message)方法进行处理。

Message

为什么先说Message呢?通过前面的分析我们可以知道Message不仅仅是个简单的消息:

  • 可以携带Runnable回调,被优先处理并拦截Handler的处理方法
  • 持有Handler引用,可以回调到特定的Handler,而Message本身是要被发送至MessageQueue中的,也就说明Message其实是MessageQueueHandler之间通信的桥梁

感性的解释一下,可以把Message理解为一个跑腿的,Handler Message去银行取钱,Message去了之后直接去到MessageQueue中排队,最后排到了自己取完了钱,需要把钱还给Handler,这时就通过Handler的引用回调至target handler的处理方法。

下面来看看Message的主要成员和方法:

成员

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 用户自定义的code,因为每个Message的target都不一样,所以不需要考虑在不同Handler中冲突的情况
public int what;

// 延迟消息的关键,标记了这个Message应该延迟多久之后才被处理
public long when;

// 回调对应Handler的关键,在Message中持有一个发送该Message的Handler引用,当Message被处理之后通过这个引用回调的特定Handler
Handler target;

// 前面分析过,Message自己持有一个优先级最高的callback,可以覆盖掉对应Handler的处理逻辑,同时其set方法对app隐藏,只能通过反射调用
Runnable callback;

// 从这个成员我们可以看出Message本身是一个链表结构
// 这里Message用链表结构主要有两方面的考虑
// 1. Message在系统中数量很多,如果用array实现的话会占用大量连续内存空间
// 2. Message虽然数量很多但是处理的也很快,而且数量不定,也就是说如果用数组实现的话为了避免空间浪费,则需要
// 对数组不停的进行扩容和减容,非常影响效率,而且会造成内存抖动
// 这个时候链表的优势就体现出来了,链表不需要连续的空间,非常适合Message这种大量且不定数量的情况
Message next;

构造

Message.obtain(),一看到obtain这个字眼,相信只要看过一些源码的同学很快就反应过来了:Message是池化的。

回顾一下池化的思想:如果有一个类需要被频繁的实例化,并且存活时间非常短,这个时候创建和销毁的工作就会白白占用很多系统资源,所以为了避免这种情况的发生,需要用一个将实例化好的对象保存起来,如果需要使用就尝试去中取出实例进行复用。

源码中obtain()方法很多,一共有7种重载,但最终调用的还都是这个无参的原始obtain()方法,下面来看看这个方法中做了什么:

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
// 消息池同步标志
public static final Object sPoolSync = new Object();
// 消息池本体,前面提到了Message本身是链表结构,所以这里消息池也是链表结构
private static Message sPool;
// 消息池的大小
private static int sPoolSize = 0;
// 消息池的最大容量
private static final int MAX_POOL_SIZE = 50;

// 无参的原始obtain方法
public static Message obtain() {
/**
* 不难发现,上面用到的所有消息池相关的成员全都是静态的,也就是说这个消息池和相关的成员
* 都是全局的,在多线程的情况下如果不加锁极大概率会造成取得的数据在预期之外
*/
synchronized (sPoolSync) {
// 查看消息池中有没有消息
if (sPool != null) {
// 从消息池中取出第一个消息
Message m = sPool;
// 将消息池的引用指向下一个节点
sPool = m.next;
// 重置刚取出的message状态,至此已完成Message的复用
m.next = null;
m.flags = 0;
// 消息池大小-1
sPoolSize--;
return m;
}
}
// 消息池中没有消息,则直接实例化一个新的Message
return new Message();
}

Handler

构造

我们直接来看Handler的常用构造函数:

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
final Looper mLooper;
final MessageQueue mQueue;
final Callback mCallback;

// 在Android 11中被弃用
@Deprecated
public Handler() {
this(null, false);
}

// 在Android 11中被弃用
@Deprecated
public Handler(@Nullable Callback callback) {
this(callback, false);
}

// 弃用的原因,Handler本身必须绑定一个Looper
// 这种无Looper参数的构造函数在隐式选择Looper的时候有可能不是预期结果

public Handler(@NonNull Looper looper) {
this(looper, null, false);
}

public Handler(@NonNull Looper looper, @Nullable Callback callback) {
this(looper, callback, false);
}

// 可以看到无论哪个构造函数最后都会调用到这个构方法
// 其工作就是简单的为类成员赋值
// 唯一需要注意的地方是,mQueue是直接从Looper拿到的,也就是说Handler通过Looper与MessageQueue进行了绑定
@UnsupportedAppUsage
public Handler(@NonNull Looper looper, @Nullable Callback callback, boolean async) {
mLooper = looper;
mQueue = looper.mQueue;
mCallback = callback;
mAsynchronous = async;
}

可以看出Handler的构造其实非常直接:

  • 持有一个Looper引用
  • 通过Looper持有一个MessageQueue引用
  • 一个可空的Handler.Callback
  • 设置是否发送异步消息

关于异步消息后面会单独讲,这里先不过多描述,总的来说Handler的初始化就是给mLoopermQueuemCallback赋值。

发送消息

Handler中有n多用于发送消息的方法,由于实在太多在这里就不一一细说了。

但是这些方法都有一个共性,无论哪个发送Message的方法最终都会调用到sendMessageAtTime方法:

1
2
3
4
5
6
7
8
9
10
11
12
public boolean sendMessageAtTime(@NonNull Message msg, long uptimeMillis) {
MessageQueue queue = mQueue;
if (queue == null) {
RuntimeException e = new RuntimeException(
this + " sendMessageAtTime() called with no mQueue");
Log.w("Looper", e.getMessage(), e);
return false;
}
// 将消息发送至MessageQueue,入队
// 其中uptimeMillis标志着这个Message的延迟时间,如果超过这个时间还没有被处理就会被回收掉
return enqueueMessage(queue, msg, uptimeMillis);
}

继续跟进enqueueMessage()方法:

1
2
3
4
5
6
7
8
9
10
11
12
private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
long uptimeMillis) {
// 这一步非常关键,在这里将msg的target指定为当前Handler,消息被处理之后可通过这个引用回调至Handler中
msg.target = this;
msg.workSourceUid = ThreadLocalWorkSource.getUid();
if (mAsynchronous) {
// 如果是异步消息则将flag置为true
msg.setAsynchronous(true);
}
// 执行MessageQueue.enqueueMessage()方法,将Mesaage加入MessageQueue中
return queue.enqueueMessage(msg, uptimeMillis);
}

回调

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 这一段逻辑前面说过了就不再过多解释
// 只需要知道最终对Message任务完成做出响应的是Handler
// Message通过target调用Handler的dispatchMessage()方法
public void dispatchMessage(@NonNull Message msg) {
// 如果msg有callback则直接处理msg的callback
if (msg.callback != null) {
handleCallback(msg);
} else {
// 如果已通过构造函数设置mCallback则只处理该callback
if (mCallback != null) {
// mCallback处理成功直接返回
if (mCallback.handleMessage(msg)) {
return;
}
}
// 最后才通过Handler自己的回调处理Message
handleMessage(msg);
}
}

稍微总结一下,Handler其实就是一个发送器/接收器,用来将Message发送到MessageQueue中,当消息被处理之后会通过Message.target回调至Handler中进行处理。

MessageQueue

因为消息量大且数量不固定,所以不可能同时处理所有Message,这个时候就需要对大量的Message排队处理。

Message链表本身就形成了一个队列,但是并不能简单的根据这个链表结构利用起队列的特性,所以通过MessageQueue将整个链表进行了封装,对外提供对于队列的操作方式。

构造

先来看一看MessageQueue的构造方法:

1
2
3
4
5
6
7
8
9
10
// 用来标记是否允许当前MessageQueue退出
private final boolean mQuitAllowed;
// 这个成员在这里不做深究,native方法使用
// 可以简单理解为,mPtr=0的时候表示MessageQueue已经退出了,否则MessageQueue还在运行
private long mPtr; // used by native code

MessageQueue(boolean quitAllowed) {
mQuitAllowed = quitAllowed;
mPtr = nativeInit();
}

可以看到MessageQueue的构造方法非常简单,只是初始化了两个标志:

  • 是否允许退出
  • 是否已经退出

入队

这里就是Handler发送消息最终调用到的方法,直接来看代码:

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
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
boolean enqueueMessage(Message msg, long when) {
// 在这里检查Message是否持有Handler引用,如果没有Handler引用的话讲无法回调到处理方法
if (msg.target == null) {
throw new IllegalArgumentException("Message must have a target.");
}
// 在多线程的情况下会有来自不同线程的消息加入进来,在这个地方需要同步操作
synchronized (this) {
if (msg.isInUse()) {
throw new IllegalStateException(msg + " This message is already in use.");
}
// 在MessageQueue开始退出,不再接受任何新的Message
if (mQuitting) {
IllegalStateException e = new IllegalStateException(
msg.target + " sending message to a Handler on a dead thread");
Log.w(TAG, e.getMessage(), e);
// 回收
msg.recycle();
return false;
}
msg.markInUse();
// 设置message的延迟时间
msg.when = when;
Message p = mMessages;
boolean needWake;
/**
* 一共有三个case可以直接想链表头添加一个Message:
* 1. 当前消息队列中没有消息
* 2. 新的Message延迟时间为0
* 3. 新的Message延迟时间小于当前第一个Message的延迟时间
*/
if (p == null || when == 0 || when < p.when) {
msg.next = p;
mMessages = msg;
/**更新消息队列的阻塞状态
* mBlock是一个boolean变量,用于表示队列是否需要阻塞
* 这里有两个case
* 1. 如果当前消息队列的第一个Message的处理时间还没到,则不需要唤醒队列,mBlocked = false
* 2. 如果当前队列的第一个Message处理时间已经到了,则需要将队列进行唤醒,mBlocked = true
*/
needWake = mBlocked;
} else {
/**
* 上面if中的情况只包括向队列头部插入Message,这三种情况之外都应该向队列中间插入Message
* 一般情况下,如果向队列中间插入Message是不需要唤醒队列的。
* 这个地方后面将next()方法的时候会提到,这里稍微解释一下。
* 判断MessageQueue是否需要阻塞的条件其实非常简单:
* 阻塞:没有Handler在等在回调消息,并且满足
* -> 1. 消息队列中没有消息
* -> 2.队列中第一个消息还没到处理时间
* 唤醒:MessageQueue有Message
* 所以在这个时候如果队列已经阻塞,只可能是因为上面阻塞case中的第二条
*/

/**
* 来说下一个问题,如果看的够仔细就会发现,这个方法前面明明筛选了target不为null的Message入队,
* 为什么这里还会出现p.target == null?
* 其实是因为这里还有一个同步屏障(barrier)的概念在里面搅局
* 同步屏障具体是什么后面会介绍,这里简单说明一下:
* 一个target为null的话,说明这个message本身就是一个同步屏障,同步屏障会将队列阻塞起来,这个时候会去
* 遍历整个MessageQueue,优先处理异步消息(asynchronous message)
*/

// 如果存在同步屏障和异步Message就立刻唤醒队列
needWake = mBlocked && p.target == null && msg.isAsynchronous();
Message prev;
// 遍历MessageQueue查找插入位置
for (;;) {
prev = p;
p = p.next;
// 找到了最后,或者找到了延迟时间大于当前插入的消息的位置
if (p == null || when < p.when) {
break;
}
// 如果队列需要唤醒,且在队列中找到了异步消息,则将needWake标志设置为false
if (needWake && p.isAsynchronous()) {
needWake = false;
}
}
// 插入消息
msg.next = p;
prev.next = msg;
}

// 根据情况决定是否唤醒
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}

同步屏障(sync barriers)

关于同步屏障前面已经介绍过了,其作用就是将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
29
30
31
32
33
34
35
36
37
38
39
40
41
@UnsupportedAppUsage
public int postSyncBarrier() {
return postSyncBarrier(SystemClock.uptimeMillis());
}

private int postSyncBarrier(long when) {
synchronized (this) {
final int token = mNextBarrierToken++;
// 从消息池中获取一个Message,可以看出来其实同步屏障本身也是一个Message
final Message msg = Message.obtain();
msg.markInUse();
msg.when = when;
msg.arg1 = token;
Message prev = null;
Message p = mMessages;
/**
* 到这里完成了同步屏障的初始化
* 仔细看一下就会发现,这个Message并没有初始化targer属性,也就是说并没有绑定任何Handler
* 前面enqueue()方法中也遇到了target==null的Message,这就是同步屏障
*/

// 如果when不为0就去MessageQueue中找对应的位置
if (when != 0) {
while (p != null && p.when <= when) {
prev = p;
p = p.next;
}
}
// 如果找到了就在这里插入同步屏障
if (prev != null) {
msg.next = p;
prev.next = msg;
} else {
// 这个case是prev!=null,只有在when!=0的时候prev才有可能不为空
// 也就是说,如果没有指定生效时间就会直接添加到队列头部,立刻生效
msg.next = p;
mMessages = msg;
}
return token;
}
}

再来看一看移除同步屏障的方法:

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
@UnsupportedAppUsage
public void removeSyncBarrier(int token) {
synchronized (this) {
Message prev = null;
Message p = mMessages;
// 在队列中寻找target为null的Message,这个Message就是同步屏障
while (p != null && (p.target != null || p.arg1 != token)) {
prev = p;
p = p.next;
}
if (p == null) {
throw new IllegalStateException("The specified message queue synchronization "
+ " barrier token has not been posted or has already been removed.");
}
final boolean needWake;
// 如果同步屏障没有阻塞MessageQueue就直接移除掉
if (prev != null) {
prev.next = p.next;
// 这个情况下是不需要唤醒的,因为同步屏障没有将队列阻塞起来
needWake = false;
} else {
// 移除同步屏障,同时根据队列是否为空,队列中第一个Message是否为另一个同步屏障决定是否唤醒队列
mMessages = p.next;
needWake = mMessages == null || mMessages.target != null;
}
// 回收
p.recycleUnchecked();

// 如果队列没在退出过程,并且需要被唤醒,就将队列唤醒
if (needWake && !mQuitting) {
nativeWake(mPtr);
}
}
}

可以看到这两个方法都被标记为对app不可见,如果需要使用的话只能通过反射调用。

遍历(next)

这个方法其实就是Looper用来遍历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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
@UnsupportedAppUsage
Message next() {
// 前面提到了mPtr是否为0可以作为判断队列是否已经退出的标志
// 所以在这里如果队列已经退出则直接返回
final long ptr = mPtr;
if (ptr == 0) {
return null;
}
// idleHandler的数量,这个东西后面会单独说
int pendingIdleHandlerCount = -1; // -1 only during first iteratio
// 下一次尝试获取Message的时间
// 如果当前队列中第一个Message还没到执行时间,则会更新这个值
int nextPollTimeoutMillis = 0;

// 开始循环链表
for (;;) {
// nextPollTimeoutMillis不为0,证明上一次尝试处理的消息还没到时间
if (nextPollTimeoutMillis != 0) {
// 刷新native状态
Binder.flushPendingCommands();
}
// 设置下一次的轮询时间,下一次轮询的时候会唤醒MessageQueue
nativePollOnce(ptr, nextPollTimeoutMillis);

synchronized (this) {
// 因为Message是有延迟属性的,所以在队列头节点的Message不一定可以立刻处理
// 所以这个地方先读区系统时间,后面会与Message的时间进行比较
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
// message不为空,且msg.target为空,证明当前队列的第一个message是同步屏障
if (msg != null && msg.target == null) {
// 这个时候去遍历所有节点,找到第一个异步消息
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}

// 检验msg合法性
if (msg != null) {
// 校验Message的时间
if (now < msg.when) {
// 若当前时间还没到,则计算再过多长时间来再次尝试处理Message
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
// 在这个case下已经拿到了需要处理的Message,将阻塞状态设置false
mBlocked = false;
// 更新链表的头节点
if (prevMsg != null) {
prevMsg.next = msg.next;
} else {
mMessages = msg.next;
}
// 清空msg的next
msg.next = null;
if (DEBUG) Log.v(TAG, "Returning message: " + msg);
msg.markInUse();

/**
* 注意这个时候Message已经返回给Looper了,这个时候MessageQueue中
* 是非空的,也就是说后面的方法都只在每次重新开始才会调用到
*/
return msg;
}
} else {
// 队列中没有Message了,直到有新消息之前阻塞起来
nextPollTimeoutMillis = -1;
}

// mQuitting是一个boolean类型的成员
// dispose()方法就是内部是通过Native调用去销毁消息队列
// Looper通过MessageQueue.quit()方法可以将mQuitting设置为true使MessageQueue退出
if (mQuitting) {
dispose();
return null;
}

/**
* 有关IdleHandler在这里需要提一下
*
* public static interface IdleHandler {
* boolean queueIdle();
* }
* 可以看到IdleHandler其实并不是Handler,只是一个普通的接口。
*
* IdleHandler是MessageQueue中的一个接口,这个接口提供了一个额外的回调时机:
* 如果当前MessageQueue中没有消息可以执行,就会尝试去执行已经添加的IdleHandler,
* 若IdleHandler.queueIdle()返回true,则会在下次继续回调,若返回false,则会被移除
*/

// 如果当前IdleHandler计数为0,切队列中没有可以执行的消息
if (pendingIdleHandlerCount < 0
&& (mMessages == null || now < mMessages.when)) {
// 对mIdleHandlers进行计数
pendingIdleHandlerCount = mIdleHandlers.size();
}
// 没有需要处理的消息和任务,阻塞
if (pendingIdleHandlerCount <= 0) {
mBlocked = true;
continue;
}
// 第一次初始化mPendingIdleHandlers
if (mPendingIdleHandlers == null) {
mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
}
mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
}

// 遍历所有IdleHandler
for (int i = 0; i < pendingIdleHandlerCount; i++) {
final IdleHandler idler = mPendingIdleHandlers[i];
mPendingIdleHandlers[i] = null;
boolean keep = false;
try {
// 这里keep的值就是IdleHandler处理返回的值
keep = idler.queueIdle();
} catch (Throwable t) {
Log.wtf(TAG, "IdleHandler threw exception", t);
}
if (!keep) {
synchronized (this) {
// 如果IdleHandler的处理结果返回false则移除这个IdleHandler
mIdleHandlers.remove(idler);
}
}
}
pendingIdleHandlerCount = 0;
// 前面说到了,nextPollTimeoutMillis这个值代表下次尝试取出Message的时间
// 如果队列中持续有可处理的消息的话,这个值就一直是0,代表可以立刻进入下一次迭代
nextPollTimeoutMillis = 0;
}
}

可以看next()方法中的工作还是比较多的

  • 处理可以立即执行的消息
  • 如果有同步屏障则优先处理异步消息
  • 如果没有可以立即处理的消息就将队列阻塞起来
  • 阻塞的时候根据情况决定是否回调IdleHandler

退出

下面来看看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
// 由Looper调用这个方法
// 调用这个方法会将mQuitting设置为true
void quit(boolean safe) {
if (!mQuitAllowed) {
throw new IllegalStateException("Main thread not allowed to quit.");
}
synchronized (this) {
if (mQuitting) {
return;
}
// 将mQutting设置为true,这个flag将在next()方法中触发dispose()
mQuitting = true;
// safe与!safe的区别只是
// safe的情况下会触发removeAllFutureMessagesLocked()方法,只移除现在还不能处理(when > now)的Message
// !safe的情况下触发removeAllMessagesLocked(),将消息队列全部清空
if (safe) {
removeAllFutureMessagesLocked();
} else {
removeAllMessagesLocked();
}
// 唤醒MessageQueue
// 因为真正执行退出的方法是dispose(),这个方法是在next()中执行的
nativeWake(mPtr);
}
}

重要的地方需要再次强调一下,quit方法并不是立刻真正退出,而是将类成员mQuitting设置为true

Looper通过MessageQueue.next()方法对消息队列进行遍历的时候,如果遇到mQuitting == true再去执行真正的退出。

这样的好处就是,一切行为都由Looper去触发,MessageQueue只负责维护Message以及记录状态。

Looper

到目前为止我们了解了MessageMessageQueueHandler,可以知道Handler由用户根据不同情况和用法自行实例化,Message是通过Message.obtain()从消息池中获取的。

似乎到目前为止还不知道MessageQueue是由谁创建和维护的。

这个宿主就是接下来要讲的Looper

Looper的类名可以说是顾名思义,只负责一个事儿,就是loop()。在loop()的过程中通过调用MessageQueue.next()来不断地从消息队列中获取消息并处理。

构造

我们先来看看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
/**
* ThreadLocal是一个线程数据隔离的工具类
* 在这里简单提一下,ThreadLocal有一个内部类叫做ThreadLocalMap
* 这个ThreadLocalMap由每个Thread维护
* ThreadLocalMap.set(T t)方法做的事情就是以自己作为Map的key,参数作为value,存入ThreadLocalMap中
* 因为ThreadLocalMap是由每个Thread单独维护的,所以在不同的Thread中通过相同的ThreadLocal取出的数据是不同的
* 这就做到了线程数据隔离。
*/
@UnsupportedAppUsage
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
@UnsupportedAppUsage
final MessageQueue mQueue;
final Thread mThread;

public static void prepare() {
prepare(true);
}

// 可以看到Looper的构造最终调用到了
private static void prepare(boolean quitAllowed) {
// 将Looper通过ThreadLocal存储在ThreadLocalMap中
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper(quitAllowed));
}

// Looper的构造函数,就是为类变量赋值
// 到这里就解答了一部分上面的问题,MessageQueue是由Looper构造和维护的
// 在Looper实例化的时候同时实例化了一个MessageQueue
private Looper(boolean quitAllowed) {
// 创建MessageQueue
mQueue = new MessageQueue(quitAllowed);
// 获取当前Thread
mThread = Thread.currentThread();
}

关于ThreadLocal,如果想详细了解的话可以看这篇 -> 理解ThreadLocal

可以看出Looper.prepare()完成了对LooperMessageQueue的初始化,并把Looper存入ThreadLocalMap中。

循环(loop)

这里不废话了,直接上代码讲:

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
public static void loop() {
// 拿到Looper
final Looper me = myLooper();
// 检查Looper是否存在,不存在直接抛出异常
// 在子线程中Looper.prepare()并不会自动初始化,这个时候如果使用Handler向这个线程抛消息就会抛出异常
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
// ...省略
// 标记当前Looper正在运行
me.mInLoop = true;
// 从Looper中取出MessageQueue
final MessageQueue queue = me.mQueue;

// ...省略很多主线无关操作

// 开始循环
for (;;) {
/**
* 这里通过MessageQueue.next()方法从消息队列中一次取出消息
* 前面讲MessageQueue的时候已经分析过next()方法,在这里再提一下
* 在这个方法中如果当前没有任何可以处理的Message会阻塞在这里
*/
Message msg = queue.next(); // might block
// 上面这行next()执行完毕说明队列没有被阻塞,同时队列中没有Message
// 就意味着MessageQueue已经在退出了,所以这里直接return结束loop
if (msg == null) {
return;
}

// ...省略很多主线无关操作

try {
/**
* 重点在这里
* 这一行通过Message.target.dispatchMessage(msg)方法,回调至Message.target
* 也就是发送Message的Handler,同时将Message传递了回去,让Handler知道自己处理
* 的是哪一个Message
*/
msg.target.dispatchMessage(msg);
// ...省略
} catch (Exception exception) {
// ...省略
throw exception;
} finally {
ThreadLocalWorkSource.restore(origWorkSource);
if (traceTag != 0) {
Trace.traceEnd(traceTag);
}
}
// ...省略很多主线无关操作

// 最后将已经经过处理的Message回收掉
msg.recycleUnchecked();
}
}

退出

这个其实没什么好讲的了…

1
2
3
4
5
6
7
public void quit() {
mQueue.quit(false);
}

public void quitSafely() {
mQueue.quit(true);
}

稍微梳理一下

  • 在主线程启动的时候就在主线程自动启动了一个Looper
  • Looper内部实例化了一个MessageQueue用来维护消息
  • Looper不停循环通过MessageQueue.next() 方法从队列中拿出Message进行处理
  • MessageQueue.next()方法中会判断当前有没有可以处理的Message,如果没有回将整个队列阻塞起来(epoll唤醒)
  • 用户通过HandlerMessageQueue发送消息
  • Message持有Handler的引用,在被处理的时候可以通过Message.target回调至对应的Handler中进行处理

关于死循环

相信了解过Handler就一定会停过一个问题:

Looper.loop()启动了一个死循环,为什么没有阻塞主线程?

说这个问题之前我要先提个醒,这个题有坑!

如果顺着题的思路去考虑“为什么死循环没有阻塞线程”的话就完全跑偏了。

以我们的App来举例子:

  • 我们知道一个Activity如果不去主动退出的
  • 我们知道Activity是运行在自己的线程中的
  • 我们知道启动一个线程,线程的任务执行完成后会自动退出

仔细想想上面说的这三个事儿,很明显逻辑是有问题的,而问题就在Looper上。不妨思考一下,如何让一个线程不主动退出?

答案就是死循环

回到上面的问题中,可以发现这个题本身就是个大坑:

死循环不会阻塞主线程的原因是,死循环本身就是这个线程的主要任务!

再来想一想Looper是干嘛的?Looper是从MessageQueue里面取出消息并回调Handler处理的。当队列没有消息的时候证明主线程也不再需要刷新任何UI,也不需要处理任何数据,所以为了不让Looper空转导致资源浪费才有了MessageQueue的阻塞机制。

另外一个问题,整个线程处于死循环中,那么外部的事件是如何进入到MessageQueue中的呢?

其实这个问题也很容易理解,我们知道Activity全是由AMS统一维护的,Activity在启动的时候就和AMS建立了联系,如果有外部事件(触摸等)需要被处理的话,会由AMS通过App中的其他线程将Message抛到主线程的MessageQueue中。

总结

没啥可总结的了,也不是一句两句能总结完的。

这里就先把前面梳理的流程再贴一遍:

  • 在主线程启动的时候就在主线程自动启动了一个Looper
  • Looper内部实例化了一个MessageQueue用来维护消息
  • Looper不停循环通过MessageQueue.next() 方法从队列中拿出Message进行处理
  • MessageQueue.next()方法中会判断当前有没有可以处理的Message,如果没有回将整个队列阻塞起来(epoll唤醒)
  • 用户通过HandlerMessageQueue发送消息
  • Message持有Handler的引用,在被处理的时候可以通过Message.target回调至对应的Handler中进行处理

最好的总结就是翻回去再看一遍~

Over.