一个小型合做的流水线——Android Handler

当咱们遇到多线程的问题,考虑到线程间消息传递的时候,首先想到的确定是 Handler。虽然写这篇文章的初衷并非想探究 Handler 的机制,但咱们仍是先从这个被说烂了的话题开始。java

Handler 的工做原理

首先,在了解 Handler 以前,咱们须要了解有四个关键的类是组成 Handler 的基础。它们分别是面试

  • Handler 负责协调安排将来某个时间点的消息或可运行状态,以及对不一样线程的运行机制进行合理的排队
  • Looper 主要做用如其名,一个循环的机制,为线程运行消息循环分发
  • MessageQueue 一个链式队列数据结构,将消息实体串联成链
  • Message 消息实体,存储咱们须要传递的消息的内容和信息等

Looper 和 MessageQueue——Handler 的流水线

ActivityThread 类中,做为入口方法的 main() 方法中,经过调用 Looperloop() 方法,启动 Looper 的循环机制(这里咱们注意到,在方法的最后,抛出了一个主线程循环意外退出的异常,说明 Android 的主流程都是经过 Handler 来驱动的)。markdown

/** * ActivityThread */
public static void main(String[] args) {
    
    // ...
    Looper.prepareMainLooper();
	// ...
    Looper.loop();

    throw new RuntimeException("Main thread loop unexpectedly exited");
}
复制代码

进入 loop() 方法,这里咱们能够看到一个死循环,传说中的死循环这么快就跟咱们见面了吗?其实否则,咱们平时面试时更关注的死循环并非这个,或者说它只是其中的一部分。废话先不说,这段代码精简后的大体做用能够概括为:从 MessageQueue 的对象队列里取出一个未处理的消息,即 Message 实例,而后获取 Message 对象的 target 属性,它是一个 Handler 对象,而后经过 dispatchMessage() 方法来将消息进行分发。数据结构

/** * Looper */
public static void loop() {
    final MessageQueue queue = me.mQueue;

    // Make sure the identity of this thread is that of the local process,
    // and keep track of what that identity token actually is.
    // 我最开始读到这段源码的时候,很困惑这个方法为何调用了两遍,后来通过思索想明白了缘由,这里稍做记录。
    // 这个方法调用的是 native 的代码,源码以下:
    // int64_t IPCThreadState::clearCallingIdentity()
	// {
    // int64_t token = ((int64_t)mCallingUid<<32) | mCallingPid;
    // clearCaller();
    // return token;
	// }
	// void IPCThreadState::clearCaller()
	// {
	// mCallingPid = getpid(); //当前进程pid赋值给mCallingPid
	// mCallingUid = getuid(); //当前进程uid赋值给mCallingUid
	// }
    // 具体做用能够网上自行搜索,这个方法的做用,简而言之,就是将(多是)其余进程的 pid 和 uid 清除,更换为本身的,
    // 而 token 是用来存储原来进程的 pid 和 uid 的64位整型,因此第一遍调用时返回的是以前进程的 pid 和 uid 信息,
    // 再次调用时,返回的才是当前进程的,而被我精简掉的源码里须要经过这个 token 来判断进程是否切换过,因此这个方法在这里会调用两遍
    Binder.clearCallingIdentity();
    final long ident = Binder.clearCallingIdentity();

    for (;;) {
        Message msg = queue.next(); // might block
        if (msg == null) {
            // No message indicates that the message queue is quitting.
            return;
        }
        try {
            msg.target.dispatchMessage(msg);
            
        } catch (Exception exception) {
            throw exception;
        }

        msg.recycleUnchecked();
    }
}
复制代码

由于 dispatchMessage() 方法比较简单,因此咱们先越过过程看结果,看看这个方法的实现。多线程

/** * Handler */
public void dispatchMessage(@NonNull Message msg) {
    if (msg.callback != null) {
        handleCallback(msg);
    } else {
        if (mCallback != null) {
            if (mCallback.handleMessage(msg)) {
                return;
            }
        }
        handleMessage(msg);
    }
}
复制代码

这里就直接调用了 Handler 对象的 handleMessage() 方法,并传递 Message 的实例,因此咱们在使用 Handler 时在这个方法中就能够接收到咱们须要的消息实体(callback 默认不实现,实现后变动为调用相应的方法)。app

好,结果咱们已经知道了,那如今咱们回过头来,研究一下上面 Looper 类的 loop() 方法中调用的 queue.next() 方法是如何拿到消息实体的(后面的注释已经提醒咱们这个方法可能会阻塞)。less

