Handler消息机制深入解析

前言

大家在日常开发中肯定用过Handler,常用的API主要有:

1
2
3
4
post(@NonNull Runnable r) 
postDelayed(@NonNull Runnable r, long delayMillis)
postAtFrontOfQueue(@NonNull Runnable r)
..........

在主线程中可以通过这些API可以进行延时操作,在子线程中可以通过这些API可以进行线程切换,把消息发送到Handler对应的线程当中去执行

关于Handler底层实现原理的话,可能大家都能脱口而出,说出以下内容:

1、每个Handler里面关联一个Looper,每个Looper里面又关联一个MessageQueue
2、当调用Handler的一些API比如post,就会往Looper.MessageQueue里面放入一个Message
3、Looper.loop()是一个死循环,循环的从MessageQueue里面取出Message执行
4、Looper里面通过ThreadLocal来保证每个Thread里面都有一个Looper副本
5、当MessageQueue空闲的时候,就会执行IdleHandler

当你回答出以上问题的时候,可能面试官并不满足此,其接着会问如下问题:

1、主线程的Looper.loop()方法是啥时候执行的?
2、Looper、MessageQueue以及Message三者交互的底层实现原理
3、Looper死循环为什么不会导致应用卡死?
4、ANR产生的原因
5、同步屏障SyncBarrier
6、onDraw里面调用invalidate为啥会导致IdleHandler得不到执行?
7、ViewAnimation循环执行为啥会导致IdleHandler得不到执行,而使用属性动画就没有这个问题?

针对以上问题,我们通过源码的方式来一一进行解答,源码基于API30版本

主线程的Looper.loop()方法是啥时候执行的?

我们都知道,每一个应用都存在于自己的虚拟机中,那么每一个应用都有自己的一个main函数,这个main函数就是ActivityThread.java的main()函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public static void main(String[] args) {
...........

Looper.prepareMainLooper();

...........
ActivityThread thread = new ActivityThread();
thread.attach(false, startSeq);

if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}

if (false) {
Looper.myLooper().setMessageLogging(new
LogPrinter(Log.DEBUG, "ActivityThread"));
}

// End of event ActivityThreadMain.
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
Looper.loop();

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

可以看出在main函数里面会通过Looper.prepareMainLooper()创建主线程的Looper,然后开始通过Looper.loop方法开启主线程的Looper循环。开启循环之后,主线程所有的代码都是运行在这个Looper里面的。

接下来进一步剖析整个Handler消息机制底层具体的一个执行过程

Looper、MessageQueue以及Message三者交互的底层实现原理

我们以Handler.post方法为入口进行讲解:

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
public final boolean post(@NonNull Runnable r) {
return sendMessageDelayed(getPostMessage(r), 0);
}

public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}

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;
}
return enqueueMessage(queue, msg, uptimeMillis);
}

private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
long uptimeMillis) {
msg.target = this;
msg.workSourceUid = ThreadLocalWorkSource.getUid();

if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}

可以看出post方法最终会走到MessageQueue.enqueueMessage(msg, uptimeMillis)方法,深入看一下:

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
boolean enqueueMessage(Message msg, long when) {
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.");
}

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();
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 {
// Inserted within the middle of the queue. Usually we don't have to wake
// up the event queue unless there is a barrier at the head of the queue
// and the message is the earliest asynchronous message in the queue.
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;
}

在分析这段代码之前,需要明确几个点:

1、Message结构体是一个链表结构,里面会有一个next指针指向下一个Message节点,每个Message在链表中的先后顺序是根据Message.when来决定的,每次来一个Message的时候会根据其when把Message插入到链表中合适的位置
2、MessageQueue里面有一个字段mBlocked,代表当前队列是否处于阻塞状态

从上面的代码可以看出,enqueueMessage方法主要分为这几步:

