0%

Handler Again

前言

关于 Android Handler 机制更多的理解和思考。

多年之前曾写过一篇 终于明白了Handler的运行机制 的博文。现在回过头来再看,其理解之肤浅和偏差实在是惨不忍睹。因此,这里就 Android 最核心的 Handler 机制做一次新的分析和理解。

Handler N 问

鉴于 Handler 的使用已经是非常熟悉了,使用方式就不再赘述。本文就从一些对 Handler 的问题出发,通过对这些问题的解答再次探索 Handler 的奥妙所在。

以下所有 Android Framework 层 Handler 相关的源码均以 Android SDK 30 (Android 11 R) 版本为准

有些问题可能看起来很简,这里提出是因为对这个问题的答案之前理解有偏差或完全错误,因此在此做一次更正

发送的消息到底会在哪里执行?

dispatchMessage
1
2
3
4
5
6
7
8
9
10
11
12
public void dispatchMessage(@NonNull Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}

我们知道在 Looper 的 loop 方法,每当获取到一个 Message 消息的时候,就会调用 msg.target.dispatchMessage(msg) 开始上述方法的调用。因此,loop 方法执行的线程就是 Message 被处理的线程。

我们看下面的例子,我们分别创建了两个 Handler。 h 是用主线程的 Looper。subHandler 是通过 HandlerThread 的机制在子线程创建的。
然后我们分别在子线程和 UI 线程向这两个 Handler 发送了消息,并在其 dispatchMessage 方法中打印线程名及 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
25
26
27
28
29
30
31
32
33
34
35
36
37
        viewBinding.handler.setOnClickListener {
val h = Handler(Looper.getMainLooper()) { msg ->
Log.e(
TAG,
"handleMessage() called in ${Thread.currentThread().name} with: msg = $msg"
)
true
}

val handlerThread = HandlerThread("subThread")
handlerThread.start()
val subHandler = Handler(handlerThread.looper) { msg ->
Log.e(
TAG,
"handleMessage() called in ${Thread.currentThread().name} with: msg = $msg"
)
// 为了方便调试多次方法,正常情况下,用完后记得立即关闭
// handlerThread.quitSafely()
true
}

Thread {
val msg1 = Message.obtain()
msg1.what = 1000
msg1.obj = "1000"
h.sendMessage(msg1)

val msg2 = Message.obtain()
msg2.what = 2000
msg2.obj = "2000"
subHandler.sendMessage(msg2)
}.start()


h.sendEmptyMessageDelayed(100,1000)
subHandler.sendEmptyMessageDelayed(200, 1000)
}

为了让 子线程的消息优先处理,这里主线程发送消息 delay 了 1000ms.

Logcat

1
2
3
4
5
6
7
8
9
10
E: handleMessage() called in subThread with: msg = { when=0 what=2000 obj=2000 target=android.os.Handler }
E: handleMessage() called in main with: msg = { when=-5ms what=1000 obj=1000 target=android.os.Handler }
E: handleMessage() called in subThread with: msg = { when=-2ms what=200 target=android.os.Handler }
E: handleMessage() called in main with: msg = { when=-2ms what=100 target=android.os.Handler }


E: handleMessage() called in subThread with: msg = { when=0 what=2000 obj=2000 target=android.os.Handler }
E: handleMessage() called in main with: msg = { when=-2ms what=1000 obj=1000 target=android.os.Handler }
E: handleMessage() called in main with: msg = { when=-4ms what=100 target=android.os.Handler }
E: handleMessage() called in subThread with: msg = { when=-4ms what=200 target=android.os.Handler }
  • 向 h 这个 Handler 发送的消息,无论是从主线程还是子线程发送,都是在 main Thread 执行。
  • 向 subHander 这个 Handler 发送到消息,无论是从主线程还是子线程发送,都是在 subThread 执行,即子线程发送的消息不一定要到主线程执行。

也就是说消息执行的线程只和创建 Handler 时 Looper 的线程有关,即 Looper.prepare 是在哪个线程调用的,MessageQueue 以及后续 dispatchMessage 的调用都将在该线程。

这里消息被处理的顺序也很有意思,在子线程发送的两个消息,subThread 总是优先处理消息,即便他是后执行的。可以先想一下原因,后面会有解释。

为啥不会阻塞主线程?

