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

参考文档

相关推荐
救救孩子把13 分钟前
深入理解 Java 对象的内存布局
java
落落落sss16 分钟前
MybatisPlus
android·java·开发语言·spring·tomcat·rabbitmq·mybatis
万物皆字节21 分钟前
maven指定模块快速打包idea插件Quick Maven Package
java
夜雨翦春韭28 分钟前
【代码随想录Day30】贪心算法Part04
java·数据结构·算法·leetcode·贪心算法
我行我素,向往自由35 分钟前
速成java记录(上)
java·速成
代码敲上天.37 分钟前
数据库语句优化
android·数据库·adb
twins352041 分钟前
解决Vue应用中遇到路由刷新后出现 404 错误
前端·javascript·vue.js
一直学习永不止步41 分钟前
LeetCode题练习与总结:H 指数--274
java·数据结构·算法·leetcode·数组·排序·计数排序
邵泽明41 分钟前
面试知识储备-多线程
java·面试·职场和发展
程序员是干活的1 小时前
私家车开车回家过节会发生什么事情
java·开发语言·软件构建·1024程序员节