0%

Android 的消息机制(二):Android 的消息机制分析

1. ThreadLocal 工作原理概述

  • 理解 ThreadLocal

  • Handler 深层次问题解答

  • 官方文档定义

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    /**
    * This class provides thread-local variables. These variables differ from
    * their normal counterparts in that each thread that accesses one (via its
    * {@code get} or {@code set} method) has its own, independently initialized
    * copy of the variable. {@code ThreadLocal} instances are typically private
    * static fields in classes that wish to associate state with a thread (e.g.,
    * a user ID or Transaction ID).
    *
    * <p>For example, the class below generates unique identifiers local to each
    * thread.
    * A thread's id is assigned the first time it invokes {@code ThreadId.get()}
    * and remains unchanged on subsequent calls.
    * <pre>
    * import java.util.concurrent.atomic.AtomicInteger;
    *
    * public class ThreadId {
    * // Atomic integer containing the next thread ID to be assigned
    * private static final AtomicInteger nextId = new AtomicInteger(0);
    *
    * // Thread local variable containing each thread's ID
    * private static final ThreadLocal<Integer> threadId =
    * new ThreadLocal<Integer>() {
    * @Override protected Integer initialValue() {
    * return nextId.getAndIncrement();
    * }
    * };
    *
    * // Return the current thread's unique ID, assigning it if necessary
    * public static int get() {
    * return threadId.get();
    * }
    * }
    * </pre>
    * <p>Each thread holds an implicit reference to its copy of a thread-local
    * variable as long as the thread is alive and the {@code ThreadLocal}
    * instance is accessible; after a thread goes away, all of its copies of
    * thread-local instances are subject to garbage collection(unless other
    * references to these copies exist).
    *
    * @author Josh Bloch and Doug Lea
    * @since 1.2
    */
    public class ThreadLocal<T> {
    // bla bla bla
    }
    • 相当于是某个共享变量的包装类,包装之后结合静态内部类 ThreadLocalMap 即可起到每个线程拥有该泛型变量的独有拷贝的作用
  • ThreadLocal 静态内部类 ThreadLocalMap 源码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    /**
    * ThreadLocalMap is a customized hash map suitable only for
    * maintainging thread local values. No operations are exported
    * outside of the ThreadLocal class. The class is package private to
    * allow declaration of fields in class Thread. To help deal with
    * very large and long-lived usages, the hash table entries use
    * WeakReferences for keys. However, since reference queues are not
    * used, stale entries are guaranteed to be removed only when
    * the table starts running out of space.
    */
    static class ThreadLocalMap {
    /**
    * The entries in this map extend WeakReference, using
    * its main ref field as the key (which is always a
    * ThreadLocal object). Note that null keys (i.e. entry.get()
    * == null) mean that the key is no longer referenced, so the
    * entry can be expunged from table. Such entries are referred to
    * as "stale entries" in the code that follows.
    */
    static class Entry extends WeakReference<ThreadLocal<?>> {
    /** The value associated with this ThreadLocal. */
    Object value;

    Entry(ThreadLocal<?> k, Object v) {
    super(k);
    value = v;
    }
    }

    /**
    * The initial capacity -- MUST be a power of two.
    */
    private static final int INITIAL_CAPACITY = 16;

    /**
    * The table, resized as necessary.
    * table.length MUST always be a power of two.
    */
    private Entry[] table;

    /**
    * The number of entries in the table.
    */
    private int size = 0;

    /**
    * Set the resize threshold to maintain at worst a 2/3 load factor.
    */
    private void setThreshold(int len) { threshold = len * 2 / 3; }

    // bla bla bla
    }
    • 分析线程 Thread 类源码可知,Thread 内部有一个类型为 ThreadLocal.ThreadLocalMap 的成员变量 threadLocals,证明了每个线程都有 ThreadLocal 包装的泛型变量的独有拷贝

    • ThreadLocal 与内存泄漏

      • 从 键值对 Entry 类的实现来看,ThreadLocalMap 中的每个键值对 Entry 都是一个弱引用弱引用的类型为 Entry 的键 ThreadLocal<?>。这种设计虽然复杂,但不容易发生内存泄漏

      • 但并不能绝对避免内存泄漏,如果在线程池中使用 ThreadLocal 就有可能导致内存泄漏。原因是线程池中线程的存活时间太长,往往和程序都是同生共死的

        • 这就意味着 Thread 持有的 ThreadLocalMap 一直都不会被回收,在加上 ThreadLocalMap 中的 Entry 对 ThreadLocal 是弱引用,所以只要 ThreadLocal 结束了自己的生命周期就是可以被回收的
        • Entry 中的 Value 却是被 Entry 强引用的,所以即便 Value 的生命周期结束了,Value 也是无法被回收的,从而导致内存泄漏
      • 所以可以通过 try/finally 来手动释放资源避免内存泄漏:

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        ExecutorService es;
        ThreadLocal t1;
        ex.execute(() -> {
        // ThreadLocal 设置变量
        t1.set(obj);
        try {
        // 省略业务代码
        } finally {
        // 手动清理 ThreadLocal,防止内存泄漏
        t1.remove();
        }
        });
  • ThreadLocal 构造方法 ThreadLocal() 源码:

    1
    2
    3
    4
    5
    6
    /**
    * Creates a thread local variable.
    * @see #withInitial(java.util.function.Supplier)
    */
    public ThreadLocal() { // 这是一个空实现
    }
  • ThreadLocal get() 方法源码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    /**
    * Returns the value in the current thread's copy of this
    * thread-local variable. If the variable has no value for the
    * current thread, it is first initialized to the value returned
    * by an invocation of the {@link #initiaValue} method.
    *
    * @return the current thread's value of this thread-local
    */
    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();
    }

    public static native java.lang.Thread currentThread(); // native 方法
  • ThreadLocal set() 方法源码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    /**
    * 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-lcoal.
    */
    public void set(T value) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if(map != null)
    map.set(this, value);
    else
    createMap(t, value);
    }

    public static native java.lang.Thread currentThread(); // native 方法