loop
1
2
3
4
5
6
7
8
9
10
public static void loop() {

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

在 Looper.loop 方法中取消息的时候,queue.next() 是阻塞操作,那么为什么不会造成主线程卡死呢?(面试经典问题 为什么主线程的Looper是一个死循环,但是却不会ANR?

这个问题细分的话可以由很多点可以讨论,比如 ANR 是什么?在 Android 中哪些情况下会 ANR ?
因为死循环,所以 ANR ? 这个问题本身的问法可能就有点问题。
具体细节可以参考知乎问题 Android中为什么主线程不会因为Looper.loop()里的死循环卡死?。里面的每一个回答基本都值得一看,从不同角度阐述了对这个问题的理解。

我们可以仔细看一下 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
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

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

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

int pendingIdleHandlerCount = -1; // -1 only during first iteration
int nextPollTimeoutMillis = 0;
for (;;) {
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}

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;
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 = (int) Math.min(msg.when - now, Integer.MAX_VALUE); // ②
} else {
// Got a message.
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.
nextPollTimeoutMillis = -1; // ④
}

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

这里的关键是 nativePollOnce 这个 native 方法的调用。 按照 深入理解Android 卷 三 一文的解读。当 nextPollTimeoutMillis 等于 0 的是这里会立即返回,等于 -1 的时候会一直阻塞等待,直到有事件发生为止。

因此,每次调用 next 方法时,由于 nextPollTimeoutMillis = 0,会立即进入 synchronized 代码块去获取消息,这里就有以下这些情况。

  • 获取到了一条消息 message
    • message 执行的时间 when 大于等于当前时间,那就立刻返回这条消息给 Looper.loop 方法去消费。
    • message 执行的时间 when 小于当前时间,那么就和当前时间算一个时间差,并赋值给 nextPollTimeoutMillis
  • 没有获取到消息
    • nextPollTimeoutMillis 设置为 -1
  • 已经执行 Looper.quit 方法了,那么就返回一条 null 消息,Looper.loop 拿到 null 也就停止了。

到这里就解析通了,面对真实场景,拿到消息时间 OK 就的话返回给 Looper,由 Looper 执行 msg.target.dispatchMessage(msg) ,完成消息相应的逻辑。没有消息就一直等待着。nativePollOnce 是个阻塞操作,但是会释放 CPU 。

那么问题来了,如果 nextPollTimeoutMillis == -1 的情况下,一直在这里阻塞。那么谁来唤醒呢?这就要靠 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
boolean enqueueMessage(Message msg, long when) {

synchronized (this) {

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

可以看到在最后,如果需要调用 nativeWake(mPtr),就会唤醒由 nativePollOnce 阻塞的逻辑。一般情况下当有消息处理时,mBlocked 都是 false。 没有消息可以处理(甚至连 IdleHandler 也没有的时候才会设置为 true) 。

Message next() 从队列中获取并返回下一个消息.

如果队列为空(无返回值), 则该方法将调用 native void nativePollOnce(long, int), 该方法将一直阻塞直到添加新消息为止. 此时,您可能会问nativePollOnce 如何知道何时醒来. 这是一个很好的问题. 当将 Message 添加到队列时, 框架调用 enqueueMessage 方法, 该方法不仅将消息插入队列, 而且还会调用native static void nativeWake(long).

nativePollOnce 和 nativeWake 的核心魔术发生在 native 代码中. native MessageQueue 利用名为 epoll 的 Linux 系统调用, 该系统调用可以监视文件描述符中的 IO 事件. nativePollOnce 在某个文件描述符上调用 epoll_wait, 而 nativeWake 写入一个 IO 操作到描述符, epoll_wait 等待. 然后, 内核从等待状态中取出 epoll 等待线程, 并且该线程继续处理新消息. 如果您熟悉 Java 的 Object.wait()和 Object.notify()方法,可以想象一下 nativePollOnce 大致等同于 Object.wait(), nativeWake 等同于 Object.notify(),但它们的实现完全不同: nativePollOnce 使用 epoll, 而 Object.wait 使用 futex Linux 调用.

值得注意的是, nativePollOnce 和 Object.wait 都不会浪费 CPU 周期, 因为当线程进入任一方法时, 出于线程调度的目的, 该线程将被禁用(引用Object类的javadoc). 但是, 某些事件探查器可能会错误地将等待 epoll 等待(甚至是 Object.wait)的线程识别为正在运行并消耗 CPU 时间, 这是不正确的. 如果这些方法实际上浪费了 CPU 周期, 则所有空闲的应用程序都将使用 100% 的 CPU, 从而加热并降低设备速度.

为什么使用 SystemClock.uptimeMillis() ?

如果你足够仔细的话,你会发现,在 Handler 机制中,关于 Message.when 这个属性的赋值以及和这个属性的比较时,获取时间的方法都是 SystemClock.uptimeMillis() 。那么这个事件和我们非常熟悉的 System.currentTimeMillis() 有什么区别呢?

SystemClock.uptimeMillis()
1
2
3
4
5
6
7
/**
* Returns milliseconds since boot, not counting time spent in deep sleep.
*
* @return milliseconds of non-sleep uptime since boot.
*/
@CriticalNative
native public static long uptimeMillis();
System.currentTimeMillis
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
* Returns the current time in milliseconds. Note that
* while the unit of time of the return value is a millisecond,
* the granularity of the value depends on the underlying
* operating system and may be larger. For example, many
* operating systems measure time in units of tens of
* milliseconds.
*
* <p> See the description of the class <code>Date</code> for
* a discussion of slight discrepancies that may arise between
* "computer time" and coordinated universal time (UTC).
*
* @return the difference, measured in milliseconds, between
* the current time and midnight, January 1, 1970 UTC.
* @see java.util.Date
*/
@CriticalNative
public static native long currentTimeMillis();

注释很清楚了,SystemClock.uptimeMillis() 返回的系统开机之后累计的 milliseconds。并且在系统处于 deep sleep 的时候不会及时。而 System.currentTimeMillis() 都很熟悉了,这个方法返回的是当前时间与协调世界时 1970 年 1 月 1 日午夜之间的时间差(以毫秒为单位测量),而且非常依赖操作系统。再有如果手动修改了操作系统的时间,这个值就没有意义了。因此,对于 Handler 已 Message 的时间为序的消费场景(这里简单指所有的同步消息,包含一部消息和屏障消息时就有差异了),是不可靠的。因此,使用了以开机时间的一个计数器这样的时间来作为所有 Message.whne 的时间标准。

Handler 有什么新的变化吗?

再次读 Handler 的源码,发现的确是有了一些变化,但是不知道是从哪个版本开始的。

构造函数的变化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
* Default constructor associates this handler with the {@link Looper} for the
* current thread.
*
* If this thread does not have a looper, this handler won't be able to receive messages
* so an exception is thrown.
*
* @deprecated Implicitly choosing a Looper during Handler construction can lead to bugs
* where operations are silently lost (if the Handler is not expecting new tasks and quits),
* crashes (if a handler is sometimes created on a thread without a Looper active), or race
* conditions, where the thread a handler is associated with is not what the author
* anticipated. Instead, use an {@link java.util.concurrent.Executor} or specify the Looper
* explicitly, using {@link Looper#getMainLooper}, {link android.view.View#getHandler}, or
* similar. If the implicit thread local behavior is required for compatibility, use
* {@code new Handler(Looper.myLooper())} to make it clear to readers.
*
*/
@Deprecated
public Handler() {
this(null, false);
}

可以看到,现在创建 Handler 必须显示的提供 Looper 了。原因在上面注释里也解释的很清楚了。Android 官方也是被逼无奈啊。

异步消息的支持

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@UnsupportedAppUsage
public Handler(@NonNull Looper looper, @Nullable Callback callback, boolean async) {
mLooper = looper;
mQueue = looper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
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);
}

在之前的版本中,由于构造函数调用的限制,应用层的开发者只能通过 Handler 发送同步消息。mAsynchronous 参数无论用什么方式创建 Handler 都会默认设置为 false。因此在 enqueueMessage 阶段,消息只能是同步的。

现在提供了 createAsync 方法,mAsynchronous 会设置为 true。也就是说我们可以发送异步消息了。当然为了兼容性考虑,建议使用 HandlerCompat 创建此类消息。

那么,异步消息有什么用呢?下面就来解释

异步消息、同步消息、屏障消息是啥?

同步消息和异步消息的区别是通过 msg.setAsynchronous() 方法设置。那么同步消息和异步消息在 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
/**
* Posts a synchronization barrier to the Looper's message queue.
*
* Message processing occurs as usual until the message queue encounters the
* synchronization barrier that has been posted. When the barrier is encountered,
* later synchronous messages in the queue are stalled (prevented from being executed)
* until the barrier is released by calling {@link #removeSyncBarrier} and specifying
* the token that identifies the synchronization barrier.
*
* This method is used to immediately postpone execution of all subsequently posted
* synchronous messages until a condition is met that releases the barrier.
* Asynchronous messages (see {@link Message#isAsynchronous} are exempt from the barrier
* and continue to be processed as usual.
*
* This call must be always matched by a call to {@link #removeSyncBarrier} with
* the same token to ensure that the message queue resumes normal operation.
* Otherwise the application will probably hang!
*
* @return A token that uniquely identifies the barrier. This token must be
* passed to {@link #removeSyncBarrier} to release the barrier.
*
* @hide
*/
@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;
}
}

屏障消息有两个特点,1. 其 target 为 null 2. message.what = token 。注释已经解析的很清楚了,屏障消息会屏蔽同步消息的执行,优先会执行异步消息。除非同步屏障被移除了。移除的时候,就要依赖添加的时候生成的 token。也就是说通过同步屏障的机制,给 Handler 消息执行机制提供了一种优先级的概念。

MessageQueue.next()
1
2
3
4
5
6
7
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());
}

