白话说Handler-MessageQueue-Looper

2021年03月08日 99 字 Framework


若无明示源码版本,全文以Android API23为准分析

从何开始

如何解释:我写的Java代码是如何在手机上一直运行着的?

首先,显而易见的,作为一个Java程序,一定有一个程序入口main(String[] args), 那么Android程序的入口main()在哪里呢,我们用 Source Insight查找一下,最终我们在android.app.ActivityThread中找到了它的身影。

让我们来看一下ActivityThread.main()到底干了些啥:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//{@link android.app.ActivityThread}
public static void main(String[] args) {
// ... something for environment & log

Looper.prepareMainLooper();

// init ActivityThread; ActivityManager.attachApplication
// init Instrumentation; ContextImpl.createAppContext
// context.mPackageInfo.makeApplication; application.onCreate
// and so on ...

Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}

且先不看程序是如何初始化环境以及是如何从ActivityThread开始一步步启动application的,我们都知道Java程序终有执行完的一刻,那么app是如何保证在手机不爆炸的情况下一直运行不退出的?手痒用Java JFrame、JPanel的做过小游戏的朋友应该就想到了:无限循环刷新!

可是肉眼看过去,这里别说死循环,连个for都没有,怎么肥四?

所幸,我们发现了一组特别的单词 【Looper】(翻译:打环装置)和 【loop】(翻译:循环电影胶片;重复指令),显然Looper应该就是维持程序一直运行的关键。

何为Looper

ActivityThread.main()代码不难看出,Looper.prepare()与Looper.loop()成对出现(prepareMainLooper()最终也是调用prepare()) ,要使用loop()保持循环,那么就必须先prepare(),我们且看一下Looper.prepare()做了哪些准备工作。

1
2
3
4
5
6
7
8
9
10
11
12
// {@link android.os.Looper#prepare()}
private static void prepare(boolean quitAllowed) {
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper(quitAllowed));
}

private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}

这里一共干了两件事情:

  • 从ThreadLocal中查询当前线程的Looper对象,有查到,说明已被prepare过,则抛错,由于一个线程只有一个ThreadLocal,从而保证一个线程仅会持有一个Looper
  • 否则,为当前线程配备一个Looper,而初始化Looper时,同时创建了一个MessageQueue。

那让我们看看 Looper.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
final MessageQueue mQueue;

// {@link android.os.Looper#loop()}
public static void loop() {
final Looper me = myLooper();
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
final MessageQueue queue = me.mQueue;

// something for Binder

for (;;) {
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}

// ... something for log
// 执行Handler.Callback.handleMessage
msg.target.dispatchMessage(msg);

// something for log

msg.recycleUnchecked();
}
}

不负众望,Looper.loop()确实保持着一个死循环,只要给马儿(Looper)草(Message),马儿就能一直跑! Looper.loop()的循环中主要干了三件事:

  • 从MessageQueue中取出一条Message,若Message为空则退出循环
  • Message不为空,则处理消息
  • 回收Message

至此,我们知道了Looper实现了程序的持续运行,而我们发现了一个控制Looper执行和中断的重要角色:MessageQueue和Message

MessageQueue和Message

Looper.loop()中循环通过MessageQueue.next()取出Message,MessageQueue看起来是Message的容器,MessageQueue真的仅仅是Message的容器吗?

我们先来看一下Message的数据结构:

1
2
3
4
5
6
7
8
9
10
11
12
public class Message {
// 出队时机
public long when;
// 发送/处理消息的Handler对象
Handler target;
// 下一个消息
Message next;
// 消息缓存池
private static Message sPool;

// ... other
}

我们可以看到,数据结构上 Message对象自身持有下一个Message的引用,形成单链表结构或者队列,从代码不难看出Message本身仅实现了单链表结构、缓存机制、持有消费自身的Handler。

试设想,若仅视MessageQueue为Message的容器,以Message现有的条件参与Looper的轮询以供Handler消费,Message仅能实现先入先出,若某一个Message的执行时机为一年后,那么这个Message后续的next将仅能在1年后获得被消费的机会,显然不合理,而实际情况也并非如此。那么是谁帮助Message正确的被消费呢?显然就是MessageQueue!