/** * MessageQueue */
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
    // 这个变量做为 nativePollOnce 方法的参数表示休眠的时间
    // 当值为 -1 时,表示无限休眠,直到有线程唤醒
    // 当值为 0 时,表示当即唤醒
    int nextPollTimeoutMillis = 0;
    for (;;) {
        if (nextPollTimeoutMillis != 0) {
            Binder.flushPendingCommands();
        }
		
        // 根据 nextPollTimeoutMillis 变量的值进行休眠
        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;
            // 若是 Message 的 target 为 null,则说明它是 Looper synchronization barrier 的临界点
            if (msg != null && msg.target == null) {
                // Stalled by a barrier. Find the next asynchronous message in the queue.
                do {
                    prevMsg = msg;
                    msg = msg.next;and the message is the earliest asynchronous message in the queue
                } while (msg != null && !msg.isAsynchronous());
            }
            // 通过上面的循环后,到达这里的 Message 要么是 null,要么是 isAsynchronous() 方法返回 true
            if (msg != null) {
                if (now < msg.when) {
                    // Next message is not ready. Set a timeout to wake up when it is ready.
                    // 消息的发送时间未到,此时的 nextPollTimeoutMillis 为距离 msg 的发送时间的时间间隔,
                    // 那 nativePollOnce() 方法休眠相应的时间后,msg 即到了它该发送的时间
                    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,
                // 那 nativePollOnce() 方法将致使线程永久休眠,直到有其余线程将其唤醒
                nextPollTimeoutMillis = -1;
            }

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

next() 方法看起来很长,可是它的主要工做只有一件事,就是找到符合要求的 Message 实例并返回。可是这个方法又特别重要,有一个常问的重要的面试考点。咱们上面已经提到了,Looperloop() 方法中有一个死循环,做用是源源不断地从 MessageQueue 中「打捞」 Message 实体,而「打捞」的动做正是经过 next() 方法完成的。在 next() 方法中,也有一个死循环,完成上面的「打捞」工做。具体的细节我在代码中做了部分注释,能够帮助理解。其中提到了一个概念——「Looper synchronization barrier」,关于它的介绍咱们放在下面的内容里。async

好了,介绍完了 Handler 机制中的死循环,它是死循环双重嵌套的形式,那么面试问题来了:请问 Handler 机制中的死循环是如何作到不阻塞主线程的呢?网上搜索到的答案一般是死循环也未必会阻塞主线程,只要不在 onCreate()onStart() 等生命周期中阻塞就不会致使界面的卡死,其次在 MessageQueue 中没有 Message 实体时,线程会进入到一个休眠的状态,在有新消息来临时线程才会被唤醒,balabala小魔仙……咱们看到 next() 方法的死循环的一开始有一句代码 nativePollOnce(),它是一个 native 的方法,经过执行 Linux 中的 epoll 机制来是线程休眠和运行,它和 nativeWake() 方法配对使用,在类文件的开头均有声明。因此每次在执行完一遍 next() 方法后,都会根据 nextPollTimeoutMillis 变量的值来决定休眠的时间。若是没有可被「打捞」的消息,那么线程将被永久休眠,等待被唤醒。那么在哪里唤醒的呢,咱们暂时无论,在这里先记住线程休眠,主线程被阻塞,等待一个白马王子将其唤醒。至于白马王子什么时候到来,咱们静待。ide

Handler——消息操做台

如今,咱们再从消息发送的源头追溯——经过 Handler 的一系列 sendMessage() 方法,将消息发送出去。oop

咱们以 sendEmptyMessage() 方法为例,通过一系列的调用后,最终会执行 enqueueMessage() 方法,该方法又会调用 MessageQueueenqueueMessage() 方法,该方法代码以下:

/** * MessageQueue */
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() 方法显真身了,当有新的消息压入队列,消息须要被处理,此时就须要唤醒睡眠的线程。可是「白马王子」

的到来是须要条件的,即 needWake,那究竟是怎样的条件呢?想一想无非是判断当前的线程是否处于可能阻塞的状态,咱们来看看。

在第一个条件 p == null || when == 0 || when < p.when 下,相比于罗列全部的知足条件的状况,更简单的方法是判断咱们前面的线程被阻塞的状况是否是在这里被断定为 needWake, 由于在等待新的消息,因此 mMessage 值为 null,此时的 needWake = mBlocked,而 mBlocked 的线程被阻塞的状况下值是为 true 的,因此这里会被断定为须要被唤醒。而在 else 分支中,其实条件为p != null && when != 0 && when >= p.when,这说明消息队列中的消息并无被取完,而是正在一个循环中,一般状况下是不须要再唤醒它,除非像注释中所说的 there is a barrier at the head of the queue and the message is the earliest asynchronous message in the queue

到这里,Handler 的大概工做流程就能够串联起来了——循环队列至关于物流,消息至关于商品,物流无时无刻在运转,当你须要新的商品时,商品被商家发送至物流,而后分发到目标客户即你的手中。

Looper synchronization barrier

在看源码的时候,不止一次会接触到这个概念,并且在上面咱们也已经率先使用了这个概念,那么这个概念究竟是个什么?搞清楚这个问题,咱们须要从它的特征入手,在 MessageQueuenext() 方法中,咱们说若是 mMessages.target == null,那么它就是一个 barrier 的临界点,咱们经过查找 mMessage 的写引用,最终定位到 MessageQueue#postSyncBarrier() 这个方法。我这里摘录它的注释,相信你们对这个概念就会有一个清晰的认识。

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!

在了解这个概念以前还须要知道一个属性的存在,那就是 Message#isAsynchronous()

好了,总结一下就是 Looper synchronization barrierMessageQueue 中那些 target == nullMessage,它们不须要被发送,只做为一种队列状态的判断标识。当 Message.isAsynchronous() == true 时,遇到 Looper synchronization barrier 时,Looper 会被阻塞,直到 removeSyncBarrier() 方法(和 postSyncBarrier() 方法成对使用)移除这个标识。可是若是 Message.isAsynchronous() == false 时,则不会被 barrier 阻断,具体使用场景见上方注释。

太多的代码和解说赶不上一张图片更能让人造成概念,那我从网上找了一张图片稍做加工,但愿能够比较形象地说明 Handler 机制中各个类之间的分工。

映射关系

为了话题的天然过渡,这里咱们思考一个问题,一个线程能够有多个 Looper 吗?一个 Looper 能够对应多个 MessageQueue 吗?从源码中看,一个线程是没法建立多个 Looper 和多个 MessageQueue 的,那么多个 LooperMessageQueue 会致使什么问题呢?最主要的就是咱们上面说的消息同步性的问题了,多个消息队列和循环体如何保证消息的次序限制以及同步分发就是一个很复杂的问题。那么系统又是如何保证每一个线程的 Looper 的惟一性的呢?那就是使用 ThreadLocal 了。

ThreadLocal

因为本篇内容旨在讨论 Handler 的相关机制,因此对于 ThreadLocal 的机制不作过多讨论。

Looper#prepare() 方法在 Looper 使用前必须调用,在这个方法里能够看到 ThreadLocal 的应用。

/** * Looper */
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

private static void prepare(boolean quitAllowed) {
    if (sThreadLocal.get() != null) {
        throw new RuntimeException("Only one Looper may be created per thread");
    }
    sThreadLocal.set(new Looper(quitAllowed));
}
复制代码

sThreadLocal 对象是一个全局的静态对象,经过使用 sThreadLocal#set() 方法来存储 Looper 的实例,而 ThreadLocal 把真正的对象存储交给了它的静态内部类 ThreadLocalMap,这是一个自定义的 hash map,具体内部实现请自行阅读源码。

/** * ThreadLocal */

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 getMap(Thread t) {
    return t.threadLocals;
}

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();
}
复制代码

能够看到,ThreadLocalMap 又和 Thread 绑定,每一个 Thread 对应一个惟一的 ThreadLocalMap 实例, ThreadLocalMapkey 的类型是 ThreadLocal,而在 Looper 中的 sThreadLocal 做为静态对象,进程内惟一,经过这样的关系,能够惟一对应到 TreadLocalMap 中的某个元素,实现读取。

碎碎念

前面两个月经历找工做和工做后的一堆杂事,致使好久没有更新。这篇也是匆忙赶工,逻辑上和图文代码编排上都有一些问题,还请多多包涵。以前作的是 Flutter 的工做,如今又回到了 Android,Flutter 的内容也会继续带着更新,后面我会尽可能保持正常的更新频率,可是水平确实有限,最后仍是请你们雅正和包涵。

相关文章
相关标签/搜索