可以看到在 next() 方法中遇到 target 为 null 的同步屏障消息,遇到同步消息会一直往后找,直到找到异步消息或到链表末尾。

HandlerThread 是啥?

UI 线程在 ActivityThread.main 方法中创建了 Looper并开始了 loop 循环,保证了 UI 线程 Handler 正常运行。为了方便在子线程中方便的使用 Handler 机制,官方简单封装 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
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
public class HandlerThread extends Thread {
int mPriority;
int mTid = -1;
Looper mLooper;
private @Nullable Handler mHandler;

public HandlerThread(String name) {
super(name);
mPriority = Process.THREAD_PRIORITY_DEFAULT;
}

/**
* Constructs a HandlerThread.
* @param name
* @param priority The priority to run the thread at. The value supplied must be from
* {@link android.os.Process} and not from java.lang.Thread.
*/
public HandlerThread(String name, int priority) {
super(name);
mPriority = priority;
}

/**
* Call back method that can be explicitly overridden if needed to execute some
* setup before Looper loops.
*/
protected void onLooperPrepared() {
}

@Override
public void run() {
mTid = Process.myTid();
Looper.prepare();
synchronized (this) {
mLooper = Looper.myLooper();
notifyAll();
}
Process.setThreadPriority(mPriority);
onLooperPrepared();
Looper.loop();
mTid = -1;
}

/**
* This method returns the Looper associated with this thread. If this thread not been started
* or for any reason isAlive() returns false, this method will return null. If this thread
* has been started, this method will block until the looper has been initialized.
* @return The looper.
*/
public Looper getLooper() {
if (!isAlive()) {
return null;
}

// If the thread has been started, wait until the looper has been created.
synchronized (this) {
while (isAlive() && mLooper == null) {
try {
wait();
} catch (InterruptedException e) {
}
}
}
return mLooper;
}
}