2. Message 工作原理概述

  • 官方文档定义

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    /**
    * Defines a message containing a description and arbitrary data object that can be
    * sent to a {@link Handler}. This object contains two extra int fields and an
    * extra object field that allow you to not do allocations in many cases.
    *
    * <p class="note">While the constructor of Message is public, the best way to get
    * one of these is to call {@link #obtain Message.obtain()} or one of the
    * {@link Handler#obtainMessage Handler.obtainMessage()} method, which will pull
    * them from a pool of recycled objects.</p>
    */
    public final class Message implements Parcelable {
    /**
    * User-defined message code so that the recipient can identify
    * what this message is about. Each {@link Handler} has its own name-space
    * for message codes, so you do not need to worry about yours conflicting
    * with other handlers.
    */
    public int what;

    public int arg1;
    public int arg2;
    public Object obj;
    public Messenger replyTo;

    // bla bla bla

    @UnsupportedAppUsage
    /*package*/ int flags;

    /**
    * The targeted delivery time of this message. The time-base is
    * {@link SystemClock#uptimeMillis}.
    * @hide Only for use within the tests.
    */
    @UnsupportedAppUsage
    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
    public long when;

    /*package*/ Bundle data;

    @UnsupportedAppUsage
    /*package*/ Handler target;

    @UnsupportedAppUse
    /*package*/ Runnable callback;

    // sometimes we store linked lists of these things
    @UnsupportedAppUsage
    /*package*/ Message next;

    /** @hide */
    public static final Object sPoolSync = new Object();
    private static Message sPool; // 消息池,便于回收复用,使用享元设计模式
    private static int sPoolSize = 0;

    private static final int MAX_POOL_SIZE = 50; // 消息池的最大容量是 50

    // bla bla bla
    }
    • 字段 when 是一个相对时间,表示 Message 期望被分发的时间,从 Handler 的 sendMessageDelayed() 方法可知,when 的值是 SystemClock.uptimeMillis()delayMillis 之和
    • SystemClock.uptimeMillis() 是表示当前时间的一的一个相对时间,表示自系统启动开始(不包括 deep sleep)到调用该方法时的毫秒数
    • Message#when 不用 System.currentTimeMillis() 来表示的原因:
      • System.currentTimeMillis() 方法表示的是从 1970-01-01 00:00:00 到当前时间的毫秒数,这个值是一个强关联系统时间的值,我们可以通过修改系统时间达到修改该值的目的,所以该值是不可靠的
      • 比如手机长时间没有开机,开机后系统时间重置为出厂时设置的时间,中间我们发送了一个延时消息,过了一段时间通过 NTP 同步了最新时间,此时会导致延时消息失效
      • 同时 Message#when 只是用时间差来表示先后关系,所以只需要一个相对时间就可以达到目的。它可以是从系统启动开始计时,也可以是从 APP启动时开始计时,甚至可以是定期重置的(所有消息都减去同一个值,但这样就复杂了没必要)
  • Message 构造方法 Message() 方法源码:

    1
    2
    3
    4
    5
    /**
    * Constructor (but the preferred way to get a Message is to call {@link #obtain() Message.obtain()}).
    */
    public Message() {
    }
  • Message obtain() 方法源码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    /**
    * Return a new Message instance from the global pool. Allows us to
    * avoid allocating new objects in many cases.
    */
    public static Message obtain() {
    synchronized (sPoolSync) {
    if (sPool != null) { // 如果 sPool 不为空,则从 sPool 里面取消息,使用享元设计模式
    Message m = sPool;
    sPool = m.next;
    m.next = null;
    m.flags = 0; // clear in-use flag
    sPolSize--;
    return m;
    }
    }
    return new Message();
    }

    // obtain() 还有很多重载方法,内部都调用了 obtain() 方法
    • 创建 Message 的最佳方式:
      • Message.obtain():如果 Message#sPool == null,则 new Message()
      • Handler#obtainMessage():内部直接调用返回 Message.obtain()

