再次学习 Handler

前言

关于 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 机制,每一行源码的实现都是非常考究的,因此这里的理解难免有局限性和偏差。但是,知识的学习就是这样,是一个不断累积的过程,在这个过程中认知会升级,会变化,会纠错。

参考文档

相关推荐
开心工作室_kaic几秒前
ssm068海鲜自助餐厅系统+vue(论文+源码)_kaic
前端·javascript·vue.js
数据猎手小k12 分钟前
AndroidLab:一个系统化的Android代理框架,包含操作环境和可复现的基准测试,支持大型语言模型和多模态模型。
android·人工智能·机器学习·语言模型
有梦想的刺儿20 分钟前
webWorker基本用法
前端·javascript·vue.js
P.H. Infinity24 分钟前
【RabbitMQ】04-发送者可靠性
java·rabbitmq·java-rabbitmq
生命几十年3万天28 分钟前
java的threadlocal为何内存泄漏
java
caridle40 分钟前
教程:使用 InterBase Express 访问数据库(五):TIBTransaction
java·数据库·express
cy玩具40 分钟前
点击评论详情,跳到评论页面,携带对象参数写法:
前端
^velpro^1 小时前
数据库连接池的创建
java·开发语言·数据库
你的小101 小时前
JavaWeb项目-----博客系统
android
苹果醋31 小时前
Java8->Java19的初步探索
java·运维·spring boot·mysql·nginx