我们来看看MessageQueue是如何处理Message的插入和取出以及Message的分配的。

首先,我们先找寻一下MessageQueue是如何插入Message或者说获取到Message对象的,从前面Message代码可以看到,Message持有Handler对象,有用过android.os.Handler的不难联想出这行代码:

1
myHandler.sendMessage(message);

Message对象由我们生产,由Handler发送,最终在合适的时机被Handler消费。那么,Handler是怎么把Message交给MessageQueue的,MessageQueue又是在什么时机把Message交给Looper转运给Handler处理的呢?

追踪代码发现,Handler.sendMessage(message)最终是调用Handler.enqueueMessage

1
2
3
4
5
6
7
8
// {@link android.os.Handler#enqueueMessage}
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}

Handler将自己设置为Message的target,并将Message和Message执行时机传递给MessageQueue,而MessageQueue的来源则是从Handler构造方法参数Looper中取得:

1
2
3
4
5
6
7
8
9
10
11
12
13
public Handler(Callback callback, boolean async) {
//... doSomething

mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
}

搞清楚Message的来源后,我们来看看MessageQueue是如何处理收到的这个Message对象的,首先我们跟着Handler.enqueueMessage找到了MessageQueue的enqueueMessage:

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
boolean enqueueMessage(Message msg, long when) {
// ... 如果Message正在被使用或target为空则抛错

synchronized (this) {
if (mQuitting) {
msg.recycle();
return false;
}
msg.markInUse();
msg.when = when;
Message p = mMessages;
boolean needWake;
if (p == null || when == 0 || when < p.when) {
// New head, wake up the event queue if blocked.
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; // invariant: p == prev.next
prev.next = msg;
}

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

简单分析代码,得知MessageQueue执行了如下操作:

  • 检查新Message是否有消费者或是否正在被使用,若无消费者或正被使用,则抛错
  • 锁定MessageQueue,保证Message对象不被重复或错误操作
  • 检查如果MessageQueue被标记为退出,则不需处理本次插入
  • 将Message标记为正在被使用
  • 找寻新Message位置
    • 若当前为第一次添加(p == null)最优执行(when == 0)新Message执行时机先于头部Message(when < p.when),则将新Message记录为MessageQueue中的头部Message(mMessages),并将旧的头部Message设置为新Message的Next
    • 若新Message不满足与头部Message竞争的条件,则依次递进比较新Message与MessageQueue中头部Message.next的执行时机when,直到Message队列的最尾端(p == null)或者合适的位置(when < p.when),将新Message插入到该位置
  • 若当前头部Message需要被执行,则唤醒MessageQueue以向Looper提供Message对象

从上述逻辑可看出,当新的Message被传入MessageQueue时,MessageQueue会等待过往操作执行完毕并锁定其他操作(synchronized (this)),根据优先级执行插入排序逻辑将新Message放置到队列的合适位置,记录并持有队列优先级最高的头部Message对象,若头部Message需要被消费,则取消消息队列阻塞(阻塞由Message为空时无限等待产生)

把视野放回到Looper.loop(),MessageQueue中的Message是如何被取走的呢?让我们来看下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
Message next() {
int nextPollTimeoutMillis = 0;
for (;;) {
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}

nativePollOnce(ptr, nextPollTimeoutMillis);

synchronized (this) {
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
// msg.target == null 源自{@link #postSyncBarrier}
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;
if (prevMsg != null) {
prevMsg.next = msg.next;
} else {
mMessages = msg.next;
}
msg.next = null;
msg.markInUse();
return msg;
}
} else {
// No more messages.
nextPollTimeoutMillis = -1;
}

if (mQuitting) {
dispose();
return null;
}

// ... 队列为空或无可消费对象时相关逻辑
}
// ... 队列为空或无可消费对象时,状态重置、调用闲置回调
}
}