1、MessageQueue里面有一个mMessages变量,代表是链表的head
2、这个needWake代表是否需要进行唤醒,关于这个字段的含义,待会会结合Looper.loop方法一起讲解
3、如果现在链表是空的,或者传入的Message需要插入到队首(根据when来进行判断),那么就把链表的head设置为传入的Message,同时如果现在MessageQueue是阻塞状态,那么就需要立即唤醒
4、当不满足第3步的条件,就会把Message插入到链表合适的位置。如果现在是阻塞的情况下,队首Msg是一个同步屏障(通过p.target == null判断出是一个同步屏障)并且Msg是一个异步消息,才需要立即唤醒,相当于如果有同步屏障,那么其后续的消息都没法执行,只允许异步消息执行。
关于同步屏障以及异步消息的使用场景,稍后会专门讲解,现在只需要知道有这个概念就行

把Message放入队列里面之后,那肯定是有一个地方会把Message从队列里面取出来执行,类似于生产者消费者模式,这个取消息的逻辑就是Looper.loop(),这个方法里面会循环从队列里面取出Message来执行:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
* Run the message queue in this thread. Be sure to call
* {@link #quit()} to end the loop.
*/
public static void loop() {
final Looper me = myLooper();
..................
me.mInLoop = true;
final MessageQueue queue = me.mQueue;

..................

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

..............
msg.recycleUnchecked();
}
}

可以看出,这个loop方法里面有一个for循环,里面会调用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
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
Message next() {
// Return here if the message loop has already quit and been disposed.
// This can happen if the application tries to restart a looper after quit
// which is not supported.
final long ptr = mPtr;
if (ptr == 0) {
return null;
}
//只有最开始设置为-1
int pendingIdleHandlerCount = -1; // -1 only during first iteration
//下一次poll超时时间
int nextPollTimeoutMillis = 0;
//这里也是一个for循环
for (;;) {
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
//会阻塞调用native层的nativePollOnce方法来获取消息队列中的消息
//这个nextPollTimeoutMillis有以下3种取值:
//0,立即返回,没有阻塞
//负数,一直阻塞,直到事件发生
//正数,表示最多等待多久时间
//因此next方法最开始nextPollTimeoutMillis设置为0,马上返回,没有阻塞
nativePollOnce(ptr, nextPollTimeoutMillis);

synchronized (this) {
// Try to retrieve the next message. Return if found.
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
//这个msg.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;
} while (msg != null && !msg.isAsynchronous());
}
if (msg != null) {
if (now < msg.when) {
// Next message is not ready. Set a timeout to wake up when it is ready.
//下一个消息的时间还没到,那么就计算出一个新的nextPollTimeoutMillis
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
// Got a message.
//获得一个msg,把这个msg从队列里面移除掉,同时返回msg,打破这个for循环
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 {
// No more messages.
//没有找到消息,设置为-1,下次轮询就一直阻塞
nextPollTimeoutMillis = -1;
}

// Process the quit message now that all pending messages have been handled.
if (mQuitting) {
dispose();
return null;
}

// If first time idle, then get the number of idlers to run.
// Idle handles only run if the queue is empty or if the first message
// in the queue (possibly a barrier) is due to be handled in the future.
//由于这个pendingIdleHandlerCount只有最开始设置为-1,代表在当前for循环里面idleHandler只会执行一次
//如果队列里面没有消息或者消息还没有到执行的时间,那么就考虑idleHandler
if (pendingIdleHandlerCount < 0
&& (mMessages == null || now < mMessages.when)) {
pendingIdleHandlerCount = mIdleHandlers.size();
}
//如果没有idleHandler可执行,就阻塞,开始执行下一个轮询
if (pendingIdleHandlerCount <= 0) {
// No idle handlers to run. Loop and wait some more.
mBlocked = true;
continue;
}
//最多执行4个idleHandler
if (mPendingIdleHandlers == null) {
mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
}
mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
}

// Run the idle handlers.
// We only ever reach this code block during the first iteration.
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);
}
//这个返回值代表是否需要一直保存这个idleHandler,如果不需要保存就从列表里面移除掉
if (!keep) {
synchronized (this) {
mIdleHandlers.remove(idler);
}
}
}

// Reset the idle handler count to 0 so we do not run them again.
//设置0,下一个循环就不会再次执行了,只执行一次
pendingIdleHandlerCount = 0;