3. MessageQueue 工作原理概述

  • 官方文档定义

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    /**
    * Low-level class holding the list of messages to be dispatched by a
    * {@link Looper}. Messages are not added directly to a MessageQueue,
    * but rather through {@link Handler} objects associated with the Looper.
    *
    * <p>You can retrieve the MessageQueue for the current thread with
    * {@link Looper#myQueue() Looper.myQueue()}.
    */
    public final class MessageQueue {
    // bla bla bla

    // True if the message queue can be quit.
    @UnsupportedAppUsage
    private final boolean mQuitAllowed;

    @UnsupportedAppUsage
    @SuppressWarnings("unused")
    private long mPtr; // used by native code

    @UnsupportedAppUsage
    Message mMessages;
    @UnsupportedAppUsage
    private final ArrayList<IdleHandler> mIdleHandlers = new ArrayList<IdleHandler>();
    private SparseArray<FileDescriptorRecord> mFileDescriptorRecords;
    private IdleHandler[] mPendingIdleHandlers;
    private boolean mQuitting;

    // Indicates whether next() is blocked waiting in pollOnce() with a non-zero timeout.
    private boolean mBlocked;

    private native static long nativeInit();

    @UnsupportedAppUsage
    private native void nativePollOnce(long ptr, int timeoutMillis); /*non-static for callbacks*/

    // bla bla bla

    /**
    * 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 shceduled to be dispatched
    * after the current time.
    */
    boolean queueIdle();
    }

    // bla bla bla
    }
    • IdleHandler 是一个接口,它提供了一种机制,当主线程消息队列空闲时,会执行 IdleHandler 中的回调方法
    • IdleHandler 在 Activity 生命周期方法 onStop()/onDestroy() 中的一个应用:Activity 生命周期方法 10s 回调的兜底机制
  • MessageQueue 构造方法 MessageQueue() 源码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    import android.compat.annotation.UnsupportedAppUsage;

    // True if the message queue can be quit.
    @UnsupportedAppUsage
    private final boolean mQuitAllowed;

    @UnsupportedAppUsage
    @SuppressWarnings("unused")
    private long mPtr; // used by native code

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

    private native static long nativeInit(); // native 方法
    • 注解 @UnsupportedAppUsage 的含义:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      /**
      * Indicates that a class member, that is not part of the SDK, is used by apps.
      * Since the member is not part of the SDK, such use is not supported.
      *
      * <p>This annotation acts as a heads up that changing a given method or field
      * may affect apps, potentially breaking them when the next Android version is
      * released. In some cases, for members that are heavily used, this annotation
      * may imply restrictions on changes to the member.
      *
      * <p>This annotation also results in access to the member being permitted by the
      * runtime, with a warning being generated in debug builds.
      *
      * <p>For more details, see go/UnsupportedAppUsage.
      *
      * {@hide}
      */
  • MessageQueue 主要包含两个操作:插入读取,读取操作本身会伴随着删除操作

  • 插入和读取对应的方法分别为 enqueueMessage()next(),其中 enqueueMessage() 方法的作用是往消息队列中插入一条消息,next() 方法的作用是从消息队列中取出一条消息并将其从消息队列中移除

  • MessageQueue 的内部实现实际上是通过一个单链表的数据结构来维护消息列表

    • MessageQueue#mMessages 字段保存单链表的第一个元素,Message#next 字段指向队列中的下一个消息
    • 单链表在插入和删除的操作上效率较高
  • MessageQueue enqueueMessage() 方法源码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    boolean enqueueMessage(Message msg, long when) {
    if (msg.target == null) {
    throw new IllegalArgumentException("Message must have a target.");
    }

    synchronized(this) {
    if (msg.isInUse()) {
    throw new IllegalStateException(msg + "This messgae is already in use.");
    }

    if (mQuiting) {
    IllegalStateException e = new IllegalStateException(msg.target + " sending message to a Handler on a dead thread");
    Log.w(TAG, e.getMessage(), e);
    msg.recycle();
    return false;
    }

    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 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 mQuiting is false.
    if (needWake) {
    nativeWake(mPtr);
    }
    }
    return true;
    }
    • 从源码实现来看,它的主要操作其实就是单链表的插入操作Handler 的一系列 post()send() 方法内部最终都是调用的 MessageQueue 的 enqueueMessage() 方法
    • needWake = mBlocked && p.target == null && msg.isAsynchronous(); 根据时间顺序,将消息插入到队列的合适位置
    • MessageQueue 是有序的,内部排序的依据是 Message 的 when 字段,表示一个相对时间。设置语句是:msg.when = when;
    • nativeWake(mPtr); 是一个 native 方法,表示唤醒主线程的 loop
    • 内部通过 synchronized 关键字保证插入操作线程安全
  • MessageQueue next() 方法源码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    @UnsupportedAppUsage
    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 {
    mMessage = 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;
    }

    // If first time idle, then get the number of idlers to run.
    // Idle handles only run if the queue is empty or if the first message
    // in the queue(possibly a barrier) is due to be handled in the future.
    if (pendingIdleHandlerCount < 0
    && (mMessages == null || now < mMessages.when)) {
    pendingIdleHandlerCount = mIdleHandlers.size();
    }
    if (pendingIdleHandlerCount <= 0) {
    // No idle handlers to run. Loop and wait some more.
    mBlocked = true;
    continue;
    }

    if (mPendingIdleHandlers == null) {
    mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
    }
    mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
    }

    // Run the idle handers.
    // We only ever reach this code block during the first iteration.
    for (int i = 0; i < pendingIdleHandlerCount; i++) {
    final IdleHandler idler = mPendingIdleHandlers[i];
    mPendingIdleHandlers[i] = null; // release the reference to the handler

    boolean keep = false;
    try {
    keep = idler.queueIdle();
    } catch (Throwable t) {
    Log.wtf(TAG, "IdleHandler threw exception", t);
    }

    if (!keep) {
    synchronized (this) {
    mIdleHandlers.remove(idler);
    }
    }
    }

    // Reset the idle handler count to 0 so we do not run them again.
    pendingIdleHandlerCount = 0;

    // While calling an idle handler, a new message could have been delivered
    // so go back and look again for a pending message without waiting.
    nextPollTimeoutMillis = 0;
    }

    // Disposes of the underlying message queue.
    // Must only be called on the looper thread or the finalizer.
    private void dispose() {
    if (mPtr != 0) {
    nativeDestroy(mPtr);
    mPtr = 0;
    }
    }
    • 从源码实现来看,next() 方法是一个无限循环的方法,如果消息队列中没有消息,那么 next() 方法会一直阻塞在:nativePollOnce(ptr, nextPollTimeoutMillis);,最终调用到 epoll_wait() 进行阻塞等待
    • nativePollOnce(ptr, nextPollTimeoutMillis); 是一个 native 方法,表示阻塞,直到下一个消息需要处理,或者有新消息入队,内部原理是 Linux 内核的 pipe/epoll 机制(休眠唤醒,读写阻塞)
    • 当有新消息到来时会唤醒 MessageQueue,next() 方法会返回这条消息并将其从单链表中移除
    • 内部通过 synchronized 关键字保证取操作线程安全
  • MessageQueue quit() 方法源码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    // True if the message queue can be quit.
    @UnsupportedAppUsage
    private final boolean mQuitAllowed;

    @UnsupportedAppUsage
    @SuppressWarnings("unused")
    private long mPtr; // used by native code

    private boolean mQuitting;

    void quit(boolean safe) {
    if (!mQuitAllowed) {
    throw new IllegalStateException("Main thread not allowed to quit.");
    }

    synchronized (this) {
    if (mQuitting) {
    return;
    }
    mQuitting = true;

    if (sate) {
    removeAllFutureMessagesLocked();
    } else {
    removeAllMessagesLocked();
    }

    // We can assume mPtr != 0 because mQuitting was previously false.
    nativeWake(mPtr);
    }
    }

    private native static void nativeWake(long ptr); // native 方法
    • Looperquit() 方法和 quitSafely() 方法内部调用的都是其 MessageQueue 的 quit() 方法

