1.Handler 的诞生

由于 Android 采用的是单线程模式,开发者无法在子线程中更新 UI,因此在 Android 开发中,经常会在子线程中进行一些操作,当操作完成之后,将结果发送到主线程进行显示。探索其背后的模式:子线程、Handler 和主线程三者组成了生产者和消费者模式。子线程负责生产数据,主线程负责消费数据,而 Handler 负责将数据从子线程抛到主线程中。详细见下图

Handler生产者消费者模型.png

Handler 生产者消费者模型.png

2.Handler 相关的类

  • Hanlder:发送和接收消息
  • Looper:用于轮询消息队列,一个线程只能有一个 Looper
  • Message: 消息实体
  • MessageQueue: 消息队列用于存储消息和管理消息。

2.1 Looper 的创建

创建 Looper 的方法是调用 Looper.prepare () 方法或者 Looper.prepareMainLooper ()

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// threadlocal保证一个线程只有一个looper
private static void prepare(boolean quitAllowed) {
if (sThreadLocal.get() != null) { //如果prepare()被调用两次就会抛异常
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper(quitAllowed));//但Thread的map中没有looper,就创建一个
}

@Deprecated //该方法被废弃的原因是:Android帮我们调用了,自己不能调用
public static void prepareMainLooper() {
prepare(false); // 调到上述方法,创建looper
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
sMainLooper = myLooper();
}
}

2.2 创建 MessageQueue 以及与 Looper 和当前线程绑定

java
1
2
3
4
5
6
7
8
9
10
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);// 创建MessageQueue
mThread = Thread.currentThread();//与当前线程绑定
}

// quitAllowed参数子线程传入的true,主线程传入false,代表主线程不允许销毁队列
MessageQueue(boolean quitAllowed) {
mQuitAllowed = quitAllowed;
mPtr = nativeInit();
}

2.3 Looper.loop () 方法

java
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
public static void loop() {
final Looper me = myLooper(); //从threadlocal中拿到调prepare()创建的looper
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
......

me.mInLoop = true;
final MessageQueue queue = me.mQueue;

......

for (;;) {
//不断从消息队列取消息。
//queue.next()取消息的过程利用了epoll机制,当没有消息的时候阻塞,当有消息时,向管道写入一个字节数据,唤醒线程取消息。
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;//如果队列返回null,说明quit()被调用,需要退出线程
}

......
try {
// 回调抛message的Handler的dispatchMessage,最终会调到handler接收到的callback.handleMessage(msg)中或者handler重写的handleMessage(msg)中。
msg.target.dispatchMessage(msg);
......
//回收处理完的消息(Message采用了享元模式,防止不停的new Message导致内存抖动)
msg.recycleUnchecked();
}
}

2.4 Handler 的创建

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//方式1
Handler handler = new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
//处理自己抛的消息
}
};
//方式2 传入main looper或者子线程looper,通常传MainLooper
public Handler(@NonNull Looper looper) {
this(looper, null, false);
}
//方式3 传入looper和callback,消息处理在callback中
public Handler(@NonNull Looper looper, @Nullable Callback callback) {
this(looper, callback, false);
}

2.5 Message 的创建

可以直接 new Message () 但是不建议这么使用,因为如果有大量的 new Message () 然后用完了就被回收,这样会导致内存抖动。推荐的方式是使用 obtain () 系列方法。Message 使用了享元模式,内部维护了一个对象池 (最大 50 个),管理者对象的创建和销毁。

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public static Message obtain() {
synchronized (sPoolSync) {
if (sPool != null) {
Message m = sPool;
sPool = m.next; //从对象池头部取一个message
m.next = null;
m.flags = 0; // clear in-use flag
sPoolSize--;
return m;
}
}
return new Message();
}
//Message中有一系列obtain()方法,主要是完成handler绑定或者callback绑定工作,就不全列举了
public static Message obtain(Handler h) //Message和handler绑定
public static Message obtain(Handler h, Runnable callback)//Message与handler和Runnable绑定
public static Message obtain(Handler h, int what) //Message与handler和事件码绑定

2.6 Message 和 Handhler 绑定

  • 方式 1:通过上面创建 Message 时绑定

  • 方式 2:在发送消息的到时候绑定 (代码如下)

java
1
2
3
4
5
6
private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
long uptimeMillis) {
msg.target = this; //绑定handler
......
return queue.enqueueMessage(msg, uptimeMillis);
}

2.7 Handler 发送消息