// While calling an idle handler, a new message could have been delivered
// so go back and look again for a pending message without waiting.
//当执行完idleHandler之后,队列里面可能已经有新消息了,那么就设置nextPollTimeoutMillis为0代表立即
//获取消息,无需等待
nextPollTimeoutMillis = 0;
}
}

上面的注释比较详细,我们大体来看一下:
next方法里面也是一个for循环,在这个循环的开头会调用一个native方法nativePollOnce,这个方法可能会阻塞,根据传入的nextPollTimeoutMillis值,有3种执行情况:
1、如果传入0,那么就立即返回没有阻塞
2、如果传入负数,一直阻塞,直到有事件发生,这个有事件发生其实就我们前面在enqueue方法里面看到的nativeWake方法,当调用这个nativeWake方法的话,这个nativePollOnce就会返回继续往下面执行
3、如果传入正数,表示最多等待多久时间,如果超过这个时间,这个nativePollOnce就会返回继续往下面执行

由于next方法刚开始传入的nextPollTimeoutMillis为0,因此第一次循环nativePollOnce方法就会马上返回,接着往下面走,如果获取到了msg就会返回(如果遇到了同步屏障,依然只能返回异步msg),打破这个for循环,回到looper的循环里面去。如果没有获取到msg,就会重新计算这个nextPollTimeoutMillis。然后接着往下走的话,就是执行IdleHandler的相关逻辑了,如果队列里面没有消息或者消息还没有到执行的时间,那么就考虑idleHandler,idleHandler执行完成之后继续从头开始下一轮循环

以上就是Handler消息机制的底层实现原理,涉及到Looper、MessageQueue以及Message三者之间的一个交互关系

Looper死循环为什么不会导致应用卡死?

上面说了,消息能够源源不断的执行,靠的时候Looper.loop()以及MessageQueue.next()方法里面的for循环来保证的,之所以使用循环是为了保证当前线程一直存活不退出,这个其实可以理解,毕竟主线程肯定不能说运行一段时间就自动退出的,那么主线程的死循环一直运行会不会特别消耗CPU资源导致应用卡死呢?

注意到,通过前面的分析,我们发现这里涉及到两个native方法:

1
2
private native void nativePollOnce(long ptr, int timeoutMillis); /*non-static for callbacks*/
private native static void nativeWake(long ptr);

这两个方法其实涉及到Linux pipe/epoll机制,在主线程的MessageQueue没有消息时,便阻塞在nativePollOnce()方法里,此时主线程会释放CPU资源进入休眠状态,然后通过调用nativeWake()方法,通过往pipe管道写端写入数据来唤醒主线程工作。 所以说,主线程大多数时候都是处于休眠状态,并不会消耗大量CPU资源。

ANR产生的原因

因此,真正卡死主线程操作的是在回调方法onCreate或者TouchEvent处理等操作时间过长,5s超时导致ANR,Looper.loop()本身不会导致应用卡死

同步屏障SyncBarrier

在前面讲解MessageQueue.next()方法的时候我们说过如果队首的Message是一个同步屏障,那么后续的消息都得不到执行,那么这个同步屏障是用来干嘛的呢?这个同步屏障涉及到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
@UnsupportedAppUsage
@TestApi
public int postSyncBarrier() {
return postSyncBarrier(SystemClock.uptimeMillis());
}

private int postSyncBarrier(long when) {
// Enqueue a new sync barrier token.
// We don't need to wake the queue because the purpose of a barrier is to stall it.
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;
}
}

/**
* Removes a synchronization barrier.
*
* @param token The synchronization barrier token that was returned by
* {@link #postSyncBarrier}.
*
* @throws IllegalStateException if the barrier was not found.
*
* @hide
*/
@UnsupportedAppUsage
@TestApi
public void removeSyncBarrier(int token) {
// Remove a sync barrier token from the queue.
// If the queue is no longer stalled by a barrier then wake it.
synchronized (this) {
Message prev = null;
Message p = mMessages;
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;
if (prev != null) {
prev.next = p.next;
needWake = false;
} else {
mMessages = p.next;
needWake = mMessages == null || mMessages.target != null;
}
p.recycleUnchecked();

// If the loop is quitting then it is already awake.
// We can assume mPtr != 0 when mQuitting is false.
if (needWake && !mQuitting) {
nativeWake(mPtr);
}
}
}