HandlerThread 本质上还是一个 Thread。在其内部使用 wait 和 notify 机制。保证了在调用线程的 start 方法后,一定可以获取的到正常的和当前线程绑定的 Looper。

利用 Handler 机制在性能优化时可以做哪些事情?

这个最有名的就是 BlockCanary了。

利用 Looper.loop

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public static void loop() {
...
for (;;) {
...
//默认为null,可通过setMessageLogging()方法来指定输出,用于debug功能
Printer logging = me.mLogging;
if (logging != null) {
//事件分发之前的时间T1
logging.println(">>>>> Dispatching to " + msg.target + " " +
msg.callback + ": " + msg.what);
}
msg.target.dispatchMessage(msg);
if (logging != null) {
//事件分发之后的时间T2
logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
}
...
}
}

可以看到在消息处理的前后进行了日志打印,如果T2-T1的时间差大于某个阀值,就可以判断发生了卡顿。

我们可以个之前创建的两个 Handler 添加自定义的 Logger .handlerThread.looper.setMessageLogging(LogPrinter(Log.DEBUG, "ActivityThread"))

日志输出
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
D/ActivityThread: >>>>> Dispatching to Handler (android.view.Choreographer$FrameHandler) {ff72a60} android.view.Choreographer$FrameDisplayEventReceiver@1667b19: 0
D/ActivityThread: <<<<< Finished to Handler (android.view.Choreographer$FrameHandler) {ff72a60} android.view.Choreographer$FrameDisplayEventReceiver@1667b19
D/ActivityThread: >>>>> Dispatching to Handler (android.view.Choreographer$FrameHandler) {ff72a60} android.view.Choreographer$FrameDisplayEventReceiver@1667b19: 0
D/ActivityThread: <<<<< Finished to Handler (android.view.Choreographer$FrameHandler) {ff72a60} android.view.Choreographer$FrameDisplayEventReceiver@1667b19
D/ActivityThread: >>>>> Dispatching to Handler (android.view.Choreographer$FrameHandler) {ff72a60} android.view.Choreographer$FrameDisplayEventReceiver@1667b19: 0
D/ActivityThread: <<<<< Finished to Handler (android.view.Choreographer$FrameHandler) {ff72a60} android.view.Choreographer$FrameDisplayEventReceiver@1667b19
D/ActivityThread: >>>>> Dispatching to Handler (android.view.Choreographer$FrameHandler) {ff72a60} android.view.Choreographer$FrameDisplayEventReceiver@1667b19: 0
D/ActivityThread: <<<<< Finished to Handler (android.view.Choreographer$FrameHandler) {ff72a60} android.view.Choreographer$FrameDisplayEventReceiver@1667b19
D/ActivityThread: >>>>> Dispatching to Handler (android.view.Choreographer$FrameHandler) {ff72a60} android.view.Choreographer$FrameDisplayEventReceiver@1667b19: 0
D/ActivityThread: <<<<< Finished to Handler (android.view.Choreographer$FrameHandler) {ff72a60} android.view.Choreographer$FrameDisplayEventReceiver@1667b19
D/ActivityThread: >>>>> Dispatching to Handler (android.view.Choreographer$FrameHandler) {ff72a60} android.view.Choreographer$FrameDisplayEventReceiver@1667b19: 0
D/ActivityThread: <<<<< Finished to Handler (android.view.Choreographer$FrameHandler) {ff72a60} android.view.Choreographer$FrameDisplayEventReceiver@1667b19
D/ActivityThread: >>>>> Dispatching to Handler (android.view.ViewRootImpl$ViewRootHandler) {b1ebc0b} android.view.View$PerformClick@b011f92: 0
D/ActivityThread: <<<<< Finished to Handler (android.view.ViewRootImpl$ViewRootHandler) {b1ebc0b} android.view.View$PerformClick@b011f92
D/ActivityThread: >>>>> Dispatching to Handler (android.os.Handler) {aa95e90} null: 2000
D/ActivityThread: >>>>> Dispatching to Handler (android.view.ViewRootImpl$ViewRootHandler) {b1ebc0b} android.view.View$UnsetPressedState@2b3dd60: 0
D/ActivityThread: <<<<< Finished to Handler (android.os.Handler) {aa95e90} null
D/ActivityThread: <<<<< Finished to Handler (android.view.ViewRootImpl$ViewRootHandler) {b1ebc0b} android.view.View$UnsetPressedState@2b3dd60
D/ActivityThread: >>>>> Dispatching to Handler (android.os.Handler) {84d5389} null: 1000
D/ActivityThread: <<<<< Finished to Handler (android.os.Handler) {84d5389} null
D/ActivityThread: >>>>> Dispatching to Handler (android.view.Choreographer$FrameHandler) {ff72a60} android.view.Choreographer$FrameDisplayEventReceiver@1667b19: 0
D/ActivityThread: <<<<< Finished to Handler (android.view.Choreographer$FrameHandler) {ff72a60} android.view.Choreographer$FrameDisplayEventReceiver@1667b19
D/ActivityThread: >>>>> Dispatching to Handler (android.os.Handler) {e85c3af} android.graphics.animation.-$$Lambda$awqPSgriNRe12PWP0zkpAtPsfV4@8edd3bc: 0
D/ActivityThread: <<<<< Finished to Handler (android.os.Handler) {e85c3af} android.graphics.animation.-$$Lambda$awqPSgriNRe12PWP0zkpAtPsfV4@8edd3bc
D/ActivityThread: >>>>> Dispatching to Handler (android.view.Choreographer$FrameHandler) {ff72a60} android.view.Choreographer$FrameDisplayEventReceiver@1667b19: 0
D/ActivityThread: <<<<< Finished to Handler (android.view.Choreographer$FrameHandler) {ff72a60} android.view.Choreographer$FrameDisplayEventReceiver@1667b19
D/ActivityThread: >>>>> Dispatching to Handler (android.view.Choreographer$FrameHandler) {ff72a60} android.view.Choreographer$FrameDisplayEventReceiver@1667b19: 0
D/ActivityThread: <<<<< Finished to Handler (android.view.Choreographer$FrameHandler) {ff72a60} android.view.Choreographer$FrameDisplayEventReceiver@1667b19
D/ActivityThread: >>>>> Dispatching to Handler (android.view.Choreographer$FrameHandler) {ff72a60} android.view.Choreographer$FrameDisplayEventReceiver@1667b19: 0
D/ActivityThread: <<<<< Finished to Handler (android.view.Choreographer$FrameHandler) {ff72a60} android.view.Choreographer$FrameDisplayEventReceiver@1667b19
D/ActivityThread: >>>>> Dispatching to Handler (android.view.Choreographer$FrameHandler) {ff72a60} android.view.Choreographer$FrameDisplayEventReceiver@1667b19: 0
D/ActivityThread: <<<<< Finished to Handler (android.view.Choreographer$FrameHandler) {ff72a60} android.view.Choreographer$FrameDisplayEventReceiver@1667b19
D/ActivityThread: >>>>> Dispatching to Handler (android.view.Choreographer$FrameHandler) {ff72a60} android.view.Choreographer$FrameDisplayEventReceiver@1667b19: 0
D/ActivityThread: <<<<< Finished to Handler (android.view.Choreographer$FrameHandler) {ff72a60} android.view.Choreographer$FrameDisplayEventReceiver@1667b19
D/ActivityThread: >>>>> Dispatching to Handler (android.view.Choreographer$FrameHandler) {ff72a60} android.view.Choreographer$FrameDisplayEventReceiver@1667b19: 0
D/ActivityThread: <<<<< Finished to Handler (android.view.Choreographer$FrameHandler) {ff72a60} android.view.Choreographer$FrameDisplayEventReceiver@1667b19
D/ActivityThread: >>>>> Dispatching to Handler (android.view.Choreographer$FrameHandler) {ff72a60} android.view.Choreographer$FrameDisplayEventReceiver@1667b19: 0
D/ActivityThread: <<<<< Finished to Handler (android.view.Choreographer$FrameHandler) {ff72a60} android.view.Choreographer$FrameDisplayEventReceiver@1667b19
D/ActivityThread: >>>>> Dispatching to Handler (android.view.Choreographer$FrameHandler) {ff72a60} android.view.Choreographer$FrameDisplayEventReceiver@1667b19: 0
D/ActivityThread: <<<<< Finished to Handler (android.view.Choreographer$FrameHandler) {ff72a60} android.view.Choreographer$FrameDisplayEventReceiver@1667b19
D/ActivityThread: >>>>> Dispatching to Handler (android.view.Choreographer$FrameHandler) {ff72a60} android.view.Choreographer$FrameDisplayEventReceiver@1667b19: 0
D/ActivityThread: <<<<< Finished to Handler (android.view.Choreographer$FrameHandler) {ff72a60} android.view.Choreographer$FrameDisplayEventReceiver@1667b19
D/ActivityThread: >>>>> Dispatching to Handler (android.os.Handler) {32e9f45} android.graphics.animation.-$$Lambda$awqPSgriNRe12PWP0zkpAtPsfV4@3bf069a: 0
D/ActivityThread: <<<<< Finished to Handler (android.os.Handler) {32e9f45} android.graphics.animation.-$$Lambda$awqPSgriNRe12PWP0zkpAtPsfV4@3bf069a
D/ActivityThread: >>>>> Dispatching to Handler (android.os.Handler) {174ebcb} android.graphics.animation.-$$Lambda$awqPSgriNRe12PWP0zkpAtPsfV4@8a26fa8: 0
D/ActivityThread: <<<<< Finished to Handler (android.os.Handler) {174ebcb} android.graphics.animation.-$$Lambda$awqPSgriNRe12PWP0zkpAtPsfV4@8a26fa8
D/ActivityThread: >>>>> Dispatching to Handler (android.os.Handler) {f1eeac1} android.graphics.animation.-$$Lambda$awqPSgriNRe12PWP0zkpAtPsfV4@df6f266: 0
D/ActivityThread: <<<<< Finished to Handler (android.os.Handler) {f1eeac1} android.graphics.animation.-$$Lambda$awqPSgriNRe12PWP0zkpAtPsfV4@df6f266
D/ActivityThread: >>>>> Dispatching to Handler (android.view.Choreographer$FrameHandler) {ff72a60} android.view.Choreographer$FrameDisplayEventReceiver@1667b19: 0
D/ActivityThread: <<<<< Finished to Handler (android.view.Choreographer$FrameHandler) {ff72a60} android.view.Choreographer$FrameDisplayEventReceiver@1667b19
D/ActivityThread: >>>>> Dispatching to Handler (android.view.Choreographer$FrameHandler) {ff72a60} android.view.Choreographer$FrameDisplayEventReceiver@1667b19: 0
D/ActivityThread: <<<<< Finished to Handler (android.view.Choreographer$FrameHandler) {ff72a60} android.view.Choreographer$FrameDisplayEventReceiver@1667b19
D/ActivityThread: >>>>> Dispatching to Handler (android.view.Choreographer$FrameHandler) {ff72a60} android.view.Choreographer$FrameDisplayEventReceiver@1667b19: 0
D/ActivityThread: <<<<< Finished to Handler (android.view.Choreographer$FrameHandler) {ff72a60} android.view.Choreographer$FrameDisplayEventReceiver@1667b19
D/ActivityThread: >>>>> Dispatching to Handler (android.view.Choreographer$FrameHandler) {ff72a60} android.view.Choreographer$FrameDisplayEventReceiver@1667b19: 0
D/ActivityThread: <<<<< Finished to Handler (android.view.Choreographer$FrameHandler) {ff72a60} android.view.Choreographer$FrameDisplayEventReceiver@1667b19
D/ActivityThread: >>>>> Dispatching to Handler (android.view.Choreographer$FrameHandler) {ff72a60} android.view.Choreographer$FrameDisplayEventReceiver@1667b19: 0
D/ActivityThread: <<<<< Finished to Handler (android.view.Choreographer$FrameHandler) {ff72a60} android.view.Choreographer$FrameDisplayEventReceiver@1667b19
D/ActivityThread: >>>>> Dispatching to Handler (android.view.Choreographer$FrameHandler) {ff72a60} android.view.Choreographer$FrameDisplayEventReceiver@1667b19: 0
D/ActivityThread: <<<<< Finished to Handler (android.view.Choreographer$FrameHandler) {ff72a60} android.view.Choreographer$FrameDisplayEventReceiver@1667b19
D/ActivityThread: >>>>> Dispatching to Handler (android.view.Choreographer$FrameHandler) {ff72a60} android.view.Choreographer$FrameDisplayEventReceiver@1667b19: 0
D/ActivityThread: <<<<< Finished to Handler (android.view.Choreographer$FrameHandler) {ff72a60} android.view.Choreographer$FrameDisplayEventReceiver@1667b19
D/ActivityThread: >>>>> Dispatching to Handler (android.view.Choreographer$FrameHandler) {ff72a60} android.view.Choreographer$FrameDisplayEventReceiver@1667b19: 0
D/ActivityThread: <<<<< Finished to Handler (android.view.Choreographer$FrameHandler) {ff72a60} android.view.Choreographer$FrameDisplayEventReceiver@1667b19
D/ActivityThread: >>>>> Dispatching to Handler (android.view.Choreographer$FrameHandler) {ff72a60} android.view.Choreographer$FrameDisplayEventReceiver@1667b19: 0
D/ActivityThread: <<<<< Finished to Handler (android.view.Choreographer$FrameHandler) {ff72a60} android.view.Choreographer$FrameDisplayEventReceiver@1667b19
D/ActivityThread: >>>>> Dispatching to Handler (android.os.Handler) {e479a7} android.graphics.animation.-$$Lambda$awqPSgriNRe12PWP0zkpAtPsfV4@c82de54: 0
D/ActivityThread: <<<<< Finished to Handler (android.os.Handler) {e479a7} android.graphics.animation.-$$Lambda$awqPSgriNRe12PWP0zkpAtPsfV4@c82de54
D/ActivityThread: >>>>> Dispatching to Handler (android.os.Handler) {aa95e90} null: 200
D/ActivityThread: >>>>> Dispatching to Handler (android.os.Handler) {84d5389} null: 100
D/ActivityThread: <<<<< Finished to Handler (android.os.Handler) {84d5389} null
D/ActivityThread: <<<<< Finished to Handler (android.os.Handler) {aa95e90} null

