Android----Handler消息机制(进阶篇)

一.前言

前不久写过一篇 Android—-Handler消息机制,那篇文章只是对 Android 中的 Hanlder 机制的简单介绍,最近又重新回顾一下,发现了之前许多没有注意到的细节,因此就增加一篇 进阶篇。

Android—-Handler消息机制

二.简介

Android 中的消息机制主要是指 Handler 的运行机制和在 MessageQueue 和 Looper 的配合下 将一个线程中的任务切换到 Hanlder 所在的线程去执行。在 Android 中常常用来在子线程将更新UI 的结果传递个 UI 线程,从而在 UI 线程更新 UI 。

实际上 Handler 消息机制的作用并不仅仅是更新 UI ,还包括对 Activity 的协调执行 Activity 的生命周期等,只是在 Handler 机制中常常以 子线程更新 UI 为例子讲述其中的原理而已。至于为什么 UI 为什么不能在子线程更新,从设计的角度上看可以看下面这段。

《Android 开发艺术探索》中这样描述:
Android 的 UI 控件不是线程安全的,如果多个线程并发访问控件会导致控件的状态不可预期,这和多线程访问同一的变量是一个道理,但是控件却不能像简单的变量一样加上锁机制就可以实现线程同步,因为这会降低 UI 的访问效率。所以采用单线程的模式通过 Handler 切换可以实现 UI 的更新。

三.原理分析

下面就从 UI 线程初始化,整个消息机制开始作用,到发送一个 消息是如果作用的这个过程讲述一下 Hanlder 机制的过程。

1.初始化

我们都知道一个 App 启动的时候首先会从 Zygote 进程 fork 出一个 App 进程,启动 Binder 线程池(这是 Native 层的),然后会开启 UI 线程 ActivityThread (main 方法是 Java 层的), 但是看过源码的都知道 ActivityThread 并不是 Thread 类的子类,但是它有 main 方法,所以可以说 ActivityThread 是依附在进程的第一个线程,不需要通过继承 Thread 。

下面看 ActivityThread 的 Main 方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public static void main(String[] args) {
...
Looper.prepareMainLooper();
...
ActivityThread thread = new ActivityThread();
thread.attach(false);
...
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}

...
Looper.loop();


}

对上面的逻辑其实就很简单,分为下面步骤

  • 创建主线程的 Looper , 和 MessageQueue
  • 开启一个 Binder 线程, thread.attach(false) 。
  • 获取 UI 线程(即主线程) 的 Handler,这个主要是用于执行 Activity 的生命周期或者其他用的。
  • 开启 Looper 循环。

首先来看 Loop 的几个变量

1
2
3
4
5
6
7
8
9
10
11
12
// sThreadLocal.get() will return null unless you've called prepare().
//ThreadLocal 类型变量,保存 Looper
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

//主线程的 Looper
private static Looper sMainLooper; // guarded by Looper.class

// 消息队列
final MessageQueue mQueue;

//当前线程
final Thread mThread;

首先可以看到 ThreadLocal 类型,里面保存的是 Looper ,对于 ThreadLocal,需要知道:

ThreadLocal 不是用来解决对象共享访问问题的,而是线程本地变量,其 set 的对象作用域为当前线程内部,生命周期伴随线程执行而终止,多个线程间不共享,切记将 ThreadLocal 理解成多线程变量副本的认知是绝对错误的,没有副本这一操作, ThreadLocal 中 set 进去的对象依然是引用方式而不是复制拷贝,所以谈不上副本,所以一般不建议 ThreadLocal 的 set 参数传递共享对象;ThreadLocal 特别适合会被多线程调用框架的编写,可以很容易解决框架中当前线程本地变量的效果.

那么在 Looper 类中,有下面两个方法

1
2
3
4
5
6
7
8
9
10
11
12
13
// 
private static void prepare(boolean quitAllowed) {
// 如果不为 null,就抛错,也就是 prepare 执行调用一次。
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper(quitAllowed));
}

//通过 sThreadLocal 获取Looper
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}

对于 上面讲述的总结如下:

  • 由于 ThreadLocal ,所以每个线程只有一个 Looper
  • 每个 Looper 又对应着 一个 MessageQueue ,所以一个线程只有一个消息队列。
  • 线程内部的的 Looper 不能共享,不能访问。

回到 ActivityThread 的 Main 方法里首先会调用 prepareMainLooper

(1).prepareMainLooper
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public static void prepareMainLooper() {
//
prepare(false);
//
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
//
sMainLooper = myLooper();
}
}
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();
}

逻辑就是

  • 通过 prepare 设置线程的 Looper ,并且设置这个 Looper 不允许 quit 随便退出,这是因为这是 主线程的 Looper ,如果主线程的 Looper 退出循环,那么整个 App 就应该是退出或者崩溃了。
  • 初始消息队列,并设置当前线程。