通过postSyncBarrier方法来建立一个同步屏障,插入到Message链表合适的位置,然后通过removeSyncBarrier来移除一个同步屏障,那么这两个方法啥时候会调用呢?通过全局搜索系统源码,发现ViewRootImpl里面有如下代码:

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
@UnsupportedAppUsage
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}

void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);

if (mProfile) {
Debug.startMethodTracing("ViewAncestor");
}

performTraversals();

if (mProfile) {
Debug.stopMethodTracing();
mProfile = false;
}
}
}

final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}

可以看出,在调用scheduleTraversals的时候,会post一个SyncBarrier建立屏障,然后调用mChoreographer.postCallback方法post一个runnable,在这个runnable里面会执行doTraversal方法,然后在这个doTraversal方法里面会removeSyncBarrier移除屏障。看到这个performTraversals大家应该比较熟悉了,这里面就就会递归进行整个界面View树的绘制

那么问题来了,这里为啥要使用同步屏障呢?post到Choreographer的runnable啥时候执行呢?为了一探究竟,我们继续深入看下:

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
@UnsupportedAppUsage
@TestApi
public void postCallback(int callbackType, Runnable action, Object token) {
postCallbackDelayed(callbackType, action, token, 0);
}

@UnsupportedAppUsage
@TestApi
public void postCallbackDelayed(int callbackType,
Runnable action, Object token, long delayMillis) {
if (action == null) {
throw new IllegalArgumentException("action must not be null");
}
if (callbackType < 0 || callbackType > CALLBACK_LAST) {
throw new IllegalArgumentException("callbackType is invalid");
}

postCallbackDelayedInternal(callbackType, action, token, delayMillis);
}

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;
msg.setAsynchronous(true);
mHandler.sendMessageAtTime(msg, dueTime);
}
}
}

可以发现传入的action会先放入到mCallbackQueues保存起来,然后进入到了scheduleFrameLocked方法:

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
private void scheduleFrameLocked(long now) {
if (!mFrameScheduled) {
mFrameScheduled = true;
if (USE_VSYNC) {
if (DEBUG_FRAMES) {
Log.d(TAG, "Scheduling next frame on vsync.");
}

// If running on the Looper thread, then schedule the vsync immediately,
// otherwise post a message to schedule the vsync from the UI thread
// as soon as possible.
if (isRunningOnLooperThreadLocked()) {
scheduleVsyncLocked();
} else {
Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_VSYNC);
msg.setAsynchronous(true);
mHandler.sendMessageAtFrontOfQueue(msg);
}
} else {
final long nextFrameTime = Math.max(
mLastFrameTimeNanos / TimeUtils.NANOS_PER_MS + sFrameDelay, now);
if (DEBUG_FRAMES) {
Log.d(TAG, "Scheduling next frame in " + (nextFrameTime - now) + " ms.");
}
Message msg = mHandler.obtainMessage(MSG_DO_FRAME);
msg.setAsynchronous(true);
mHandler.sendMessageAtTime(msg, nextFrameTime);
}
}
}

这个USE_VSYNC指的是垂直同步,关于什么是垂直同步,这里不做深入讲解,只需要知道是一种屏幕刷新机制就行。如果使用垂直同步的话,就可以发现进入到了scheduleVsyncLocked方法:

1
2
3
4
5
6
7
/**
* Schedules a single vertical sync pulse to be delivered when the next
* display frame begins.
*/
private void scheduleVsyncLocked() {
mDisplayEventReceiver.scheduleVsync();
}