如下图调用关系,Handler 发送消息的重载方法很多,但是主要只有 2 种。 sendMessage (Message) sendMessage 方法通过一系列重载方法的调用,sendMessage 调用 sendMessageDelayed,继续调用 sendMessageAtTime,继续调用 sendMessageAtTime,继续调用 enqueueMessage,继续调用 MessageQueue 的 enqueueMessage 方法,将消息保存在消息队列中。

发送消息.png

发送消息.png

2.8 Handler 消费消息

当 Looper 取出,交给 Handler 的 dispatchMessage 进行处理

我们可以看到在 dispatchMessage 方法中,message 中 callback 是一个 Runnable 对象,如果 callback 不为空,则直接调用 callback 的 run 方法,否则判断 mCallback 是否为空,mCallback 在 Handler 构造方法中初始化,在主线程通直接通过无参的构造方法 new 出来的为 null, 所以会直接执行后面的 handleMessage () 方法。

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public void dispatchMessage(@NonNull Message msg) {
if (msg.callback != null) {
//callback在message的构造方法中初始化或者使用handler.post(Runnable)时候才不为空
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {//如果callback不为空就调用callback的run()方法
return;
}
}
//如果没有Runnable,就回调handler重写的handleMessage()方法。
//注意,不管是否设置callback都会回调handleMessage()方法
handleMessage(msg);
}
}

3. 难点问题

3.1 消息加入和取出如何保证线程安全