4. Looper 工作原理概述

  • 官方文档定义

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    /**
    * Class used to run a message loop for a thread. Threads by default do
    * not have a message loop associated with them; to create one, call
    * {@link #prepare} in the thread that is to run the loop, and then
    * {@link #loop} to have it process messages until the loop is stopped.
    *
    * <p>This is a typical example of the implementation of a Looper thread,
    * using the separation of {@link #prepare} and {@link #loop} to create an
    * initial Handler to communicate with the Looper.
    *
    * <pre>
    * class LooperThread extends Thread {
    * public Handler mHandler;
    *
    * public void run() {
    * Looper.prepare();
    *
    * mHandler = new Handler() {
    * public void handleMessage(Message msg) {
    * // process incoming messages here
    * }
    * };
    *
    * Looper.loop();
    * }
    * }</pre>
    */
    public final class Looper {
    // bla bla bla
    }
  • Looper 的主要作用是它会不停地从 MessageQueue 中查看是否有新消息,如果有新消息就会立刻处理,否则就一直阻塞

  • Looper 的构造方法 Looper() 方法源码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    @UnsupportedAppUsage
    final MessageQueue mQueue;
    final Thread mThread;

    private Looper(boolean quitAllowed) {
    mQueue = new MessageQueue(quitAllowed);
    mThread = Thread.currentThread();
    }

    public static native java.lang.Thread currentThread(); // native 方法
    • 在构造方法中会创建一个 MessageQueue 消息队列,然后将当前线程对象保存起来
  • 通过 Looper.prepare() 方法即可为当前线程创建一个 Looper,接着通过 Looper.loop() 方法来开启消息循环。源码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    // sThreadLocal.get() will return null unless you've called prepare().
    @UnsupportedAppUsage
    static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

    /** Initialize the current thread as a looper.
    * This gives you a chance to create handlers that then reference
    * this looper, before actually starting the loop. Be sure to call
    * {@link #loop()} after calling this method, and end it by calling
    * {@link #quit()}.
    */
    public static void prepare() {
    prepare(true);
    }

    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));
    }
    • ActivityThread 的入口方法 main() 方法的 Looper.getMainLooper() 可知: 主线程默认已经创建了 Looper子线程需要自己创建 Looper
    • 每个线程只能有一个 Looper,否则抛出异常:"Only one Looper may be created per thread"
    • 因为 TheadLocal 是与线程绑定的,所以 Looper 与 Thread 之间是通过 ThreadLocal 关联的
  • Looper 除了 prepare() 方法外,还提供了 prepareMainLooper() 方法,这个方法主要是给主线程也就是 ActivityThread 创建 Looper 使用的,其本质也是通过 prepare() 方法来实现的,现已废弃 @Deprecated。源码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    // sThreadLocal.get() will return null unless you've called prepare().
    @UnsupportedAppUsage
    static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

    @UnsupportedAppUsage
    private static Looper sMainLooper; // guarded by Looper.class

    /**
    * Initialize the current thread as a looper, marking it as an
    * application's main looper. See also: {@link #prepare()}
    *
    * @deprecated The main looper for your application is created by the Android environment,
    * so you should never need to call this function yourself.
    */
    @Deprecated
    public static void prepareMainLooper() {
    prepare(false);
    synchronized(Looper.class) {
    if (mMainLooper != null) {
    throw new IllegalStateException("The main Looper has already been prepared.");
    }
    sMainLooper = myLooper();
    }
    }

    /**
    * Return the Looper object associated with the current thread. Returns
    * null if the calling thread is not associated with a Looper.
    */
    public static @Nullable Looper myLooper() {
    return sThreadLocal.get();
    }
    • 子线程通过 Looper.prepare() 方法创建了当前线程的 Looper 之后,子线程可以通过 Looper.myLooper() 方法获取当前线程的 Looper
    • 从注释可知,主线程的 Looper 由 Android 环境创建上层开发者不应该调用 Looper.prepareMainLooper() 方法
  • 由于主线程的 Looper 比较特殊,所以 Looer 提供了一个 getMainLooper() 方法,通过它可以在任何地方获取到主线程的 Looper。源码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    @UnsupportedAppUsage
    private static Looper sMainLooper; // guarded by Looper.class

    /**
    * Returns the application's main looper, which lives in the main thread of the application.
    */
    public static Looper getMainLooper() {
    synchronized (Looper.class) {
    return sMainLooper;
    }
    }
    • Looper.getMainLooper() 方法在我们开发 Library 时很有用,毕竟不知道别人在调用使用你的库时会在哪个线程初始化。所以我们在创建 Handler 时每次都通过指定主线程的 Looper 的方式保证库的正常运行
  • 判断当前线程是否是主线程的方法

    • 方法一(从 Looper 的角度):Looper.myLooper() == Looper.getMainLooper();
    • 方法二(从 Thread 的角度):Looper.getMainLooper().getThread() == Thread.currentThread();
    • 方法三(方法二的简化版本):Looper.getMainLooper().isCurrentLooper();
  • Looper 不会自动退出但 Looper 提供了 quit() 方法和 quitSafely() 方法来退出一个 Looper。二者的区别,quit() 方法会直接退出 Looper;quitSafely() 方法只是设定一个退出标记,然后把消息队列中的已有消息处理完毕后才安全退出。源码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    @UnsupportedAppUsage
    final MessageQueue mQueue;

    /**
    * Quits the looper.
    * <p>
    * Causes the {@link #loop} method to terminate without processing any
    * more message in the message queue.
    * </p><p>
    * Any attempt to post messages to the queue after the looper is asked to quit will fail.
    * For example, the {@link Handler#sendMessage(Message)} method will return false.
    * </p><p class="note">
    * Using this method may be unsafe bacause some message may not be delivered
    * before the looper terminates. Consider using {@link #quitSafely} instead to ensure
    * that all pending work is completed in an orderly manner.
    * </p>
    *
    * @see #quitSafely
    */
    public void quit() {
    mQueue.quit(false);
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    /**
    * Quits the looper safely.
    * <p>
    * Causes the {@link #loop} method to terminate as soon as all remaining messages
    * in the message queue that already due to be delivered have been handled.
    * However pending delayed messages with due times in the future will not be
    * delivered before the loop terminates.
    * </p><p>
    * Any attempt to post messages to the queue after the looper is asked to quit will fail.
    * For example, the {@link Handler#sendMessage(Message)} method will return false.
    * </p>
    */
    public void quitSafely() {
    mQueue.quit(true);
    }
  • Looper 退出后,通过 Handler 发送的消息会失败,这个时候 Handler 的 send() 方法会返回 false

  • 在子线程中,如果手动为其创建了 Looper,那么在所有的事情完成以后应该调用 quit() 方法来终止消息循环,否则这个子线程就会一直处于等待的状态,而如果退出 Looper 以后,这个线程就会立刻终止,因此建议不需要的时候终止 Looper

  • Looper 最重要的一个方法是 loop() 方法,只有调用了 loop() 方法后,消息循环系统才会真正起作用loop() 方法源码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    import android.util.Slog;

    @UnsupportedAppUsage
    final MessageQueue mQueue;
    private boolean mInLoop;

    /**
    * Run the message queue int this thread. Be sure to call
    * {@link #quit()} to end the loop.
    */
    public static void loop() {
    final Looper me = myLooper();
    if (me == null) {
    throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
    }
    if (me.mInLoop) {
    Slog.w(TAG, "Loop again would have the queued messages be executed"
    + " before this oce completed.");
    }

    me.mInLoop = true;
    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.
    Binder.clearCallingIdentity();
    final long ident = Binder.clearCallingIdentity();

    // Allow overriding a threshold with a system prop. e.g.
    // adb shell 'setprop log.looper.1000.main.slow 1 && stop && start'
    final int thresholdOverride = SystemProperties.getInt("log.looper." + Process.myUid() + "." + Thread.currentThread().getName() + ".slow", 0);

    boolean slowDeliveryDetected = false;

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

    // This musb be in a local variable, in case a UI event sets the logger
    final Printer logging = me.mLogging;
    if (logging != null) {
    logging.println(">>>>> Dispatching to " + msg.target + " " + msg.callback + ": " + msg.what);
    }
    // Make sure the observer won't change while processing a transaction.
    final Observer observer = sObserver;

    final long traceTag = me.mTraceTag;
    long slowDispatchThresholdMs = me.mSlowDispatchThresholdMs;
    long slowDeliveryThresholdMs = me.mSlowDeliveryThresholdMs;
    if (thresholdOverride > 0) {
    slowDispatchThresholdMs = thresholdOverride;
    slowDeliveryThresholdMs = thresholdOverride;
    }
    final boolean logSlowDelivery = (slowDeliveryThresholdMs > 0) && (msg.when > 0);
    final boolean logSlowDispatch = (slowDispatchThresholdMs > 0);

    final boolean needStartTime = logSlowDelivery || logSlowDispatch;
    final boolean needEndTime = logSlowDispatch;

    if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
    Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
    }

    final long dispatchStart = needStartTime ? SystemClock.uptimeMillis() : 0;
    final long dispatchEnd;
    Object token = null;
    if (observer != null) {
    token = observer.messageDispatchStarting();
    }
    long origWorkSource = ThreadLocalWorkSource.setUid(msg.workSourceUid);
    try {
    msg.target.dispatchMessage(msg); // 分发消息,非常关键的一行代码
    if (observer != null) {
    observer.messageDispatched(token, msg);
    }
    dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
    } catch (Exception exception) {
    if (observer != null) {
    observer.dispatchingThrewException(token, msg, exception);
    }
    throw exception;
    } finally {
    ThreadWorkSource.restore(origWorkSource);
    if (traceTag != 0) {
    Trace.traceEnd(traceTag);
    }
    }
    if (logSlowDelivery) {
    if (slowDeliveryDetected) {
    if ((dispatchStart - msg.when) <= 10) {
    Slog.w(TAG, "Drained");
    slowDeliveryDetected = false;
    }
    } else {
    if (showSlowLog(slowDeliveryThresholdMs, msg.when, dispatchStart, "delivery", msg)) {
    // Once we write a slow delivery log, suppress until the queue drains.
    slowDeliveryDetected = true;
    }
    }
    }
    if (logSlowDispatch) {
    showSlowLot(slowDispatchThresholdMs, dispatchStart, dispatchEnd, "dispatch", msg);
    }

    if (logging != null) {
    logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
    }

    // Make sure that during the course of dispatching the
    // identity of the thread wasn't corrupted.
    final long newIdent = Binder.clearCallingIdentity();
    if (ident != newIdent) {
    // Log.wtf(String tag, String msg): What a Terrible Failure: Report a condition that should never happen.
    Log.wtf(TAG, "Thread identity changed from 0x"
    + Long.toHexString(ident) + " to 0x"
    + Long.toHexString(newIdent) + " while dispatching to "
    + msg.target.getClass().getName() + " "
    + msg.callback + " what=" + msg.what);
    }

    msg.recycleUnchecked();
    }
    }

    /**
    * bla bla bla
    */
    @CriticalNative -->: https://source.android.google.cn/devices/tech/dalvik/improvements?hl=zh-cn
    public static final native long clearCallingIdentity(); // native 方法
    • loop() 方法的作用就是从消息队列 MessageQueue 中不断地取出并分发消息

    • 调用 Looper.loop() 方法之前必须要调用一次 Looper.prepare() 方法,否则抛出异常:"No Looper; Looper.prepare() wasn't called on this thread."

      • loop() 方法是一个死循环唯一跳出循环的方式是 MessageQueue 的 next() 方法返回了 null。当 Looper 的 quit() 方法被调用时,Looper 就会调用 MessageQueue 的 quit() 或者 quitSafely() 方法来通知消息队列退出,当消息队列被标记为退出状态时,它的 next() 方法就会返回 null。即 Looper 必须退出,否则 loop() 方法就会无限循环下去
    • loop() 方法会调用 MessageQueue 的 next() 方法来获取最新消息,而 next() 方法是一个阻塞操作,当没有消息时,next() 方法会一直阻塞在那里,这也导致 loop() 方法一直阻塞在那里

    • 如果 MessageQueue 的 next() 方法返回了新消息,Looper 就会处理这条消息:msg.target.dispatchMessage(msg);

      • 这里的 msg.target 是发送这条消息的 Handler 对象,这样 Handler 发送的消息最终又交给它的 dispatchMessage() 方法来处理了。即,当创建有多个 Handler 时,通过 target 字段可以做到 Handler 一一对应、各自处理自己发出的消息
      • 这里不同的是,Handler 的 dispatchMessage() 方法是在创建 Handler 时所使用的 Looper 中执行的,这样就成功地将代码逻辑切换到指定的线程中去执行了
    • msg.recycleUnchecked() 方法表示 Message 回收复用,放入 Message#sPool 中,使用的是享元设计模式

    • Android 中为什么主线程不会因为 Looper.loop() 里的死循环卡死

      • 原理涉及到 Linux 的 pipe/epoll 机制
      • 简单说就是在主线程的 MessageQueue 没有消息时,便阻塞在 queue.next() 方法中的 nativePollOnce() 本地方法里,最终调用到 epoll_wait() 进行阻塞等待,此时主线程会释放 CPU 资源进入休眠状态,直到下个消息或者有事务发送,通过往 pipe 管道写入数据来唤醒主线程工作
      • 这里采用的是 epoll 机制,是一种 IO 多路复用机制,可以同时监控多个描述符,当某个描述符就绪(读或写就绪),则立刻通知相应程序进行读或写操作,本质同步 IO,即读写是阻塞的
      • 所以,主线程大多数时间都是处于休眠状态,并不会消耗大量 CPU 资源
        1. 表面上看 epoll 的性能最好,但在连接数少并且连接都十分活跃的情况下,select 和 poll 的性能可能比 epoll 好,毕竟 epoll 的通知机制需要很多函数回调
        2. select 低效是因为每次它都需要轮询,但低效也是相对的,视情况而定,也可通过良好的设计改善
  • Looper.getMainLooper().setMessageLogging() 方法可以打印出主线程消息队列中的消息,可方便排查卡顿方面的性能优化问题

    1
    2
    logging.println(">>>>> Dispatching to " + msg.target + " " + msg.callback + ": " + msg.what);
    logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);