可以看到在处理代码发送的消息之前,会优先执行 Choreographer$FrameHandler 的消息,毕竟点击事件也是由其触发的。同时由于 UI 线程要处理 UI 绘制相关的内容(点击 Button 会有动画),因此,即便在子线程中 message.what = 2000 的消息是后发送的,他也是优先执行。毕竟他所在的 Handler 是子线程创建的,没有其他消息需要处理。

IdleHandler 有什么用? 原理是啥

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* Callback interface for discovering when a thread is going to block
* waiting for more messages.
*/
public static interface IdleHandler {
/**
* Called when the message queue has run out of messages and will now
* wait for more. Return true to keep your idle handler active, false
* to have it removed. This may be called if there are still messages
* pending in the queue, but they are all scheduled to be dispatched
* after the current time.
*/
boolean queueIdle();
}

就是在 MessageQueue 中没有消息,等待消息的时候,会执行 IdleHanlder (其实叫做 IdleMessage 可能更贴切)。 实现原理就很简了,在 next() 方法中取不到消息的时候,就会检查是否有有可执行的 IdleHandler ,有的话就执行。

ThreadLocal 有啥用?原理是啥

ThreadLocal 本质上和 Handler 不是强相关的东西。通过以上的问题,我们已经了解了 线程-Handler-Looper-MessageQueue 一对一绑定的关系。而这其中最关键的就是 Looper 的创建,毕竟可以在任意一个地方调用 Looper.prepare 创建 Looper(随之内部会自动创建 MessageQueue),并由此创建 Handler。因此,要保证这个一对一的关系,其实就是要保证线程和 Looper 的一对一关系,就是在不同线程内的 Looper 是唯一的。