根据注释,这个方法其实就是在下一帧开始的时候安排一个垂直同步脉冲,然后会在FrameDisplayEventReceiver.onVsync收到回调:

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
/**
* Called when a vertical sync pulse is received.
* The recipient should render a frame and then call {@link #scheduleVsync}
* to schedule the next vertical sync pulse.
*
* @param timestampNanos The timestamp of the pulse, in the {@link System#nanoTime()}
* timebase.
* @param physicalDisplayId Stable display ID that uniquely describes a (display, port) pair.
* @param frame The frame number. Increases by one for each vertical sync interval.
*/
public void onVsync(long timestampNanos, long physicalDisplayId, int frame) {
// Post the vsync event to the Handler.
// The idea is to prevent incoming vsync events from completely starving
// the message queue. If there are no messages in the queue with timestamps
// earlier than the frame time, then the vsync event will be processed immediately.
// Otherwise, messages that predate the vsync event will be handled first.
long now = System.nanoTime();
if (timestampNanos > now) {
Log.w(TAG, "Frame time is " + ((timestampNanos - now) * 0.000001f)
+ " ms in the future! Check that graphics HAL is generating vsync "
+ "timestamps using the correct timebase.");
timestampNanos = now;
}

if (mHavePendingVsync) {
Log.w(TAG, "Already have a pending vsync event. There should only be "
+ "one at a time.");
} else {
mHavePendingVsync = true;
}

mTimestampNanos = timestampNanos;
mFrame = frame;
Message msg = Message.obtain(mHandler, this);
msg.setAsynchronous(true);
mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS);
}

从方法的注释可以看出,当一个垂直同步脉冲达到的时候就会回调这个方法,在这个方法里面会render frame,然后调用scheduleVsync开始安排下一个垂直同步脉冲。

从上面代码可以看出,在onVsync这个方法里面, 会发送一个异步的Message,前面我们说过,设置了同步屏障之后,只允许异步的Message得到执行,我们来看下这个Message的callBack执行代码:

1
2
3
4
5
@Override
public void run() {
mHavePendingVsync = false;
doFrame(mTimestampNanos, mFrame);
}

在这里会执行doFrame方法:

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
void doFrame(long frameTimeNanos, int frame) {
final long startNanos;
synchronized (mLock) {
if (!mFrameScheduled) {
return; // no work to do
}
............
long intendedFrameTimeNanos = frameTimeNanos;
startNanos = System.nanoTime();
final long jitterNanos = startNanos - frameTimeNanos;
if (jitterNanos >= mFrameIntervalNanos) {
final long skippedFrames = jitterNanos / mFrameIntervalNanos;
if (skippedFrames >= SKIPPED_FRAME_WARNING_LIMIT) {
Log.i(TAG, "Skipped " + skippedFrames + " frames! "
+ "The application may be doing too much work on its main thread.");
}
final long lastFrameOffset = jitterNanos % mFrameIntervalNanos;
if (DEBUG_JANK) {
Log.d(TAG, "Missed vsync by " + (jitterNanos * 0.000001f) + " ms "
+ "which is more than the frame interval of "
+ (mFrameIntervalNanos * 0.000001f) + " ms! "
+ "Skipping " + skippedFrames + " frames and setting frame "
+ "time to " + (lastFrameOffset * 0.000001f) + " ms in the past.");
}
frameTimeNanos = startNanos - lastFrameOffset;
}

if (frameTimeNanos < mLastFrameTimeNanos) {
if (DEBUG_JANK) {
Log.d(TAG, "Frame time appears to be going backwards. May be due to a "
+ "previously skipped frame. Waiting for next vsync.");
}
scheduleVsyncLocked();
return;
}

if (mFPSDivisor > 1) {
long timeSinceVsync = frameTimeNanos - mLastFrameTimeNanos;
if (timeSinceVsync < (mFrameIntervalNanos * mFPSDivisor) && timeSinceVsync > 0) {
scheduleVsyncLocked();
return;
}
}

mFrameInfo.setVsync(intendedFrameTimeNanos, frameTimeNanos);
mFrameScheduled = false;
mLastFrameTimeNanos = frameTimeNanos;
}
try {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "Choreographer#doFrame");
AnimationUtils.lockAnimationClock(frameTimeNanos / TimeUtils.NANOS_PER_MS);

mFrameInfo.markInputHandlingStart();
doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos);

mFrameInfo.markAnimationsStart();
doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos);
doCallbacks(Choreographer.CALLBACK_INSETS_ANIMATION, frameTimeNanos);

mFrameInfo.markPerformTraversalsStart();
doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos);

doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos);
} finally {
AnimationUtils.unlockAnimationClock();
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}