在Looper循环从MessageQueue取Message时,MessageQueue做了如下操作:

  • 若头部Massage需等待,则等待一定时间 (nativePollOnce(ptr, nextPollTimeoutMillis);)
  • 遍历队列,锁定MessageQueue对象
  • 若头部消息消费者为null,则消息为同步屏障,说明有立即执行的消息,则遍历直到找到一个需要立即执行的异步消息
  • 查找合适的可被消费的Message,若Message不为空:
    • 如果Message仍需等待,则计算需要等待的时间nextPollTimeoutMillis
    • 若Message不需要等待,则传递当前可消费Message的后续元素:
      • 若有队列前不可被消费的对象,则将next-Message作为其next
      • 若当前可消费Message为头部,则将next记录为新头部
    • 切断可消费Message与后续元素的联系,将其置为使用状态并返回
  • 查询到Message为空,若需退出则销毁MessageQueue,否则执行闲置回调、重置相关状态属性执行下次轮询,设置无限等待(当有新事件如enqueueMessage时执行唤醒)

综合MessageQueue对Message存取的操作不难总结出,MessageQueue以Message的优先级when为依据管理着Message优先级队列,并始终持有Message队列头部元素,MessageQueue既是Message队列的容器也管理着Message的入队出队顺序,保证Message稳定有序的被传递。

Handler扮演了什么角色

纵观Message的一生,生于new或缓存队列,起于Handler,存于MessageQueue,行于Looper,兜兜转转又回到了Handler,归于缓存队列,那么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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
public class MainActivity extends AppCompatActivity {
public static final String TAG = "HandlerTest";
ScheduledExecutorService executorService = new ScheduledThreadPoolExecutor(5,
new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
Thread thread = new Thread(r);
thread.setName("test-Thread");
return thread;
}
});

Handler myHandler = new MyHandler(Looper.getMainLooper());

Runnable taskRunnable = new Runnable() {
@Override
public void run() {
myHandler.sendEmptyMessage(1);
MyHandler handler = new MyHandler(subThread.getLooper());
handler.sendEmptyMessage(3);
}
};

HandlerThread subThread = new HandlerThread("test-Thread2");

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

subThread.start();
executorService.schedule(taskRunnable, 3, TimeUnit.SECONDS);

myHandler.postDelayed(new Runnable() {
@Override
public void run() {
MyHandler handler = new MyHandler(subThread.getLooper());
handler.sendEmptyMessage(2);
}
},1000);
}

static class MyHandler extends Handler {

public MyHandler(@NonNull Looper looper) {
super(looper);
}

@Override
public void handleMessage(@NonNull Message msg) {
// doSomething
Log.i(TAG, String.format("ThreadName: %s, msg.what = %s",
Thread.currentThread().getName(), msg.what));
}
}

}

代码比较简单,既:

  • 主线程初始化myHandler,在子线程中发送Message [1],最终myHandler打印出工作线程信息
  • 主线程中使用子线程的Looper初始化handler,在主线程中发送Message [2],最终handler打印出工作线程信息
  • 子线程初始化Handler,在子线程中发送Message [3],最终subHandler打印出工作线程信息

最终执行的结果是:

1
2
3
08-23 13:37:26.579 12969-12983/a.b.c I/HandlerTest: ThreadName: test-Thread2, msg.what = 2
08-23 13:37:28.578 12969-12969/a.b.c I/HandlerTest: ThreadName: main, msg.what = 1
08-23 13:37:28.578 12969-12983/a.b.c I/HandlerTest: ThreadName: test-Thread2, msg.what = 3

结果分析:

  • 子线程taskRunner中使用myHandler发送的消息1最终在主线程中被处理
  • 子线程taskRunner中使用subyHandler发送的消息3最终在子线程中被处理
  • 而主线程(myHandler.postDelayed) 中使用subHandler发送的消息2最终在子线程中被处理;

由此可见,Handler.handleMessage()执行的线程是Handler初始化所传递的Looper的工作线程,而不是执行sendMessage()的所在线程。那么,想要实现跨线程通信,只需要在响应线程中初始化Handler传入对应线程的Looper,将Handler对象交给任意工作线程都可与之实现数据交换。

Looper为动力,以MessageQueue为分拣器Handler作为跟着Message产品走的工人,将一个个Message产品,搬运至MessageQueue以品质好坏(优先级)分拣排序、动力Looper督促分拣器MessageQueue工作,优先的Message产品被送出,再由操作工Handler搬离流水线,最终完成Message产品按品质从上游运送到下游。