这其实就是如何在线程内部维护一个变量的问题。因此,使用 ThreadLocal 便可以解决问题。

ThreadLocal 使用事例
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
viewBinding.threadLocal.setOnClickListener {
val tag = "threadLocal"

val threadLocal = ThreadLocal<Int>()
val threadLocal2 = ThreadLocal<String>()
val threadLocal3 = ThreadLocal<Boolean>()

threadLocal.set(-1)
threadLocal2.set(this::class.java.name)

fun printAll() {
Log.e(tag, "${Thread.currentThread().name} : threadLocal = ${threadLocal.get()}")
Log.e(tag, "${Thread.currentThread().name} : threadLocal2 = ${threadLocal2.get()}")
Log.e(tag, "${Thread.currentThread().name} : threadLocal3 = ${threadLocal3.get()}")
}

val t1 = Thread {
threadLocal.set(1)
threadLocal2.set("22222")
threadLocal3.set(true)
printAll()
}

val t2 = Thread {
threadLocal.set(2)
threadLocal3.set(false)
printAll()
}

t1.start()
Thread.sleep(1000)
t2.start()

t1.join()
t2.join()

printAll()
}

可以看一下输出结果

1
2
3
4
5
6
7
8
9
E/threadLocal: Thread-2 : threadLocal = 1
E/threadLocal: Thread-2 : threadLocal2 = 22222
E/threadLocal: Thread-2 : threadLocal3 = true
E/threadLocal: Thread-3 : threadLocal = 2
E/threadLocal: Thread-3 : threadLocal2 = null
E/threadLocal: Thread-3 : threadLocal3 = false
E/threadLocal: main : threadLocal = -1
E/threadLocal: main : threadLocal2 = com.engineer.android.mini.ui.behavior.BehaviorActivity
E/threadLocal: main : threadLocal3 = null