可以看出,在这个doFrame方法里面,会先判断如果出现了丢帧现象就会调用scheduleVsyncLocked方法开始安排下一个垂直同步脉冲,否则就会把mFrameScheduled设置为false,停止监听这个Vsync信号。虽然系统会每隔16.6ms执行一次屏幕刷新,但是app上层不一定会监听这个Vsync事件,只有是调用了scheduleFrameLocked方法才会开始监听这个Vsync信号,app上层才会收到回调。比如界面需要进行刷新了,才会调用scheduleFrameLocked方法来监听屏幕刷新信号,遍历绘制View树来重新计算屏幕数据。如果界面一直不需要进行刷新,那么app上层就不会去接收每隔16.6ms回调的屏幕刷新信号了。

然后继续往下走的话会执行doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos)代码,这个代码里面最终会执行最开始说的TraversalRunnable来进行UI刷新,然后会调用removeSyncBarrier解除屏障。

同时,我们再来回顾一下前面分析的MessageQueue.next方法里面的一段代码:

1
2
3
4
5
6
7
8
9
10
11
12
// If first time idle, then get the number of idlers to run.
// Idle handles only run if the queue is empty or if the first message
// in the queue (possibly a barrier) is due to be handled in the future.
if (pendingIdleHandlerCount < 0
&& (mMessages == null || now < mMessages.when)) {
pendingIdleHandlerCount = mIdleHandlers.size();
}
if (pendingIdleHandlerCount <= 0) {
// No idle handlers to run. Loop and wait some more.
mBlocked = true;
continue;
}

有同步屏障的情况下,这个mMessage就是这个同步屏障Message ,这个now肯定是要大于mMessages.when的,因为同步屏障是在scheduleTraversals方法里面就加入了,因此如果屏障不解除,那么idle就永远得不到执行!

总结一下:

1、从以上的分析过程来看,使用同步屏障主要是与Vsync配合使用来做屏幕刷新的,开始安排一个Vsync的时候设置一个同步屏障,只有当收到Vsync回调的时候才会解除屏障。
2、这里使用同步屏障的目的主要是为了保证收到Vsync信号的时候能够第一时间响应遍历绘制View树的工作,不然就会造成丢帧现象
3、从以上分析可以进一步看出:当我们调用invalidate等刷新界面的时候,并不是马上就会执行UI刷新操作的,而是先通过ViewRootImpl的scheduleTraversals方法向底层注册监听下一个垂直同步信号,等这个垂直同步信号来了之后,才会通过performTraversals方法来刷新界面
4、其实以上分析就是Android的屏幕刷新机制

onDraw里面调用invalidate为啥会导致IdleHandler得不到执行?

当我们在onDraw方法里面调用invalidate方法的时候,会调用到ViewRootImpl的scheduleTraversals方法,里面会发送一个同步屏障,然后收到onVsync回调最终执行到TraversalRunnable的时候会解除屏障,同时调用performTraversals方法,这个方法里面又会调用onDraw方法,从而形成死循环,导致屏障刚被解除马上又发送了一个新的屏障,这样idle一直得不到执行。

因此为了避免这种case,尽量不要在onDraw方法里面调用invalidate方法!

ViewAnimation循环执行为啥会导致IdleHandler得不到执行,而使用属性动画就没有这个问题?

ViewAnimation底层也是通过调用invalidate来实现的,无限循环动画就会导致无限调用invalidate,就会导致idle得不到执行。

属性动画的实现原理不同于View动画。View动画的每一帧都是通过invalidate方法来触发重绘,而属性动画每一帧的绘制都是通过Choreographer的回调实现。因此,本质上来说,属性动画少了一个很重要的步骤,就是post一个同步屏障。在属性动画中,没有同步屏障,那么后续的任务能够继续执行,当队列中没有任务时,自然就会回调IdleHandler了。

关于属性动画的底层实现原理,后续有机会会进行分析

总结

本篇文章主要是从源码的角度分析了Handler消息机制的底层实现原理,包括阻塞与同步、同步屏障、android屏幕刷新机制、idleHandler得不到执行的问题等,希望通过阅读本篇文章,能够对Handler消息机制有一个全新的认识,能够达到知其然且知其所以然