前言
关于 Android Handler 机制更多的理解和思考。
Handler N 问
鉴于 Handler 的使用已经是非常熟悉了,使用方式就不再赘述。本文就从一些对 Handler 的问题出发,通过对这些问题的解答再次探索 Handler 的奥妙所在。
以下所有 Handler 相关的源码均以 Android SDK 34 (Android 14) 版本为准
有些问题可能看起来很简,这里提出是因为对这个问题的答案之前理解有偏差或完全错误,因此在此做一次更正
发送的消息到底会在哪里执行?
java
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 的信息来看看消息是在哪里执行的。
kotlin
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.
shell
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 总是优先处理消息,即便他是后执行的。可以先想一下原因,后面会有解释。
为啥不会阻塞主线程?
java
for (;;) {
if (!loopOnce(me, ident, thresholdOverride)) {
return;
}
}
java
private static boolean loopOnce(final Looper me,
final long ident, final int thresholdOverride) {
Message msg = me.mQueue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return false;
}
...
}
在 Looper.loopOnce(这里似乎是有点变化了) 方法中取消息的时候,me.mQueue.next()
是阻塞操作,那么为什么不会造成主线程卡死呢?(面试经典问题 为什么主线程的Looper是一个死循环,但是却不会ANR?)
这个问题细分的话可以由很多点可以讨论,比如 ANR 是什么?在 Android 中哪些情况下会 ANR ? 因为死循环,所以 ANR ? 这个问题本身的问法可能就有点问题。 具体细节可以参考知乎问题 Android中为什么主线程不会因为Looper.loop()里的死循环卡死?。里面的每一个回答基本都值得一看,从不同角度阐述了对这个问题的理解。
我们可以仔细看一下 MessageQueue
的实现.
java
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 了。
java
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()
有什么区别呢?
java
/**
* 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();
java
/**
* 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 的源码,发现的确是有了一些变化.
构造函数的变化
java
/**
* 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 官方也是被逼无奈啊。
异步消息的支持
java
@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
创建此类消息。其实这个方法早在 Android SDK API 28 (即 9.0)就支持了。( Android 新版本发布,除了官方提到的特性,回看一下常用组件的源码变更也是很有意义的)
java
/**
* Create a new Handler whose posted messages and runnables are not subject to
* synchronization barriers such as display vsync.
*
* <p>Messages sent to an async handler are guaranteed to be ordered with respect to one another,
* but not necessarily with respect to messages from other Handlers.</p>
*
* @see #createAsync(Looper, Callback) to create an async Handler with custom message handling.
*
* @param looper the Looper that the new Handler should be bound to
* @return a new async Handler instance
*/
@NonNull
public static Handler createAsync(@NonNull Looper looper) {
if (looper == null) throw new NullPointerException("looper must not be null");
return new Handler(looper, null, true);
}
那么,异步消息有什么用呢?下面就来解释
异步消息、同步消息、屏障消息是啥?
同步消息和异步消息的区别是通过 msg.setAsynchronous()
方法设置。那么同步消息和异步消息在 next() 当中获取的时候有什么区别呢?这就要说到屏障消息了。
java
/**
* 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 消息执行机制提供了一种优先级的概念。
java
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 的同步屏障消息,遇到同步消息会一直往后找,直到找到异步消息或到链表末尾。关于屏障消息使用最广泛的场景就是大家非常熟悉的 View 测了绘制布局的过程了。
HandlerThread 是啥?
UI 线程在 ActivityThread.main 方法中创建了 Looper并开始了 loop 循环,保证了 UI 线程 Handler 正常运行。为了方便在子线程中方便的使用 Handler 机制,官方简单封装 Looper 的创建过程。
java
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
java
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"))
日志输出
shell
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 有什么用? 原理是啥
java
/**
* 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 便可以解决问题。
java
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()
}
可以看一下输出结果
shell
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
java
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 机制,每一行源码的实现都是非常考究的,因此这里的理解难免有局限性和偏差。但是,知识的学习就是这样,是一个不断累积的过程,在这个过程中认知会升级,会变化,会纠错。