MessageQueue 在消息入队的时候和取消息的时候都会对队列加锁,保证要么入队消息,要么出队消息。

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 消息入队
boolean enqueueMessage(Message msg, long when) {
if (msg.target == null) {
throw new IllegalArgumentException("Message must have a target.");
}
//当入队的时候对消息队列加锁
synchronized (this) {
......
}
// 消息出队
Message next() {
.....
for (;;) {
//循环等待消息
......
//消息出队过程加锁
synchronized (this) {
......
}

3.2 消息机制之同步屏障

通过上面分析,通常情况下 Handler 抛的消息会按照时间排序,然后 Looper 从头部开始一个一个取消息执行。但是有个需求是在一个小的时间范围内,handler 抛的消息需要优先执行,那我们应该如何处理呢?例如 UI 需要立马重绘,而且需要优先处理这个消息,那就要用到消息同步屏障。

MessageQueue.postSyncBarrier () // 开启同步屏障

MessageQueue.removeSyncBarrier () // 移除同步屏障

Message.setAsynchronous (true) // 设置为异步消息

3.2.1 开启同步屏障
java
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
public int postSyncBarrier() {
return postSyncBarrier(SystemClock.uptimeMillis());
}
//注意,方法调用返回一个屏障的token,到时候删除的时候会用到这个token
private int postSyncBarrier(long when) {
// Enqueue a new sync barrier token
synchronized (this) {
final int token = mNextBarrierToken++;
//从消息池中获取Message
final Message msg = Message.obtain();
msg.markInUse();

//就是这里!!!初始化Message对象的时候,并没有给target赋值,因此 target==null
msg.when = when;
msg.arg1 = token;

Message prev = null;
Message p = mMessages;

if (when != 0) {
while (p != null && p.when <= when) {
//如果开启同步屏障的时间(假设记为T)T不为0,且当前的同步消息里有时间小于T,则prev也不为null
prev = p;
p = p.next;
}
}
//根据prev是不是为null,将 msg 按照时间顺序插入到 消息队列(链表)的合适位置
if (prev != null) { // invariant: p == prev.next
msg.next = p;
prev.next = msg;
} else {
msg.next = p;
mMessages = msg;
}
return token;
}
}

此处开启同步屏障之后,从消息池获取的 Message 中 target==null。

3.2.2 处理异步消息

从下面可以看出,当消息队列开启同步屏障的时候(即标识为 msg.target == null),消息机制在处理消息的时候,优先处理异步消息。这样,同步屏障就起到了一种过滤和优先级的作用。

java
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
//MessageQueue.java

Message next() {
.....//省略一些代码
for (;;) {
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
//获取系统开机到现在的时间
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages; //当前链表的头结点

//关键!!!
//如果target==null,那么它就是屏障,需要循环遍历,一直往后找到第一个异步的消息
if (msg != null && msg.target == null) {
// Stalled by a barrier. Find the next asynchronous message in the queue.
do {
prevMsg = msg;
msg = msg.next; //=====重新给msg赋值,达到优先返回异步消息的作用===
} while (msg != null && !msg.isAsynchronous());//如果有异步消息
}
if (msg != null) {
//如果有时间到的消息就返回,否则就等待。
}
.....//省略

}

如果开启了同步屏障,在返回的 msg 中会被重新赋值,所以如果开启了同步屏障,MessageQueue 会遍历队列中是否有时间到了的异步消息,如果有就重新给 msg 赋值让他返回出去给 handler 处理。

示意图如下:

同步屏障示意图.png

同步屏障示意图.png

3.2.3 同步屏障的使用场景

在 View 更新时,draw、requestLayout、invalidate 等很多地方都调用 ViewRootImpl#scheduleTraversals(),如下

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//ViewRootImpl.java
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
//开启同步屏障
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
//发送异步消息

// Choreographer.postCallback最终会调用到postCallbackDelayedInternal(),在这里面设置为异步消息并且发送到消息队列中
// mTraversalRunnable中会移除同步屏障并且执行UI更新
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
if (!mUnbufferedInputDispatch) {
scheduleConsumeBatchedInput();
}
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
private void postCallbackDelayedInternal(int callbackType,
Object action, Object token, long delayMillis) {
if (DEBUG_FRAMES) {
Log.d(TAG, "PostCallback: type=" + callbackType
+ ", action=" + action + ", token=" + token
+ ", delayMillis=" + delayMillis);
}

synchronized (mLock) {
final long now = SystemClock.uptimeMillis();
final long dueTime = now + delayMillis;
mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);

if (dueTime <= now) {
scheduleFrameLocked(now);
} else {
Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
msg.arg1 = callbackType;
//将消息设置成异步消息并且发送到消息队列中,消息队列会根据target==null时取出异步消息执行
msg.setAsynchronous(true);
mHandler.sendMessageAtTime(msg, dueTime);
}
}
}

3.3 同步执行消息

3.3.1 runWithScissors () 分析

我们从前面的分析可以看出生产者 - 消费者模式其实是异步的执行操作。生产者生产好了商品向容器里面放,消费者只管从容器取就行,但是有这么一个场景,就是我生产的东西需要消费了才能继续生产下去,那么就需要考虑同步执行消息了。(可能举例不恰当)但是在 framework 中,WindowManagerService 初始化的时候,就会出现这样的情况,WindowManagerService 的初始化是放在显示子线程完成的,但是使用多线程来初始化服务虽然加快了初始化速度,但是也会出现一种情况就是某个服务没有初始化结束,导致其他服务运行异常的情况。因此系统提供了 Handler.runWithScissors () 这个方法来实现同步执行消息。如果任务没有执行结束,该线程不能执行其他的任务。下面我们来分析一下这个方法:

java
1
2
3
4
5
6
public final boolean runWithScissors(@NonNull Runnable r, long timeout) {
......
// 创建一个带阻塞功能的Runnable,主要是传入待执行的任务
BlockingRunnable br = new BlockingRunnable(r);
return br.postAndWait(this, timeout); // 发送任务到消息队列,并且阻塞线程
}
java
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
public boolean postAndWait(Handler handler, long timeout) {
if (!handler.post(this)) { //发送任务到消息队列
return false;
}

synchronized (this) {
if (timeout > 0) {
final long expirationTime = SystemClock.uptimeMillis() + timeout;
while (!mDone) {
long delay = expirationTime - SystemClock.uptimeMillis();
if (delay <= 0) {
return false; // timeout
}
try {
wait(delay); //有超时时间的阻塞线程
} catch (InterruptedException ex) {
}
}
} else {
while (!mDone) {
try {
wait();//阻塞线程
} catch (InterruptedException ex) {
}
}
}
}
return true;
}
java
1
2
3
4
5
6
7
8
9
10
11
// BlockingRunnable的run方法
public void run() {
try {
mTask.run(); //调用传入任务的run方法执行任务
} finally {
synchronized (this) {
mDone = true;
notifyAll(); //通知其他线程不再休眠
}
}
}
3.3.2 runWithScissors () 使用场景
java
1
2
3
4
5
6
7
8
public static WindowManagerService main(final Context context, final InputManagerService im,final boolean showBootMsgs, final boolean onlyCore, WindowManagerPolicy policy,
ActivityTaskManagerService atm, Supplier<SurfaceControl.Transaction> transactionFactory,Supplier<Surface> surfaceFactory,
Function<SurfaceSession, SurfaceControl.Builder> surfaceControlFactory) {
//使用显示子线程来初始化WindowManagerService
DisplayThread.getHandler().runWithScissors(() ->
sInstance = new WindowManagerService(context, im, showBootMsgs, onlyCore, policy,atm, transactionFactory, surfaceFactory, surfaceControlFactory), 0);
return sInstance;
}

参考文章:

同步屏障