5. Handler 工作原理概述

  • 官方文档定义

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    /**
    * A Handler allows you to send and process {@link Message} and Runnable
    * objects associated with a thread's {@link MessageQueue}. Each Handler
    * instance is associated with a single thread and that thread's message
    * queue. When you create a new Handler it is bound to a {@link Looper}.
    * It will deliver messages and runnables to that Looper's message
    * queue and execute them on that Looper's thread.
    *
    * <p>There are two main uses for a Handler: (1) to schedule messages and
    * runnables to be executed at some point in the future; and (2) to enqueue
    * an action to be performed on a different thread than your own.
    *
    * <p>Scheduling messages is accomplished with the
    * {@link #post}, {@link #postAtTime(Runnable, long)},
    * {@link #postDelayed}, {@link #sendEmptyMessage},
    * {@link sendMessage}, {@link #sendMessageAtTime}, and
    * {@link #sendMessageDelayed} methods. The <em>post</em> version allow
    * you to enqueue Runnable objects to be called by the message queue when
    * they are received; the <em>sendMessage</em> version allow you to enqueue
    * a {@link Message} object containing a bundle of data that will be
    * processed by the Handler's {@link #handleMessage} method (requiring that
    * you implement a subclass of Handler).
    *
    * <p>When posting or sending to a Handler, you can either
    * allow the item to be processed as soon as the message queue is ready
    * to do so, or specify a delay before it gets processed or absolute time for
    * it to be processed. The latter two allow you to implement timeouts,
    * ticks, and other timeing-based behavior.
    *
    * <p>When a
    * process is created for your application, its main thread is dedicated to
    * running a message queue that takes care of managing the top-level
    * application objects(activities, broadcast receivers, etc) and any windows
    * they create. You can create your own threads, and communicate back with
    * the main application thread through a Handler. This is done by calling
    * the same <em>post</em> or <em>sendMessage</em> methods as before, but from
    * your new thread. The given Runnable or Message will then be scheduled
    * in the Handler's message queue and processed when appropriate.
    */
    public class Handler {
    // bla bla bla

    @UnsupportedAppUsage
    final Looper mLooper;
    final MessageQueue mQueue;
    @UnsupportedAppUsage
    final Callback mCallback;
    final boolean mAsynchronous;
    @UnsupportedAppUsage
    IMessenger mMessenger;

    /**
    * Callback interface you can use when instantiating a Handler to avoid
    * having to implement your own subclass of Handler.
    */
    public interface Callback {
    /**
    * @param msg A {@link android.os.Message Message} object
    * @return True if no further handling is desired
    */
    boolean handleMessage(@NonNull Message msg);
    }

    /**
    * Subclass must implement this to receive message.
    */
    public void handleMessage(@NonNull Message msg) {

    }

    // bla bla bla

    /**
    * Handle system messages here.
    */
    public void dispatchMessage(@NonNull Message msg) {
    if (msg.callback != null) {
    handleCallback(msg);
    } else {
    if (mCallback != null) {
    if (mCallback.handleMessage(msg)) {
    return;
    }
    }
    handleMessage(msg);
    }
    }

    // bla bla bla
    }
  • Handler 构造方法 Handler() 系列源码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    /*
    * Set this flag to true to detect anonymous, local or member classes
    * that extend this Handler class and that are not static. These kind
    * of classes can potentially create leaks.
    */
    private static fianl boolean FIND_POTENTIAL_LEAKS = false;


    /**
    * Use the {@link Looper} for the current thread with the specified callback interface
    * and set whether the handler should be asynchronous.
    *
    * Handlers are synchronous by default unless this constructor is used to make
    * one that is strictly asynchronous.
    *
    * Asynchronous message represent interrupts or events that do not require global ordering
    * with respect to synchronous messages. Asynchronous messages are no subject to
    * the synchronization barriers introduced by {@link MessageQueue#enqueueSyncBarrier(long)}.
    *
    * @param callback The callback interface in which to handle messages, or null.
    * @param async If true, the handler calls {@link Message#setAsynchronous(boolean)} for
    * each {@link Message} that is sent to it or {@link Runnable} that is posted to it.
    *
    * @hide
    */
    public Handler(@Nullable Callback callback, boolean async) {
    if (FIND_POTENTIAL_LEAKS) {
    final Class<? extends Handler> klass = getClass(); // 这个 klass 是不是手误。。。
    if ((klass.isAnonymousClass() || klass.isMemberClass || klass.isLocalClass()) &&
    (klass.getModifiers() & Modifier.STATIC == 0)) {
    Log.w(TAG, "The following Handler class should be static or leaks might occur: " + klass.getCanonicalName());
    }
    }

    mLooper = Looper.myLooper();
    if (mLooper == null) {
    throw new RuntimeException("Can't create handler inside thread " + Thread.currentThread() + " that has not called Looper.prepare()");
    }
    mQueue = mLooper.mQueue;
    mCallback = callback;
    mAsynchronous = async;
    }
    • mLooper = Looper.myLooper(); 通过获取当前线程的 Looper 与 Handler 绑定

    • 在子线程弹 Toast 或者 Dialog 会崩溃报错java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()

      • 分析 Toast 源码可知,本质是因为 Toast 和 Dialog 的实现依赖于 Handler,底层通过 Binder 完成绘制
      • 子线程使用 Handler 的要求修改即可,即在弹 Toast 或 Dialog 的前后分别调用 Looper.prepare()Looper.loop()。但是,不建议子线程弹 Toast 或者 Dialog,因为子线程无法执行结束的话会导致内存泄漏
    • 注解 @hide 标记的 API 为非开放 API,即参数 async 默认为 false,可以指定该参数的 API 标记为非开放 API

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      /**
      * Use the provided {@link Looper} instead of the default one and take a callback
      * interface in which to handle messages. Also set whether the handler
      * should be asynchronous.
      *
      * Handlers are synchronous by default unless this constructor is used to make
      * one that is strictly asynchronous.
      *
      * Asynchronous messages represent interrupts or events that do not require global ordering
      * with respect to synchronous messages. Asynchronous messages are not subject to
      * the synchronization barriers introduced by conditions such as display vsync.
      *
      * @param looper The looper, must not be null.
      * @param callback The callback interface in which to handle messages, or null.
      * @param async If true, the handler calls {@link Message#setAsynchronous(boolean)} for
      * each {@link Message} that is sent to it or {@link Runnable} that is posted to it.
      *
      * @hide
      */
      @UnsupportedAppUsage
      public Handler(@NonNull Looper looper, @Nullable Callback callback, boolean async) {
      mLooper = looper;
      mQueue = looper.mQueue;
      mCallback = callback;
      mAsynchronous = async;
      }
    • mLooper = looper; 通过构造方法传参使 Looper 与 Hanlder 绑定

    • mQueue = looper.mQueue; 参数 Looper 的 MessageQueue 与 Handler 绑定

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      /**
      * Default constructor associates this handler with the {@link Looper} for the
      * current thread.
      *
      * If this thread does not have a looper, this handle 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 somtimes 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); }

      @Deprecated
      public Handler(@Nullable Callback callback) { this(callback, false); }
    • 从注释可知,构造 Handler 时应明确指定对应的 Looper,没有明确指定 Looper 的 API 标记为 @Deprecated 已废弃

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      /**
      * Use the provided {@link Looper} instead of the default one.
      *
      * @param looper The looper, must not be null.
      */
      public Handler(@NonNull Looper looper) { this(looper, null, false); }

      public Handler(@NonNull Looper looper, @Nullable Callback callback) { this(looper, callback, false); }

      @UnsupportedAppUsage
      public Handler(boolean async) { this(null, async); }
  • Handler 的工作主要包含消息的发送接收过程。消息的发送可以通过 post() 的一系列方法以及 send() 的一系列方法来实现,post() 的一系列方法最终是通过 send() 的一系列方法来实现的

  • Handler post() 系列方法源码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    /**
    * Cause the Runnable r to be added to the message queue.
    * The runnable will be run on the thread to which this handler is
    * attached.
    *
    * @param r The Runnable that will be executed.
    *
    * @return Returns true if the Runnable was successfully placed in to the
    * message queue. Returns false on failure, usually because the
    * looper processing the message queue is exiting.
    */
    public final boolean post(@NonNull Runnable r) {
    return sendMessageDelayed(getPostMessage(r), 0);
    }

    private static Message getPostMessage(Runnable r) {
    Message m = Message.obtain();
    m.callback = r;
    return m;
    }
    1
    2
    3
    4
    5
    6
    7
    public final boolean postAtTime(@NonNull Runnable r, long uptimeMillis) {
    return sendMessageAtTime(getPostMessage(r), uptimeMillis);
    }

    public final boolean postAtTime(@NonNull Runnable r, @Nullable Object token, long uptimeMillis) {
    return sendMessageAtTime(getPostMessage(r, token), uptimeMillis);
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    public final boolean postDelayed(@NonNull Runnable r, long delayMillis) {
    return sendMessageDelayed(getPostMessage(r), delayMillis);
    }

    public final boolean postDelayed(Runnable r, int what, long delayMillis) {
    return sendMessageDelayed(getPostMessage(r).setWhat(what), delayMillis);
    }

    public final boolean postDelayed(@NonNull Runnable r, @Nulable Object token, long delayMillis) {
    return sendMessageDelayed(getPostMessage(r, token), delayMillis);
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    /**
    * Posts a message to an object that implements Runnable.
    * Causes the Runnable r to executed on the next iteration through the
    * message queue. The runnable will be run on the thread to which this
    * handler is attached.
    * <b>This method is only for use in very special circumstances -- it
    * can easily starve the message queue, cause ordering problems, or have
    * other unexpected side-effects.</b>
    *
    * @param r The Runnable that will be executed.
    *
    * @return Returns true if the messages was successfully placed in to the
    * message queue. Returns false on failure, usally because the
    * looper processing the message queue is exiting.
    */
    public final boolean postAtFrontOfQueue(@NonNull Runnable r) {
    return sendMessageAtFrontOfQueue(getPostMessage(r));
    }
    • 由注释可知,该方法要慎用,容易引起 unexpected side-effects.
  • Handler send() 系列方法源码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    /**
    * Pushs a message onto the end of the message queue after all pending messages
    * before the current time. It will be received in {@link #handleMessage},
    * in the thread attached to this handler.
    *
    * @return Returns true if the message was successfully placed in to the
    * message queue. Returns false on failure, usually because the
    * looper processing the message queue is exiting.
    */
    public final boolean sendMessage(@NonNull Message msg) { return sendMessageDelayed(msg, 0); }
    1
    2
    3
    4
    5
    6
    7
    8
    /**
    * Send s Message containing only the what value.
    *
    * @return Returns true if the message was successfully placed in to the
    * message queue. Returns false on failure, usually because the
    * looper processing the message queue is exiting.
    */
    public final boolean sendEmptyMessage(int what) { return sendEmptyMessageDelayed(what, 0) }
    1
    2
    3
    4
    5
    public final boolean sendEmptyMessageDelayed(int what, long delayMillis) {
    Message msg = Message.obtain();
    msg.what = what;
    return sendMessageDelayed(msg, delayMillis);
    }
    1
    2
    3
    4
    5
    public final boolean sendEmptyMessageAtTime(int what, long uptimeMillis) {
    Message msg = Message.obtain();
    msg.what = what;
    return sendMessageAtTime(msg, uptimeMillis);
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
    if (delayMillis < 0) {
    delayMillis = 0;
    }
    return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
    }

    /**
    * Return milliseconds since boot, not counting time spent in deep sleep.
    * d
    * @Return milliseconds of non-sleep uptime since boot.
    */
    @CriticalNative
    natiev public static long uptimeMillis(); // 位于 SystemClock.java 中,是一个 native 方法
    1
    2
    3
    4
    5
    6
    7
    8
    9
    public boolean sendMessageAtTime(@NonNull Message msg, long uptimeMillis) {
    MessageQueue queue = mQueue;
    if (queue == null) {
    RuntimeException e = new RuntimeException(this + " sendMessageAtTime() called with no mQueue");
    Log.w("Looper", e.getMessage(), e);
    return false;
    }
    return enqueueMessage(queue, msg, uptimeMillis);
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    public final boolean sendMessageAtFrontOfQueue(@NonNull Message msg) {
    MessageQueue queue = mQueue;
    if (queue == null) {
    RuntimeException e = new RuntimeException(this + " sendMessageAtTime() called with no mQueue");
    Log.w("Looper", e.getMessage(), e);
    return false;
    }
    return enqueueMessage(queue, msg, 0);
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    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 发送消息都会调用到 enqueuMessage() 方法,最终都会调用到 MessageQueue 的同名的 enqueueMessage() 方法
    • Handler#enqueueMessage() 方法内部会把 Handler 自己的引用赋值给 Message 的 target 字段
    • Handler 线程切换的原理
      • 线程之间是共享资源的,子线程通过 post()send() 系列方法发送消息
      • 然后通过 Looper.loop() 方法在消息队列中轮询检索消息
      • 然后交给 handler.dispatchMessage() 方法进行消息的分发
      • 最后调用 handler.handleMessage() 方法里完成消息的处理
  • 分析源码可知

    • Handler 发送消息的过程仅仅是向消息队列中插入一条消息
    • MessageQueue 的 next() 方法就会返回这条消息给 Looper,Looper 收到消息后就开始处理
    • 最终消息由 Looper 交由 Handler 处理,即 Handler 的 dispathMessage() 方法会被调用
    • 这时 Handler 就进入了处理消息的阶段,dispatchMessage() 方法内部会调用 handleMessage() 方法
  • Handler dispatchMessage() 方法源码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    /**
    * Handle system messages here.
    */
    public void dispatchMessage(@NonNull Message msg) {
    if (msg.callback != null) {
    handleCallback(msg);
    } else {
    if (mCallback != null) {
    if (mCallback.handleMessage(msg)) {
    return;
    }
    }
    handleMessage(msg);
    }
    }
    • Handler 的消息处理顺序

      • 首先,检测 Message 的 callback 是否为 null,不为 null 就通过 handleCallback() 方法来处理消息。Message 的 callback 是一个 Runnable 对象,实际上就是 Handler 的 post() 方法所传递的 Runnable 参数。这个 Runnable 对象只是调用了它的 run() 方法,并没有开启一个线程。handleCallback() 方法源码:

        1
        2
        3
        private static void handleCallback(Message message) {
        message.callback.run();
        }
      • 其次,检查 mCallback 是否为 null,不为 null 就调用 mCallback 的 handleMessage() 方法来处理消息,该方法的返回值是一个布尔值,起到拦截的作用。Callback 是一个接口,定义如下:

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        /**
        * Callback interface you can use when instantiating a Handler to avoid
        * having to implement your own subclass of Handler.
        */
        public interface Callback {
        /**
        * @param msg A {@link android.os.Message Message} object
        * @return True if no further handling is desired
        */
        boolean handleMessage(@NonNull Message msg);
        }
        • 通过 Callback 可以采用如下方式来创建 Handler 对象:Handler handler = new Handler(callback);Callback 的意义是:可以用来创建一个 Handler 的实例但并不需要派生 Handler 的子类
        • 日常开发中,创建 Handler 最常见的方式就是派生一个 Handler 的子类并重写其 handleMessage() 方法来处理具体的消息,而 Callback 提供了另外一种使用 Handler 的方式,当不想派生子类时,就可以通过 Callback 来实现
      • 最后,调用 Handler 的 handleMessage() 方法来处理消息。从方法实现代码可知:dispatchMessage() 方法内部流程很重要,有三处可以 handle 处理消息的地方

  • Handler 使用不当造成内存泄漏的原因及解决方案

    • 现象:使用 Handler 发送延时消息,如果在延时期间(消息未到达或者正在处理)关闭了 Activity(同理上下文、Fragment),此时会造成 Activity 内存泄漏

    • 原因:延时消息 Message 会持有 Handler 的引用,又因为 Java 的特性,内部类会持有外部类,所以外部的 Activity 会被内部的 Handler 持有,即引用链:MessageQueue -> Message -> Handler -> Activity

    • 方案:1)将 Hanlder 定义成静态内部类,在内部持有 Activity 的弱引用。2)及时移除消息队列中的所有消息。Demo 如下:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      private static class SafeHandler extends Handler {

      private WeakReference<HandlerActivity> weakRef; // 指定弱引用的泛型为外部 Activity

      public SafeHandler(HandlerActivity activity) {
      this.weakRef = new WeakReference(activity); // 在 Handler 的内部持有外部 Activity 的弱引用
      }

      @Override
      public void handleMessage(final Message msg) {
      HandlerActivity activity = weakRef.get();
      if (activity != null) {
      activity.handleMessage(msg); // 处理延时消息
      }
      }
      }
      1
      2
      3
      4
      5
      6
      // HandlerActivity.java
      @Override
      protected void onDestroy() {
      safeHandler.removeCallbacksAndMessages(null); // 查看方法的 API 可知:方法参数为空时,all callbacks and messages will be removed.
      super.onDestroy();
      }
      • 静态内部类不依赖外部类的实例,通常情况下实例是强引用,所以静态内部类更容易被回收也就更不容易造成内存泄漏
      • Java 中的四种引用,按照实例对象引用的强弱顺序依次是:强引用 > 软引用 > 弱引用 > 虚引用,在希望内存及时回收的场景(可以手动调用 System.gc())一般使用弱引用
      • 及时移除消息时,方法调用链Handler#removeCallbacksAndMessages(null) -> MessageQueue#removeCallbacksAndMessages(null)。但单纯在 Activity 的 onDestroy() 方法里移除消息并不保险,因为在某些操作场景下 onDestroy() 方法并不一定会执行
      • 当延时消息正在处理还没有处理完毕时,此时关闭界面,依然会导致内存泄漏。经过测试可知,消息还是会被处理,即 handleMessage() 方法还是会收到消息,引用链MessageQueue -> Message -> Handler -> Activity,所以 Handler 和 Activity 都不会被回收导致内存泄漏
  • Handler 的同步屏障机制

    • 作用:异步消息优先执行

    • 原理:

      • MessageQueue#postSyncBarrier() 发送同步屏障,MessageQueue#removeSyncBarrier() 移除同步屏障。如果消息队列队头是一个发送了同步屏障的消息的话,那么它后面所有的同步消息就都被拦截住了,直到这个同步屏障消息被移除出队列,否则主线程就一直不会去处理同步屏障后面的同步消息
      • 所有的消息默认都是同步消息,只有手动设置了异步标志,这个消息才会是异步消息。另外,同步屏障消息只能由内部来发送,这个接口没有开放给上次开发者使用
    • 场景:

      • Choreographer 原理

      • Choreographer 里所有跟 message 有关的代码,都手动设置了异步消息的标志,所以这些操作是不受到同步屏障影响的。这样做的原因可能就是为了尽可能保证上层 app 在接收到屏幕刷新信号时,可以在第一时间执行遍历绘制 View 树的工作

      • Choreographer 过程中的动作也都是异步消息,这样可以确保 Choreographer 的顺利运转,也确保了第一时间执行 doTraversal(),这个过程如果有其他同步消息也无法得到处理,都要等到 doTraversal() 之后

      • 因为主线程中如果有太多消息要执行,而这些消息又是根据时间戳进行排序,如果不加一个同步屏障的话,那么遍历绘制 View 树的工作就可能被迫延迟执行,因为它也需要排队,那么久有可能出现当一帧快结束时才开始计算屏幕数据,即时这次的计算时间少于 16.6ms,也同样会造成丢帧现象。造成丢帧大体有两类原因:

        • 遍历绘制 View 树计算屏幕数据的时间超过了 16.6ms
        • 主线程一直在处理其他耗时的消息,导致遍历绘制 View 树的工作迟迟不能开始,从而超过了 16.6ms 底层切换下一帧画面的时机
      • 同步屏障机制只是尽可能去做接收到屏幕刷新信号就及时处理,但并不能保证一定可以第一时间处理。因为同步屏障是在 scheduleTraversals() 方法被调用时才发送消息到消息队列的,即只有当某个 View 发起了刷新请求,在这个时刻的后面同步消息才会被拦截掉。如果在 scheduleTraversals() 之前就发送到消息队列里的工作仍然会按顺序依次被取出来执行

      • 绝大多数 Android 设备屏幕的显示屏的刷新率都是 60HZ,所以场同步周期 vsync period = 1000/60 = 16.6ms。可通过 Choreographer.getInstance().postFrameCallback() 来监听帧率情况

  • Handler 中的同步方法:同步方法 runWithScissors() 可以让 Handler#post() 消息执行之后然后再继续往下执行。源码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    /**
    * Runs the specified task synchronously.
    * <p>
    * If the current thread is the same as the handler thread, then the runnale
    * runs immediately without being enqueued. Otherwise, posts the runnable
    * to the handler and waits for it to complete before returning.
    * </p><p>
    * This method is dangerous! Improper use can result in deadlocks.
    * Never call this method while any locks are held or use it in a
    * possibly re-entrant manner.
    * </p><p>
    * This method is occasionally useful in situations where a background thread
    * must synchronously await completion of a task that must run on the
    * handler's thread. However, this problem is often a symptom of bad design.
    * Consider improving the design(if possible) before resorting to this method.
    * </p><p>
    * One example of where you might want to use this method is when you just
    * set up a Handler thread and need to perform some initialization steps on
    * it before continuing execution.
    * </p><p>
    * If timeout occurs then this method returns <code>false</code> but the runnable
    * will remain posted on the handler and may already be in progress or
    * complete at a later time.
    * </p><p>
    * When using this method, be sure to use {@link Looper#quitSafely} when
    * quitting the looper. Otherwise {@link #runWithScissors} may hang indefinitely.
    * (TODO: We should fix this by making MessageQueue aware of blocking runnables.)
    * </p>
    *
    * @param r The Runnable that will be executed synchronously.
    * @param timeout The timeout in milliseconds, or 0 to wait indefinitely.
    *
    * @return Returns true if the Runnable was successfully executed.
    * Returns false on failure, usually because the
    * looper processing the message queue is exiting.
    *
    * @hide This method is prone to abuse and should probably not be in the API.
    * If we ever do make it part of the API, we might want to remove it to something
    * less funny like runUnsafe().
    */
    public final boolean runWithScissors(@NonNull Runnable r, long timeout) {
    if (r == null) {
    thow new IllegalArgumentException("runnable must not be null");
    }
    if (timeout < 0) {
    throw new IllegalArgumentException("timeout must be non-negative");
    }

    if (Looper.myLooper() == mLooper) {
    r.run();
    return true;
    }

    BlockingRunnable br = new BlockingRunnable(r);
    return br.postAndWait(this, timeout);
    }
    • 方法的注释也是有意思
      • “This method is dangerous!”
      • “TODO: We should fix this by …”
      • “However, this problem is often a symptom of bad design.”
-------------------- 本文结束感谢您的阅读 --------------------