(2) Looper.loop()

初始化完成后就开启了消息的循环

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public static void loop() {
final Looper me = myLooper();
....
final MessageQueue queue = me.mQueue;
....
for (;;) {
Message msg = queue.next(); // might block

try {
msg.target.dispatchMessage(msg);
end = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
}
msg.recycleUnchecked();
}
}

可以看到这里明显有一个死循环,不停地从 MessageQueue 中取出消息。这里就有两个问题

  • 死循环获取消息的时候 ,如果响应其他事务,比如点击?
  • 死循环会不会一直消耗资源。
对于第一个问题

对于第一个问题:可以回到 ActivityThread main 中执行 Looper.loop 方法之前

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public static void main(String[] args) {
...
Looper.prepareMainLooper();
...
//在执行 loop 循环之前首先 thread.attach(false);

ActivityThread thread = new ActivityThread();
thread.attach(false);
...
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}

...
Looper.loop();

这里 又创建了一个 ActivityThread ,并执行 thread.attach(false) ,实际上这里面开启一个 Binder 线程,并且对于主线程 的一些重要的属性 比如主线程的 Hanlder ,Instrumentation ,mInitialApplication 都是在这里初始化的,这就说明 这个 ActivityThread 的作用是通过 Binder 处理各个 Binder 信息,比如 AMS 发送来的 启动 Activity 的命令。因为 ActivityThread 的创建也是直接 在这个进程上创建的 ,所以实际上他们的 Lopper 是同一个,只不过另一个线程在执行死循环,这个 Binder 线程 用与处理各种事务。这就解答了第一个问题。

对于第二个问题:

可以参考 这里 Android消息机制2-Handler(Native层)

这个问题涉及到 Linux pipe/epoll 机制,简单说就是在主线程的 MessageQueue 没有消息时,便阻塞在 loop 的 queue.next() 中的 nativePollOnce() 方法里,此时主线程会释放 CPU 资源进入休眠状态,直到下个消息到达或者有事务发生,通过往pipe管道写端写入数据来唤醒主线程工作。这里采用的 epoll 机制,是一种 IO 多路复用机制,可以同时监控多个描述符,当某个描述符就绪(读或写就绪),则立刻通知相应程序进行读或写操作,本质同步 I/O,即读写是阻塞的。 所以说,主线程大多数时候都是处于休眠状态,并不会消耗大量CPU资源。

总结如下:
  • 首先会创建主线程的 Looper
  • 然后再开启循环前,启动一个 Binder 线程,用于处理其他事务
  • 启动 Looper 循环。

2.Handler

在 ActivityThread 中有一个 H 的变量,通过源码可以知道这也是一个 Handler ,这个 Handler 的主要作用就是分发 Binder 线程中的消息,常见的有执行 Activity 的生命周期。

1
2
3
4
5
6
7
8
private class H extends Handler {
//启动 Activity
public static final int LAUNCH_ACTIVITY = 100;
//暂停 Activity
public static final int PAUSE_ACTIVITY = 101;
...

}

Handler 的构造方法

1
2
3
4
5
6
7
8
9
10
11
public Handler(Callback callback, boolean async) {
....
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;
}

可以看到对于 Handler 的构造会自动地绑定这个线程的 Looper 和消息队列。对于主线程的 那当然就是 主线程的 MainLooper

3.发送消息

两种方式:sendMessage 和 post

(1).sendMessage( Message )
1
2
3
4
5
public final boolean sendMessage(Message msg)
{
//会去调用 延迟的方法,设置时间为 0
return sendMessageDelayed(msg, 0);
}
1
2
3
4
5
6
7
8
public final boolean sendMessageDelayed(Message msg, long delayMillis)
{
if (delayMillis < 0) {
delayMillis = 0;
}
//系统当前时间和 延迟的时间
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
1
2
3
4
5
6
//执行
public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
MessageQueue queue = mQueue;
...
return enqueueMessage(queue, msg, uptimeMillis);
}
1
2
3
4
5
6
7
8
9
//将消息添加的到 对列中
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
//在这里 msg 的 target 就被赋值,这里指向就是 handler
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}

从上面的方法一步步执行下来,在 enqueueMessage 方法中将 msg 的 target 指向 调用的 handler ,最后就会将消息添加到消息队列中。

(2)post( Runnable )
1
2
3
4
5
public final boolean post(Runnable r)
{
//将 Runable 封装成 Message
return sendMessageDelayed(getPostMessage(r), 0);
}
1
2
3
4
5
6
private static Message getPostMessage(Runnable r) {
Message m = Message.obtain();
//将 Runnable 赋给 Message 的 callback
m.callback = r;
return m;
}