注意,这里 threadLocal3 默认为 null,因为是对象类型 。可以看到,ThreadLocal 类型的变量在不同线程的内部的更新是互不影响的。这就提供了统一变量在不同线程之间互相隔离互不影响的实现。

原理浅析

ThreadLocal 本身的实现并不复杂,每一个线程都会有 ThreadLocal.ThreadLocalMap 类型的 threadLocals 成员变量。这个 ThreadLocalMap 顾名思义就是一个 Map。 key 就是当前 ThreadLocal 的实例,值就是 ThreadLocal 泛型对应变量的值。

set/get
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
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}

/**
* Variant of set() to establish initialValue. Used instead
* of set() in case user has overridden the set() method.
*
* @return the initial value
*/
private T setInitialValue() {
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
return value;
}

/**
* Sets the current thread's copy of this thread-local variable
* to the specified value. Most subclasses will have no need to
* override this method, relying solely on the {@link #initialValue}
* method to set the values of thread-locals.
*
* @param value the value to be stored in the current thread's copy of
* this thread-local.
*/
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
ThreadLocalMap

这篇 Java面试必问:ThreadLocal终极篇 分析,已经很详细了。

总结

关于 Handler ,通过此次阅读源码又有了一些比较深入的认识和了解。但是学海无涯,尤其是对于作为 Android 基石的 Handler 机制,每一行源码的实现都是非常考究的,因此这里的理解难免有局限性和偏差。但是,知识的学习就是这样,是一个不断累积的过程,在这个过程中认知会升级,会变化,会纠错。

参考文档

加个鸡腿呗.