post 方式中参数是一个 Runnable 对象,在 Hanlder 会被封装为一个 Message ,最后同样调用 sendMessageDelayed 方法,后续的操作就是一样的。

4.Looper 获取消息

之前我们知道 Looper 是在一个循环内获取消息的

1
2
3
4
5
6
7

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

(1)Message

首先看看 Message 这个对象的获取,对于 sendMessage 中的方法,这个 Message 是我们自己创建的,但是对于 对于 post 方法是通过 Message.obtain(); 获取的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public final class Message implements Parcelable {
private static Message sPool;
// sometimes we store linked lists of these things
/*package*/ Message next;


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();
}
}

关注于 sPool/next 这个两个 Message 的关键变量,可以看到对于 Message 的复用机制并不是通过 Map , 而是通过链表的形式服用。
image.png

在 MessageQueue 中每次添加一个消息,也是通过链表维护添加的顺序的。

1
2
3
4
5
6
7
8
9
10
boolean enqueueMessage(Message msg, long when) {

msg.when = when;
Message p = mMessages;
...
msg.next = p;
mMessages = msg;
....

}
1
2
3
4
5
6
7
8
9
10
11
//在 MessageQueue 的方法中,获取链表最末尾也就是最先添加的消息
Message next() {
Message msg = mMessages;
if (msg != null && msg.target == null) {
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
...
return msg;
(2)添加 Message 到复用链表

前面讲了 Message 的复用,对于 Message 的回收,实际上就是在 Looper 循环中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public static void loop() {
final Looper me = myLooper();
....
final MessageQueue queue = me.mQueue;
....
for (;;) {
Message msg = queue.next(); // might block

try {
msg.target.dispatchMessage(msg);
end = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
}
msg.recycleUnchecked();
}
}

最后这段代码就是回收 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
*/
void recycleUnchecked() {
// Mark the message as in use while it remains in the recycled object pool.
// Clear out all other details.
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++;
}
}
}

可以看到就是对 Message 的各种信息置 为初始,然后添加到 链表中 。

(2)延迟的 Message

在 Hanlder 是可以发送一个 延迟的 Message 给消息队列的, Hanlder 可以保证在一个相对精确的时间被执行。在 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

int pendingIdleHandlerCount = -1; // -1 only during first iteration
int nextPollTimeoutMillis = 0;

for (;;) {
//这个方法相当于线程 的wait 方法,
//第一次的时候还 是 0
nativePollOnce(ptr, nextPollTimeoutMillis);

synchronized (this) {
//获取当前的延迟时间
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
//当前的时间小于 小于消息的延迟时间
//说明还没有到时间
if (now < msg.when) {
// Next message is not ready. Set a timeout to wake up when it is ready.
//计算剩余的时间
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
}
...

if (pendingIdleHandlerCount <= 0) {
// No idle handlers to run. Loop and wait some more.
//设置阻塞
mBlocked = true;
continue;
}
}
....

//然后继续循环
//这个方法相当于线程 的wait 方法,
//这个时候 nextPollTimeoutMillis 就不 是 0 了
//进行等待,等到时间到了的时候就唤醒线程执行。
nativePollOnce(ptr, nextPollTimeoutMillis);

如果在 第一个消息的时间还没有到的时候,又有一个新的 Message 进来,那么在 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

boolean enqueueMessage(Message msg, long when) {
msg.markInUse();
msg.when = when;
Message p = mMessages;
boolean needWake;
// 新的 Message 的时间小于 前一个 Message
// 所以 将新的 Message 添加到链表的头部。
if (p == null || when == 0 || when < p.when) {

msg.next = p;
mMessages = msg;
needWake = mBlocked;
}

// We can assume mPtr != 0 because mQuitting is false.
// 唤醒线程,这个时候 新的 Message 就在链表头部
//取出来就可以直接处理,而延迟的就继续延迟。
if (needWake) {
nativeWake(mPtr);
}
}
return true;

5.处理消息

通过 3 中两种方式都可以发送一个消息,往消息队列中添加 Message ,而接着 looper 循环就会取出这个消息,对其进行分配也就是在之前讲过的 msg.target.dispatchMessage 方法。这里的 msg.target 在 3 中可以看到其实就是 Handler ,接着就看 dispatchMessage .

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public void dispatchMessage(Message msg) {
//这对应 post 方式
if (msg.callback != null) {
handleCallback(msg);
//这对应 sendMessage 方式
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}

可以看到最后就会根据之前的发送消息的方式从而选择不同的方法,如果是 post 方式就会执行 post 里面的 run 方法,如果是 sendMessage 方式就会执行 handleMessage .大致流程如图:
Handler机制.png

Hanlder 之所以能在线程间传递消息实际上还是因为,线程间是共享资源的.

0%