Android 消息机制 - Handler, Looper, Message, MessageQueue 的源码分析

转载请注明本文出自 clevergump 的博客:http://blog.csdn.net/clevergump/article/details/50776596, 谢谢!

一. 前言

Android 的消息系统, 主要涉及到四个类: Handler (注意是 android.os.Handler , 不是 java.util.logging.Handler), Message, MessageQueue 和 Looper. 而我们在日常开发中, 通常是将 Handler 对象定义在主线程中用于更新UI, 这样在我们自己的代码中能够接触到的类也就是 Handler 和 Message. 而一旦你是在子线程中创建 Handler对象, 那么你的代码就很可能会出错, 而且通常都是报如下错误:
这里写图片描述
虽然根据错误提示原因, 你能够大致找到解决的办法, 但是你可能还是不太明白这样解决的原因, 同时你也可能会产生疑惑, 为何我在主线程创建 Handler对象就不会出错, 而到了子线程就会报这个错误呢? 其实, 要想解决类似于上边这样的和 Android消息系统相关的各种问题, 最好的办法就是, 阅读和 Android消息系统相关的这几个类的源码. 阅读他们的源码, 不仅可以让你找到发生异常时的解决办法以及这样解决的原因, 还能让你理解 Android 消息系统的工作原理, 并且还能在你遇到一些特殊需求时, 能够根据你对 Android消息系统的理解, 从而编写出适合你那个特殊场景的代码, 此外, 还能顺便学习源码中很多优秀的设计思想和架构并借鉴到我们自己的代码中, 提升我们自己的代码水平. 所以, 阅读源码对 Android开发者来说, 简直就是一件有百利而无一害的事情啊. 下面我们就来分析一下这几个类的源码吧.


二. 相关类的源码分析

1. Message 源码分析

Message, 也就是消息. Message类中与消息传递相关的字段主要有: target, callback, next, sPool, sPoolSync. 下面分别简要介绍一下:

// 就是调用 sendMessage() 或 post() 等类似方法的那个 Handler对象.
Handler target;
// 一个 Runnable对象. 如果该对象不为 null, 那么在 Handler接收到该 Message对象后, 将不会执行 handleMessage()
// 方法, 而是去执行该 callback的 run()方法. 详情请见 Handler类的 dispatchMessage()方法的源码.
Runnable callback;
// 一个指向其他 Message对象的引用(单向链表). 消息队列(MessageQueue)和 Message类中的消息池的都是基于这种单向链表
// 的结构. 其中, 消息队列(MessageQueue)是一个基于单向链表的队列. 而 Message类中的消息池是一个基于单向链表的栈.
// sometimes we store linked lists of these things
Message next;
// 一个静态的 Message引用. 其实就是Message类中消息池这个栈的栈顶元素.
private static Message sPool;
// 消息池中消息数量发生变化时要预先加上, 结束后要释放掉的一个同步锁.
private static final Object sPoolSync = new Object();
// 消息池中当前已存入的消息引用的数量.
private static int sPoolSize = 0;
// 消息池的容量, 即: 消息池中能存入的消息引用数量的上限值, 池子中的消息数量必须小于或等于这个数值. 池子中存放的消息
// 数量等于这个数值时, 池子就满了, 不能再继续接收更多的消息引用了.
private static final int MAX_POOL_SIZE = 10;

Message类中, 重要方法的源码分析如下:

  • obtain():

    /**
     * Return a new Message instance from the global pool. Allows us to
     * avoid allocating new objects in many cases.
     * 
     * 如果消息池中存放有消息对象, 就直接返回该池子这个栈结构中位于栈顶的那个消息对象 sPool. 如果消息池中没  
     * 有任何消息对象(即: 消息池为空), 才会去创建一个新的消息对象并返回.
     */
    public static Message obtain() {
        // 先对消息池加上同步锁
        synchronized (sPoolSync) {
            // 如果消息池中存放有消息引用, 那么就直接取出消息池这个栈的栈顶的消息引用, 将该引用所指向的
            // 对象作为该方法的返回值.
            if (sPool != null) {
                // 先用一个临时变量 m 也去指向当前栈顶元素 sPool所指向的消息对象.
                Message m = sPool;
                // 由于先前位于栈顶的元素即将出栈, 所以需要将新的栈顶指向先前栈顶元素的下一个元素.
                sPool = m.next;
                // 将栈顶元素与他的下一个元素之间的链断开
                m.next = null;
                // 消息池这个栈中存放的元素数量减一
                sPoolSize--;
                // 返回先前栈顶元素所指向的消息对象
                return m;
            }
        }
        // 如果消息池为空, 才会去创建一个消息对象并返回.
        return new Message();
    }

    在介绍这个方法之前, 需要先了解一下 Message类中有个 “消息池”的设计, 如果你认真分析 obtain() 和 recycle() 这两个方法, 你会发现这个消息池的底层数据结构其实就是一个基于单向链表的栈, 消息的存入和取出满足 “后进先出”(LIFO) 的原则. 而消息池设计的初衷, 就是想充分重复利用创建出来的每一个消息对象, 如果没有消息池, 那么每当我们需要使用一个消息对象时, 我们就必须通过 new Message()的方式来创建, 这样会创建出许多个有可能只会使用一次就不会再次使用的消息对象, 这些对象使用完以后如果不及时清理掉就会一直闲置在内存中, 这既会浪费内存空间, 又会降低系统的性能, 并且频繁地创建对象本身也是一种很耗资源的操作. 所以, 为了节约内存资源并提升性能, 我们需要用消息池来回收掉那些已经不再使用了的消息对象, 下次再需要使用消息对象时, 就可以直接从池子中获取而不用创建新的消息对象了. 当然了, 消息池其实存放的仅仅是各个消息对象的引用而已, 并不是消息对象. 而 Message类中的这个消息池其实是一种基于单向链表的栈结构, 栈顶的消息引用就是 Message类中的 sPool 这个字段. 根据栈的 “后进先出” 的原则, sPool一定是最后进入消息池的, 而一旦我们需要从池子中获取消息引用, sPool 又会是最先被使用的. Message类中的这个消息池最多能存放10个消息对象的引用 (MAX_POOL_SIZE 这个字段就是表示消息池的容量).

    介绍完 Message类中消息池的设计之后, 我们来分析一下 obtain()方法, 这个方法其实就是要获取一个消息对象. 而由于有消息池的设计, 所以需要先看看消息池中是否有可用的对象, 如果有就直接取出栈顶的那个消息引用所指向的对象, 这样就无需创建消息对象了.

    这里要注意的是, 该方法只是获取了一个 Message对象, 但是获取的这个 Message对象中的 target引用(是一个 Handler引用) 仍然是 null. 而如果你分析过后文将要介绍的 Looper类中 quit()方法的源码后, 你会知道, 如果用一个 Handler对象将一个 target引用为 null的 Message对象发送出去的话, 其实这个 Message对象是无法被该 Handler对象处理的, 不仅如此, 而且 Android的消息系统还将这种”发送一个 target为 null的 Message对象到消息队列”的行为赋予了一种特殊的含义, 含义是: 表示结束消息循环.

    只有当消息池为空的时候, 才会去创建消息对象. 该方法的其他几个重载方法与该方法的分析是类似的, 所以就不一一进行分析了.


  • recycle():

    用于将不再使用的消息对象的引用回收到消息池中. 其源码如下:

    /**
     * Return a Message instance to the global pool.  You MUST NOT touch
     * the Message after calling this function -- it has effectively been
     * freed.
     */
    public void recycle() {
        // 将相关变量进行复位. 基本类型的变量清零, 引用设置为null. 
        clearForRecycle();
    
        // 加上同步锁 sPoolSync. 为什么需要同步锁呢? 因为消息池的设计思想是, 消息对象之间不会因为他们先前工作的线程
        // 不同而不同, 消息池中的任何一个消息对象都会随时等待任何一个线程的召唤, 对任何一个线程都是一视同仁的. 所以将
        // 消息池中需要被重复使用的那些消息引用(其实每一次被重复使用的都是栈顶 sPool)以及回收消息的总数量 sPoolSize
        // 都设置为静态的(static), 这样消息池作用的范围就是整个消息类的范围, 而不是局限于某一个消息对象的范围. 正由于
        // 池子中同一个消息引用有可能会被多个线程在同一时间竞争使用, 所以需要加上同步锁, 保证线程安全. 
        synchronized (sPoolSync) {
            // 如果消息池未满, 就可以将当前消息对象回收到消息池中.
            if (sPoolSize < MAX_POOL_SIZE) {
                // 将当前消息引用的下一个引用指向当前消息池中处于栈顶的那个对象.
                next = sPool;
                // 由于当前的消息引用入栈, 所以需要将栈顶指针指向当前的消息引用.
                sPool = this;
                // 将消息池中存放的消息引用数量加一
                sPoolSize++;
            }
            // 如果消息池已满, 那么当前的这个消息对象就无法被回收到消息池中.
        }
    }
    
    // 将相关的变量, 引用都复位.
    /*package*/ void clearForRecycle() {
        flags = 0;
        what = 0;
        arg1 = 0;
        arg2 = 0;
        obj = null;
        replyTo = null;
        when = 0;
        target = null;
        callback = null;
        data = null;
    }

    源码中的注释已经非常详细了, 结合前边介绍的消息池的知识, 我想你应该能够理解这个 recycle()方法的作用和工作原理了吧.

    当然, 以上都是理论分析, 如果你还是有些似懂非懂, 那么我写了一个 Demo工程, 是关于消息池对消息引用的回收与重用, 以及消息池满了以后就无法继续回收消息引用的一个Demo. 如果有兴趣, 可以到 这里 下载. 该 Demo包含了多个 module, 运行名称为 MessagePoolDemo 的那个 module 即可. (由于该 Demo 是用 Android Studio写的, 所以如果不熟悉 Android Studio 的小伙伴, 可以在 Eclipse 中新建一个工程, 然后将 MessagePoolDemo 这个 module的代码复制过去即可, 当然也可以先学习一下 Android Studio 的使用方法, 毕竟这是当前以及未来 Android 开发的潮流. 推荐 stormzhang 的 Android Studio 教程—-Android Studio详细教程汇总 ).

    简单分析一下这个 module, 该 module中有两个用于测试的方法MessageTest.testMessagePoolReuse();MessageTest.testMessagePoolUpperLimit() , 前者是用来测试消息池对消息的回收与重复利用, 后者是用来验证消息池的容量上限是10. 下面分别做简要分析.

    1. 在方法 MessageTest.testMessagePoolReuse();中, 先用 obtain()方法获取3个消息对象, 分别取名为 “msgA”, “msgB”, “msgC”(由于此时消息池为空, 所以其实是用 new Message() 的方式创建这3个对象的), 然后将这3个消息对象回收, 然后再用 obtain()方法获取3个消息对象, 如果这3个对象是新创建的, 那么就分别命名为 “msgD”, “msgE”, “msgF”. 如果这3个消息对象是从消息池中获取的, 那么获取的这3个消息对象就还是先前创建的 “msgA”, “msgB”, “msgC”. 所以, 我们可以根据最后获取的这3个消息对象的名称是不是 “msgA”, “msgB”, “msgC”, 来判断消息池是否起了回收和重复利用消息对象的作用. 这个方法打印的日志如下:
      Android 消息机制 - Handler, Looper, Message, MessageQueue 的源码分析_第1张图片
      通过日志中红框标注的三个”REUSE” 日志可以看出, 消息池确实是重复使用了先前的3个消息对象 “msgA”, “msgB”, “msgC”, 而并没有创建以 “msgD”, “msgE”, “msgF” 为名称的新的消息对象.

    2. 在方法 MessageTest.testMessagePoolUpperLimit() 中, 为了验证消息池的最大容量是10, 我们创建 了 11个消息对象, 然后再分别对这11个消息对象逐一调用 recycle()方法. 该方法打印的日志如下图所示:
      Android 消息机制 - Handler, Looper, Message, MessageQueue 的源码分析_第2张图片
      从日志可以看出, 由于先前消息池为空, 所以前10个消息对象都会被回收, 而当第11个消息对象在调用其 recycle()方法时, 由于此时消息池已满, 所以第11个消息对象将无法被回收, 无法添加到消息池中, 请看上图日志中使用蓝色方框框住的那句 warning—-“Message pool is full. Recycle fails.”, 这就说明了, 消息池的最大容量确实是10, 并且消息池满的时候将无法继续回收消息对象了.


2. Handler 源码分析

Handler 类中与消息传递相关的字段主要有:

// 该 Handler对象的创建语句(也就是 new Handler() 这句代码)所在的那个线程所绑定的 Looper对象. 
final Looper mLooper;
// 消息队列, 该 Handler对象会被绑定到一个 Looper对象上, 而每个 Looper对象又都会被绑定到一个 
// MessageQueue上, 所以, 该 Handler 会被间接绑定到一个 MessageQueue对象上, 这里的 mQueue
// 其实就是这个 MessageQueue对象.
final MessageQueue mQueue;
// 一个内部接口的实例, 用于处理接收到的消息对象中所携带的数据. 优先级高于 handleMessage()方法.
// 详情请见
// dispatchMessage()方法.
final Callback mCallback;

Handler类中常用的方法有:

  • 构造方法:

    Handler类有如下几个构造方法:
    这里写图片描述

    我们只分析红框标出的那两个方法, 因为使用这两个方法非常容易出错. 这两个方法与没有用红框标出的方法相比, 最显著的差异就是, 不需要提供 Looper参数. 正是因为不提供 Looper参数, 才会使得我们在开发时, 如果不熟悉 Handler源码的话, 就非常容易写出错误的代码导致程序抛异常而崩溃. 并且程序崩溃时通常都是因为下面的原因导致的:

    这里写图片描述

    报错语句说的是, 不能在没有调用 Looper.prepare() 的线程中创建 Handler对象, 也就是在 Handler的构造方法内抛出的这个异常. 我们以无参构造方法为例来看看 Handler构造方法的源码:

    /**
     * Default constructor associates this handler with the queue for the
     * current thread.
     *
     * If there isn't one, this handler won't be able to receive messages.
     */
    public Handler() {
        if (FIND_POTENTIAL_LEAKS) {
            final Class klass = getClass();
            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 that has not called Looper.prepare()");
        }
        mQueue = mLooper.mQueue;
        mCallback = null;
    }

    果然, 在第18~20行, 会检测 mLooper是否为 null, 如果为 null, 就会抛出上述异常. 不过这个问题的解决方法倒也简单, 只要根据报错时的提示先调用 Looper.prepare() , 然后再创建 Handler对象即可. 那么我们有理由相信, 这句 Looper.prepare() 代码就一定是在为 mLooper 赋值, 使他不为 null, 那么这个 mLooper到底是什么呢? 为什么必须要先给他赋值才不会报异常呢? 为什么我们在开发时没有调用这句代码, 多数时候都是没问题的, 但偶尔又会遇到这个问题呢? 要想彻底解答这些问题, 只有等到后边分析完 Looper类的源码后, 才能得到答案.——这里暂时标记为 疑问1.

  • obtainMessage():

    /**
     * Returns a new {@link android.os.Message Message} from the global message pool. More 
     * efficient than creating and allocating new instances. The retrieved message has its handler
     * set to this instance (Message.target == this).
     * If you don't want that facility, just call Message.obtain() instead.
     */
    public final Message obtainMessage()
    {
        // 直接调用的是 Message类的 obtain(Handler)方法, 该方法源码见下边.
        return Message.obtain(this);
    }
    
    // Message.obtain(Handler)的源码:
    
    /**
     * Same as {@link #obtain()}, but sets the value for the target member on the Message 
     * returned.
     * @param h  Handler to assign to the returned Message object's target member.
     * @return A Message object from the global pool.
     */
    public static Message obtain(Handler h) {
        // 如果消息池中有消息对象, 那么就直接从消息池中取出一个消息对象; 如果消息池中没有消息对象, 才会创建一个
        // 消息对象.
        Message m = obtain();
        // 将得到的消息对象的 target设置为该 Handler对象.
        m.target = h;
    
        return m;
    }

    该方法直接调用的是 Message类的 obtain(Handler)方法, 而 Message类的 obtain(Handler)方法, 其实就是先调用 Message类的 obtain()这个无参方法先获取一个 Message对象, 然后将该 Handler对象设置为该 Message对象的 target. 其实这里获取 Message对象的过程, 依然是先查找消息池中是否有可复用的消息对象, 如果没有, 才会去创建一个消息对象.

    所以, 我们在使用 Android的消息系统时, 如果要发送消息, 那么不要直接使用 new Message() 的方式创建消息对象, 而要使用 Message.obtain()Handler.obtainMessage() 的方式, 这样可以节省系统资源开销.


  • sendMessage(Message msg) 和 post(Runnable r):

    Handler类中, 可以用以下几个以 send 开头的方法来发送消息:
    Android 消息机制 - Handler, Looper, Message, MessageQueue 的源码分析_第3张图片
    也可以用以下几个以 post开头的方法来发送携带有 Runnable对象的消息:
    这里写图片描述
    而以上所有以 post开头的方法, 又都是调用以 send开头的那些方法. 所以我们只需要分析以 send开头的那些方法, 也就是上边第一张图中的那几个方法. 以 send开头的那些方法执行的原理, 都是将一个给定的消息对象插入到一个消息队列(就是前边介绍的 Handler类中的 mQueue字段) 的过程, 而这些 send方法根据消息对象被插入到消息队列中的位置的不同, 又可以分成两类:

    1. sendMessageAtFrontOfQueue(Message msg) 方法
      也就是用红框标注的那个方法. 该方法是将一个消息对象插入到消息队列的队首, 而不是队尾. 如果你需要让某个消息对象被优先处理, 就可以考虑使用这个方法, 或者使用 postAtFrontOfQueue(Runnable r) 方法在保证消息对象被插入到消息队列队首的同时还能携带一个Runnable参数. 简要分分析一下该方法的源码, 如下所示:

      /**
       * Enqueue a message at the front of the message queue, to be processed on
       * the next iteration of the message loop.  You will receive it in
       * {@link #handleMessage}, in the thread attached to this handler.
       * 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.
       *  
       * @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 sendMessageAtFrontOfQueue(Message msg)
      {
          boolean sent = false;
          MessageQueue queue = mQueue;
          if (queue != null) {
              msg.target = this;
              // 将我们要发送的消息对象 msg 加入到消息队列中, 并设置该消息要执行的时间点为 0 (可以理解为
              // 我们希望该消息能够被立即执行, 所以系统也会满足我们的这一合理要求)
              sent = queue.enqueueMessage(msg, 0);
          }
          else {
              RuntimeException e = new RuntimeException(
                  this + " sendMessageAtTime() called with no mQueue");
              Log.w("Looper", e.getMessage(), e);
          }
          return sent;
      }

      该方法会调用 MessageQueue类的 enqueueMessage(Message msg, long when) 方法, 也就是消息入队的过程, 并将该消息对象的执行时间(即: 参数 when的值)设置为0, 表示我们希望系统能够立即执行该消息. 我们来看看 MessageQueue类中 enqueueMessage(Message msg, long when) 方法的源码吧, 这里只贴出与消息入队相关的代码, 其他代码就省略了, 如下所示:

      // 该方法可以将一个消息对象加入到消息队列中
      final boolean enqueueMessage(Message msg, long when) {
         // ...
         synchronized (this) {
              // ...
              // 将期望该消息 msg 的执行时间 when 设置给 msg内部的 when字段
              msg.when = when;
              // 定义一个临时变量, 指向当前位于队首的消息对象.
              Message p = mMessages;
              // 如果队首没有消息对象了, 即队列为空(p == null), 或者设置让该消息 msg 要立即执行 (when == 0), 
              // 或者 msg的执行时间比当前队首消息对象的执行时间还要早 (when < p.when), 那么就将我们要插入的消息
              // 对象 msg 插入到队首的位置成为新的队首.
              if (p == null || when == 0 || when < p.when) {
                  // 将要入队的这个消息对象 msg 插入到原先队首元素前面的位置
                  msg.next = p;
                  // 将队首指针指向我们新入队的这个消息对象 msg, 使他正式成为新的队首.
                  mMessages = msg;
                  needWake = mBlocked; // new head, might need to wake up
              } 
              // 如果原先队列不为空 (即: p != null), 并且要插入的消息的执行时间不是立即执行 (即: when != 0, 
              // 通常来说都是 > 0) 而且该执行时间还不早于队首消息的执行时间 (即: when >= p.when) 
              else {
                  Message prev = null;
                  // 找到队列中某个位置, 使得该位置之前的消息对象的执行时间都早于这个要插入的消息 msg 的执行时间, 
                  // 该位置之后的消息对象的执行时间晚于这个要插入的消息 msg 的执行时间.-----(ps: 按照消息
                  // 对象的执行时间选择消息对象合适的入队位置, 这就是优先级队列的特征)
                  while (p != null && p.when <= when) {
                      prev = p;
                      p = p.next;
                  }
                  // 将要插入的这个消息对象 msg 插入到前边已经找到的那个位置中.
                  msg.next = prev.next;
                  prev.next = msg;
                  needWake = false; // still waiting on head, no need to wake up
              }
          }
          // ...
          // 如果该消息对象 msg 插入成功, 就返回 true, 表示入队成功.
          return true;
      }
    2. 其他方法
      其实这些方法最终都是调用 sendMessageAtTime(Message msg, long uptimeMillis) 这个方法的, 我们就只分析这个方法吧. 其源码如下:

      /**
       * Enqueue a message into the message queue after all pending messages
       * before the absolute time (in milliseconds) uptimeMillis.
       * The time-base is {@link android.os.SystemClock#uptimeMillis}.
       * You will receive it in {@link #handleMessage}, in the thread attached
       * to this handler.
       * 
       * @param uptimeMillis The absolute time at which the message should be
       *         delivered, using the
       *         {@link android.os.SystemClock#uptimeMillis} time-base.
       *         
       * @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.  Note that a
       *         result of true does not mean the message will be processed -- if
       *         the looper is quit before the delivery time of the message
       *         occurs then the message will be dropped.
       */
      public boolean sendMessageAtTime(Message msg, long uptimeMillis)
      {
          boolean sent = false;
          MessageQueue queue = mQueue;
          if (queue != null) {
              msg.target = this;
              // 也是将给定的消息对象 msg入队. 参数 uptimeMillis 表示希望消息被处理的具体时间, 是个正数.
              sent = queue.enqueueMessage(msg, uptimeMillis);
          }
          else {
              RuntimeException e = new RuntimeException(
                  this + " sendMessageAtTime() called with no mQueue");
              Log.w("Looper", e.getMessage(), e);
          }
          return sent;
      }

      第26行同样是调用了消息队列的入队操作, 只不过传递的参数 uptimeMillis 是我们希望消息被执行的具体时间, 如果该参数是0, 那么该方法就等效于前边分析的 sendMessageAtFrontOfQueue方法, 将msg消息对象插入到消息队列的队首. 如果该参数不是0, 那么该方法在执行消息队列的入队操作时, 也就是在执行 MessageQueue 的 enqueueMessage(Message msg, long when) 方法时, 将会从队首开始向队尾的方向逐一查找, 直到找到某个位置, 使得该位置前后两个消息对象的执行时间刚好满足”前一个小于 msg的执行时间, 而后一个大于 msg的执行时间”的条件, 就把 msg 插入到该位置, 如果全部不满足该条件, 那么就把 msg 插入到队尾. 具体分析可以见前边对 enqueueMessage(Message msg, long when) 方法第22~64行的分析, 也就是 else 内的代码.

    以上就是对以 send开头用于发送消息的几个方法的分析. 虽然以 post开头的几个方法, 在其内部也是通过调用以 send开头的方法, 但是这里我还想提几点需要注意的或者值得我们关注的:

    1. 以 post开头的方法, 会传递一个 Runnable参数, 如果我们将 Handler对象定义在主线程中, 并且使用 post开头的方法传递一个非 null 的 Runnable对象, 那么实际上该 Runnable对象的 run() 方法将运行在主线程中, 同时 Handler对象的 handleMessage(Message msg) 方法将不会执行, 所以为了避免阻塞 UI, 我们不能在这个 Runnable参数的 run() 方法中做耗时的操作, 但可以做一些更新 UI 的操作. 关于这一点, 想必大家可能会有些疑问, 没关系, 我们会在后边分析 dispatchMessage(Message msg) 方法时进行解释. ———–这里先暂时标记为 疑问2
    2. Activity类中定义了一个 runOnUiThread(Runnable action) 方法, 如果我们在我们自己的某个 Activity类中, 创建了一个子线程, 由于在子线程中是不允许更新 UI的, 所以我们通常做法是在主线程中创建一个 Handler对象, 然后借助这个 Handler对象来实现 UI 的更新, 但是我们其实还有一种简单的做法, 那就是直接在该子线程中调用 runOnUiThread(Runnable action) 方法, 其实该方法也是通过 Handler的方式来完成的, 我们看下其源码:

      /**
       * Runs the specified action on the UI thread. If the current thread is the UI
       * thread, then the action is executed immediately. If the current thread is
       * not the UI thread, the action is posted to the event queue of the UI thread.
       *
       * @param action the action to run on the UI thread
       */
      public final void runOnUiThread(Runnable action) {
          // 如果当前线程不是主线程, 即: 是在子线程中.
          if (Thread.currentThread() != mUiThread) {
              // 其实就是通过调用 Handler.post(Runnable) 方法来实现
              mHandler.post(action);
          } else {
              // 如果当前线程是主线程, 就直接运行该 Runnable的 run()方法.
              action.run();
          }
      }

      先将我们想要在主线程中要执行的一系列动作封装在一个 Runnable对象的 run() 方法中, 然后判断当前线程是不是主线程. 如果当前线程不是主线程, 那么就调用 Handler.post(Runnable) 方法将这个 Runnable对象发到主线程中去执行. 如果当前线程是主线程, 那么就直接运行该 Runnable对象的 run() 方法. 可见, Activity的 runOnUiThread(Runnable) 方法在子线程中也能间接更新 UI 的原理其实就是调用了 Handler.post(Runnable) 这个方法.

    3. 除了上述2介绍的 Activity的 runOnUiThread(Runnable action) 方法外, 我们也可以调用一个 View对象的 post(Runnable action) 方法在子线程中间接更新 UI , 但该方法不是直接调用 Handler.post(Runnable) 方法的, 而是先将这些 Runnable对象暂存在一个基于数组的 RunQueue 队列中, 然后才会调用 Handler.post(Runnable) 方法将暂存在 RunQueue 中的这些 Runnable对象又发送到主线程的 MessageQueue 这个基于链表的队列中去执行, 这个过程有点间接, 该方法的源码如下:

      
      /*----------------------- 下面的源码是 API-14 -------------------------------*/
      
      /**
       * 

      Causes the Runnable to be added to the message queue. * The runnable will be run on the user interface thread.

      * *

      This method can be invoked from outside of the UI thread * only when this View is attached to a window.

      * * @param action 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 boolean post(Runnable action) { Handler handler; AttachInfo attachInfo = mAttachInfo; // 如果 attachInfo 不为 null, 那么调用的就是 `Handler.post(Runnable)` 方法 if (attachInfo != null) { handler = attachInfo.mHandler; } // 反之, 调用的不是 Handler.post(Runnable) 方法, 而是 ViewRootImpl 类的内部类 RunQueue // 中的 post(Runnable) 方法. 这个 RunQueue 的底层是基于数组的, 在工作中发现, 某些场景下可 // 能会出现特殊 OOM 的情况(该观点待验证). else { // Assume that post will succeed later ViewRootImpl.getRunQueue().post(action); return true; } return handler.post(action); } /******************* ViewRootImpl 类, 在 API-8中对应的类是 ViewRoot ****************/ public final class ViewRootImpl extends Handler implements ViewParent, View.AttachInfo.Callbacks, HardwareRenderer.HardwareDrawCallbacks { // .....该类中的其他字段省略...... static final ThreadLocal sRunQueues = new ThreadLocal(); static RunQueue getRunQueue() { // 从 ThreadLocal 中获取. RunQueue rq = sRunQueues.get(); if (rq != null) { return rq; } rq = new RunQueue(); sRunQueues.set(rq); return rq; } /** * ViewRootImpl 的内部类 RunQueue * @hide */ static final class RunQueue { // 这个队列的底层是基于 ArrayList, 也就是数组. private final ArrayList mActions = new ArrayList(); void post(Runnable action) { postDelayed(action, 0); } void postDelayed(Runnable action, long delayMillis) { HandlerAction handlerAction = new HandlerAction(); handlerAction.action = action; handlerAction.delay = delayMillis; synchronized (mActions) { // 数组中添加一个 HandlerAction对象, 也就是一个携带有延迟时间的 Runnable. mActions.add(handlerAction); } } void removeCallbacks(Runnable action) { final HandlerAction handlerAction = new HandlerAction(); handlerAction.action = action; synchronized (mActions) { final ArrayList actions = mActions; while (actions.remove(handlerAction)) { // Keep going } } } /** * 任务的执行, 执行时直接给了一个 Handler对象, 结合 View.post(Runnable)方法的注释以及合理 * 推测可知 , 这个 Handler参数应该是在主线程中定义的 (该推测待进一步论证) */ void executeActions(Handler handler) { synchronized (mActions) { // 定义一个新的引用, 指向 mActions final ArrayList actions = mActions; final int count = actions.size(); for (int i = 0; i < count; i++) { // 又从 ArrayList 中一个一个取出来 final HandlerAction handlerAction = actions.get(i); // 将每一个 Runnable对象(即: handlerAction.action) 封装成 Message对象, 发送到参数 // handler所在的线程(我猜测是主线程, 待验证)所绑定的那个 MessageQueue中, 而在本文后 // 边我们会分析 MessageQueue 的源码, 那时你会知道, MessageQueue 会按照消息的执行 // 时间 (即: handlerAction.delay) 对消息进行排序, 并且 MessageQueue 底层是基于链表而 // 非数组的. handler.postDelayed(handlerAction.action, handlerAction.delay); } actions.clear(); } } private static class HandlerAction { Runnable action; long delay; @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; HandlerAction that = (HandlerAction) o; return !(action != null ? !action.equals(that.action) : that.action != null); } @Override public int hashCode() { int result = action != null ? action.hashCode() : 0; result = 31 * result + (int) (delay ^ (delay >>> 32)); return result; } } } // .....该类中的其他方法省略...... } /*-------------------------------------------------------------------------------------- ----------------------- 下面的源码是 API-8 (Android 2.2) ------------------------------ 由于我只有 API-8 ( Android 2.2 ) 的完整源码, 所以下面的分析是基于 API-8 ( Android 2.2), 而不是前边基于的 API-14 (Android 4.0.0~4.0.2), 前边分析的 ViewRootImpl 类在 API-8 ( Android 2.2 ) 源码中是找不到的, 不过有个作用类似的类, 名字是 ViewRoot. 而 API-8 ( Android 2.2 ) 的 ViewRoot类中的 RunQueue 内部类的源码与 API-14 中 ViewRootImpl.RunQueue是完全相同的, 所以我们就转战到 API-8 的 ViewRoot.RunQueue类, 继续开始分析. 接下来查找 ViewRoot.RunQueue 类的 executeActions(Handler)方法被调用的 地方, 发现只有 ViewRoot 类的 performTraversals() 方法中调用了这个方法. --------------------------------------------------------------------------------------*/ // 注意这是 API-8 (Android 2.2) 中的类 public final class ViewRoot extends Handler implements ViewParent, View.AttachInfo.Callbacks { private void performTraversals() { // ......该方法逻辑太复杂, 不是本文的重点, 因此不做详细分析.... // Execute enqueued actions on every layout in case a view that was detached // enqueued an action after being detached getRunQueue().executeActions(attachInfo.mHandler); // ... } // ......省略其他代码....... }

      上述是 View.post(Runnable) 方法的源码, 注释较详细. 但该方法对 Runnable 对象的处理过程比 Handler 要略微复杂. 该方法不是本文的重点, 所以我只做简要的分析. 第18~20行及31行, 如果 attachInfo 非null, 那么执行的就是 Handler.post(Runnable) 的逻辑. 反之如果 attachInfo 为 null, 那么会进入27行, 得到一个 RunQueue 队列对象, 这是 ViewRootImpl 的一个内部类对象, 也是一个临时存储 Runnable 的队列. 从第 57行开始后边的代码就是这个内部类的代码. 看第59行, 这个内部队列类内部是基于 ArrayList , 也就是数组. 我们调用 View.post(Runnable) 方法发出的 Runnable 对象一定是想要让他们得到执行的, 这些 Runnable 的执行, 是在 RunQueue 类的 executeActions(Handler) 方法中进行的(见第 97行). 而该方法内部其实又是调用了参数 Handler 的 post(Runnable) 方法来处理的. 由于我只有 API-8 (Android 2.2)的完整源码, 所以只能转去分析 API-8 (Android 2.2)的源码, 查找 API-8 (Android 2.2)中, ViewRoot.RunQueue类 executeActions(Handler) 方法的调用情况, 发现只有在 ViewRoot 类的 performTraversals() 方法中, 我们查找的这个方法被调用了, 见第 164 行. performTraversals() 方法会在多种情况下被调用, 例如: 测量一个 View 的时候, 或者一个 View 被重绘时等等. 这里不做详细分析了, 总之, 看第6行, View.post(Runnable) 方法的注释提到 “The runnable will be run on the user interface thread.”可知, 我们用该方法发送的 Runnable 是在主线程中执行的.


  • dispatchMessage(Message msg) 和 handleMessage(Message msg):
    上一段分析绕的有点远, 我们还是回到 Handler 类来继续分析该类的源码. 下面要分析的是 dispatchMessage(Message msg)handleMessage(Message msg) 两个方法.
    dispatchMessage(Message msg) 方法的作用是, 在 Handler对象接收到一个消息后, 将该消息分发给特定的方法进行处理. 通常来说, 我们熟悉的 handleMessage(Message msg) 方法只是这些特定方法中的一个. 我们来看看与 dispatchMessage(Message msg) 方法相关的源码:

    /**
     * Handle system messages here.
     * 该方法的作用是: 对接收到的消息进行分发(仅仅是分发消息, 分发给别人去处理, 该方法自己不处理消息).
     * 
     * 对接收到的消息对象进行分发的优先级顺序从高到低依次为: 
     * 1. 消息对象 msg中 callback字段中的 run()方法,  即: 
     *    msg.callback.run()
     * 2. Handler 中的 mCallback.handleMessage(msg)方法
     * 3. Handler的 handleMessage(msg)方法
     * 并且优先级高的方法对消息的处理可能会屏蔽优先级低的方法对消息的处理.
     */
    public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
            // 优先级1:  其实执行的就是 message.callback.run() 方法
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                // 优先级2.  返回值是 boolean 型, 根据返回值来决定是否要执行优先级3的方法.
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            // 优先级3. 如果优先级2的方法返回 true, 则该方法就不执行. 如果优先级2的方法返回 false, 则该方法就会执行.
            handleMessage(msg);
        }
    }
    
    private final void handleCallback(Message message) {
        // callback是 Runnable对象, 注意这里是直接执行 callback的 run()方法, 而不是将该 Runnable对象赋值给
        // 一个线程对象然后启动该线程.
        message.callback.run();
    }
    
    final Callback mCallback;
    
    /**
     * Callback interface you can use when instantiating a Handler to avoid
     * having to implement your own subclass of Handler.
     */
    public interface Callback {
        // 注意该方法的返回值不是 void, 而是 boolean. 我们可以合理利用该返回值实现某些特殊的需求哦.
        public boolean handleMessage(Message msg);
    }
    
    /**
     * Subclasses must implement this to receive messages.
     * 空方法, 需要我们自己去实现. 如果不实现该方法, 则默认是不处理消息.
     */
    public void handleMessage(Message msg) {
    }

    dispatchMessage(Message msg) 方法的作用是: 该 Handler对象在接收到一条消息后, 将该消息分发给特定的方法进行处理. 而消息分发是有优先级顺序的, 通常按照优先级顺序从高到低依次为:

    1. 消息对象 msg中 callback字段中的 run()方法, 即: msg.callback.run()
    2. Handler 中的 mCallback.handleMessage(msg)方法
    3. Handler的 handleMessage(msg)方法

    处理消息的优先级顺序:
    msg.callback.run() > Handler.mCallback.handleMessage(msg) > Handler.handleMessage(msg).

    并且只要有某个方法已经处理了某个消息对象, 那么比该方法优先级更低的那些方法一般来说都不会再去处理该消息对象了, 也就是说, 优先级高的方法的执行一般情况下都会屏蔽优先级低的方法的执行. 特殊情况是, 如果上述优先级为2的方法返回 false, 就可以让优先级为2和3的两个方法都得到执行. 我们常用的 Handler.handleMessage(msg) 方法处理消息的优先级其实是最低的. 我们可以充分利用这些方法之间的屏蔽作用, 来实现某些特殊的需求. 例如: 对于一般情况的处理逻辑, 我们就将 Handler的 mCallback字段和发送的消息对象的 callback字段都设置为 null, 这样消息的处理就在 Handler.handleMessage(msg) 方法中进行, 而对于特殊的消息, 我们就可以设置这些消息的 callback不为 null, 从而使得对他们的处理是在这些消息对象的 callback字段的run()方法中进行, 并且也屏蔽掉 Handler.handleMessage(msg) 方法的执行. 从而实现对不同的消息进行不同的处理. 当然我们也可以重写 dispatchMessage(Message) 方法来自定义消息分发的逻辑.

    最后单独说明一下优先级1中的 msg.callback, 还记得我们前边提到过, Handler类中有一系列以 post开头的方法吗? 还记得下面这张图吗?

    这里写图片描述

    我们在前边说过, 这几个以 post开头的方法, 最终都是调用以 send开头的方法, 但是有一点需要注意的是, 这几个方法都传入了一个 Runnable参数, Android系统会在这几个方法内部将这个 Runnable参数封装进 Message对象之后, 才会转而去调用以 send开头的那些发送消息的方法. 而这个封装的过程, 其实就是将这个 Runnable参数的值赋给获取到的 Message对象的 callback字段. 所以如果我们调用 Handler的以 post开头的方法并为该方法传入一个非 null的 Runnable参数, 那么该 Handler对象所发送的 Message对象的 callback字段就是我们 post的这个 Runnable对象. 那么在该 Handler对象接收到该 Message对象后对其进行分发处理时, 根据消息分发的优先级顺序及屏蔽作用可知, 由于接收的 Message对象的 callback字段不为 null, 所以, 将直接运行优先级为1的方法, 也就是该 Message对象中携带的我们 post的那个 Runnable对象的 run()方法, 并且同时也不再去执行 Handler自己对消息的那两个处理方法了(包括: 优先级2中的 Handler.mCallback.handleMessage(msg)方法, 和优先级3中我们熟悉的 Handler.handleMessage(msg)方法). 并且优先级1中的 msg.callback.run()方法其实是运行在该 Handler对象被创建的那个线程中的 (因为该方法必定是和 dispatchMessage(msg)方法处于同一线程中). 所以如果我们按照通常的使用方式, 将 Handler对象创建在主线程中用于接收消息后更新 UI, 并且调用以 post开头的方法发送一个携带有 Runnable任务的消息对象, 只要我们 post的 Runnable参数不为 null, 那么在接收到该消息后, 不仅 handleMessage(msg)方法将无法执行, 而且我们 post的那个 Runnable对象还将直接运行在主线程中(因为是直接运行该 Runnable对象的 run()方法, 而不是将该 Runnable对象赋值给一个线程对象然后启动该线程), 所以我们 post出去的 Runnable对象一定不要执行耗时的任务, 否则会阻塞 UI造成界面长时间无响应. 还记得我们前面提出的 疑问2 吗? 想必此时你应该明白了吧.

    这里总结一下 Handler对象(假设用 handler表示)对接收到的消息(假设用 msg表示)进行分发后, 可能出现的几种情况以及各情况下, 有哪些方法分别将会被执行:

    1. 如果 msg内部的 callback字段不为 null, 则只会执行 msg.callback.run() 这一个方法.
    2. 如果 msg内部的 callback字段为 null, 但 handler.mCallback 不为 null, 并且 handler.mCallback 的回调方法 handleMessage(msg) 返回 true, 则只会执行 handler.mCallback.handleMessage(msg) 这一个方法.
    3. 如果 msg内部的 callback字段为 null, 但 handler.mCallback 不为 null, 并且 handler.mCallback 的回调方法 handleMessage(msg) 返回 true, 则会先后执行 handler.mCallback.handleMessage(msg) 和 handler.handleMessage(msg) 这两个方法.
    4. 如果 msg内部的 callback字段和 handler.mCallback 都为 null, 则只会执行 handler.handleMessage(msg) 这一个方法.

    针对以上这几种可能出现的情况, 我也专门写了一个 Demo, 来模拟每一种情况, 并在每一种情况出现时, 分别将那些被执行的方法通过日志的方式打印出来. 有兴趣的朋友, 可以使用我在本文前面提到的那个 Demo工程, 也可以在 这里 下载, 然后运行名称为 “HandlerDispatchMessageDemo”的那个 module. 运行后会打印如下的日志:
    Android 消息机制 - Handler, Looper, Message, MessageQueue 的源码分析_第4张图片
    该日志显示了在上述几种情况下, 各个方法的执行情况, 这与我们前边的结论是完全一致的.


  • removeCallbacksAndMessages(Object token):

    该方法的源码如下:

    /**
     * Remove any pending posts of callbacks and sent messages whose
     * obj is token.  If token is null,
     * all callbacks and messages will be removed.
     */
    public final void removeCallbacksAndMessages(Object token) {
        mQueue.removeCallbacksAndMessages(this, token);
    }

    我们通常使用该方法来移除还在队列中的特定的消息对象以及他们身上携带的 Runnable对象. 如果我们为该方法传递参数为 null, 则表示移除所有还在队列中的消息对象以及他们身上携带的 Runnable对象. 通常我们在一个 Activity / Fragment的 onDestroy() 方法中, 会调用相关 Handler 对象的该方法, 并传入参数 null, 用于在退出时清理掉相关还未执行的消息和任务. 源码中直接调用的是 MessageQueue 的 removeCallbacksAndMessages(Handler h, Object object) 方法, 所以我们此处就不详细分析源码了, 我们会在分析 MessageQueue类时再做详细分析.


3. MessageQueue 源码分析

MessageQueue 翻译作”消息队列”, 其实他是一个基于单向链表的优先级队列, 从队首到队尾按照消息对象要执行的时间 when 从小到大(也就是从最近的时间到最远的时间)进行排列. 新入队的消息对象的入队位置不一定是队尾, 而是他前面的消息对象的执行时间比该消息对象早, 而他后面的消息对象的执行时间又比该消息对象晚的那个位置. 具体可以参阅后文对 enqueueMessage(Message msg, long when) 方法的源码分析.

MessageQueue类中常用的字段有:

// 位于队首的消息对象的引用, 对消息队列的任何一项操作结束后他总是指向当前位于队首的消息对象.
Message mMessages;
// 是否允许消息循环的退出. 如果是主线程, 则不允许退出(即: false), 关于这一点可以参阅 
// Looper.prepareMainLooper()方法的源码. 如果不是主线程, 则默认是允许退出的(即: true).
boolean mQuitAllowed = true;
// 是否正在退出消息循环, 默认是false. 如果不是主线程, 那么可以调用 Looper.quit()方法来退出消息循环, 调用该方法
// 后, 该线程中与 Looper对应的那个消息队列中的该变量就会被置为 true, 表示此时正在退出消息循环, 那么新的消息对象将
// 无法再入队. 如果是主线程, 则不允许退出, 即使调用了 Looper.quit()方法也会被拒绝的, 关于这一点可以
// 参阅 MessageQueue.enqueueMessage(Message msg, long when)方法的源码.
private boolean mQuiting;

MessageQueue类中常用的方法有:

  • enqueueMessage(Message msg, long when):

    表示入队的操作. 参数 when表示该消息何时被分发处理. 该入队方法的源码如下:

    final boolean enqueueMessage(Message msg, long when) {
        if (msg.isInUse()) {
            throw new AndroidRuntimeException(msg
                    + " This message is already in use.");
        }
        // 只有主线程的 mQuitAllowed 才为 false (也就是不允许退出消息循环), 其他线程的该变量默认是 true(也就
        // 是允许退出消息循环). 入队一个 target为 null 的消息对象的含义是: 想要退出当前线程中的消息循环(具体原
        // 因, 可以分析 Looper.quit()方法的源码).  如果是在主线程中入队一个target为 null的消息对象, 由于主线
        // 程不允许退出消息循环, 所以会抛出异常.
        if (msg.target == null && !mQuitAllowed) {
            throw new RuntimeException("Main thread not allowed to quit");
        }
        final boolean needWake;
        synchronized (this) {
            // 如果当前线程中的消息队列正在退出消息循环, 那么此时就不能再接收新消息的入队了, 所以如果此时有新消息
            // 入队, 就会拒绝此次入队行为 (返回 false), 并用 log 的形式提示一个警告.
            if (mQuiting) {
                RuntimeException e = new RuntimeException(
                    msg.target + " sending message to a Handler on a dead thread");
                Log.w("MessageQueue", e.getMessage(), e);
                // 返回 false表示入队失败.
                return false;
            } 
            // 在第6~12行提到过, 入队一个 target为 null 的消息对象的含义是: 想要退出当前线程中的消息循环.
            // 所以设置 mQuiting 为 true, 就表示当前线程中的消息队列正在退出消息循环.
            else if (msg.target == null) {
                mQuiting = true;
            }
            // 将期望该消息 msg 的执行时间 when 设置给 msg内部的 when字段
            msg.when = when;
            //Log.d("MessageQueue", "Enqueing: " + msg);
            // 定义一个临时变量, 指向当前位于队首的消息对象.
            Message p = mMessages;
            // 如果队首没有消息对象了, 即队列为空(p == null), 或者设置让该消息 msg 要立即执行 (when == 0), 
            // 或者 msg的执行时间比当前队首消息对象的执行时间还要早 (when < p.when), 那么就将我们要插入的消息
            // 对象 msg 插入到队首的位置成为新的队首.
            if (p == null || when == 0 || when < p.when) {
                // 将要入队的这个消息对象 msg 插入到原先队首元素前面的位置
                msg.next = p;
                // 将队首指针指向我们新入队的这个消息对象 msg, 使他正式成为新的队首.
                mMessages = msg;
                needWake = mBlocked; // new head, might need to wake up
            } 
            // 如果原先队列不为空 (即: p != null), 并且要插入的消息的执行时间不是立即执行 (即: when != 0, 
            // 通常来说都是 > 0) 而且该执行时间还不早于队首消息的执行时间 (即: when >= p.when) 
            else {
                Message prev = null;
                // 找到队列中某个位置, 使得该位置之前的消息对象的执行时间都早于这个要插入的消息 msg 的执行时间, 
                // 该位置之后的消息对象的执行时间晚于这个要插入的消息 msg 的执行时间.-----(ps: 按照消息
                // 对象的执行时间选择消息对象合适的入队位置, 这就是优先级队列的特征)
                while (p != null && p.when <= when) {
                    prev = p;
                    p = p.next;
                }
                // 将要插入的这个消息对象 msg 插入到前边已经找到的那个位置中.
                msg.next = prev.next;
                prev.next = msg;
                needWake = false; // still waiting on head, no need to wake up
            }
        }
        if (needWake) {
            nativeWake(mPtr);
        }
        // 如果该消息对象 msg 插入成功, 就返回 true, 表示入队成功.
        return true;
    }

    源码中的注释已经很详细了. 我只强调两点:

    1. 如果要结束一个消息循环, 通常来说是将要入队的消息对象的 target字段 (也就是该消息对应的 Handler对象) 置为 null (具体原因请见后文将要介绍的 Looper.quit() 方法的源码), 但是主线程是不允许结束消息循环的, 其他线程可以结束该循环. 如果成功结束了一个消息循环, 那么此后再有消息要入队到该消息队列, 就会被拒绝并用 log 提示一个警告.
    2. 消息队列从队首到队尾是按照消息对象要执行的时间从小到大(也就是从最近的时间到最远的时间) 的顺序进行排列的. 如果要将一个消息入队, 那么消息队列会先根据该消息对象的执行时间, 找出适合他的入队位置(不一定是队尾), 然后将他插入到该位置. 如果该消息的执行时间越早, 那么其入队位置就越靠近队首, 越晚则越远离队首. 如果想让该消息对象立即执行, 就可以设置其执行时间为0, 在实际使用中我们可以通过调用 Handler类中的 sendMessageAtFrontOfQueue(Message msg)postAtFrontOfQueue(Runnable r) 方法来间接设置其执行时间为0. 如果消息队列先前为空, 则该消息对象入队后就直接成为该消息队列的队首. 队列中的每一个消息对象内部都包含有一个指向下一个位置的消息对象的引用, 所以从数据结构的角度来看, 消息队列其实是一个基于单向链表并且按照各消息执行时间从小到大进行排序的优先级队列.

  • next():

    该方法表示消息出队的过程, 将位于队首的那个消息对象出队给 Handler进行处理. 其源码如下:

    final Message next() {
        int pendingIdleHandlerCount = -1; // -1 only during first iteration
        int nextPollTimeoutMillis = 0;
    
        // 这是一个无限循环. 如果队列为空, 或者虽然队列不为空但位于队首的消息对象的执行时间还未到(即: 晚于当前
        // 时间), 那么就一直继续等待并轮询. 如果队首的消息对象的执行时间已到(即:早于当前时间), 就将队首的消息
        // 对象出队并作为返回值返回.
        for (;;) {
            if (nextPollTimeoutMillis != 0) {
                Binder.flushPendingCommands();
            }
            nativePollOnce(mPtr, nextPollTimeoutMillis);
    
            synchronized (this) {
                // Try to retrieve the next message.  Return if found.
                final long now = SystemClock.uptimeMillis();
                // 定义一个临时变量, 指向队首的消息对象
                final Message msg = mMessages;
                // 如果队列不为空
                if (msg != null) {
                    final long when = msg.when;
                    // 如果队首的消息对象的的执行时间已到
                    if (now >= when) {
                        mBlocked = false;
                        // 将原先位于队首的消息对象的下一个消息对象作为新的队首
                        mMessages = msg.next;
                        // 将原队首的消息对象与消息队列之间的链接断开, 使二者互相独立.
                        msg.next = null;
                        if (false) Log.v("MessageQueue", "Returning message: " + msg);
                        // 将即将出队的这个消息对象标记为 "正在使用中"
                        msg.markInUse();
                        // 将该消息对象返回.
                        return msg;
                    } else {
                        nextPollTimeoutMillis = (int) Math.min(when - now, Integer.MAX_VALUE);
                    }
                } else {
                    nextPollTimeoutMillis = -1;
                }
    
                // If first time, then get the number of idlers to run.
                if (pendingIdleHandlerCount < 0) {
                    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 handlers.
            // 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("MessageQueue", "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;
        }
    }

    该方法内使用一个无限循环. 如果队列为空, 或者虽然队列不为空但位于队首的消息对象的执行时间还未到(即: 晚于当前时间), 那么就一直继续等待并轮询直到到达队首的消息对象的执行时间, 才将队首的消息对象出队并结束该方法. 如果队首的消息对象的执行时间已经到了甚至已经超过该时间很久了, 就直接将队首的消息对象出队并结束该方法.


  • removeCallbacksAndMessages(Handler h, Object object):

    介绍该方法之前, 先要简单介绍一个知识点: 在后面分析完 Looper类的源码后, 你会知道, 一个线程内只能有一个 Looper对象, 一个 Looper对象内又只有一个 MessageQueue对象, 所以一个线程内就只能有一个 MessageQueue. 而我们先前所描述的 Handler 将 Message发送到 MessageQueue中, 其实都是发送到该 Handler对象所在线程的那个 MessageQueue中. 由于一个线程内可以创建多个 Handler对象, 但这多个 Handler对象各自发送的 Message对象却都存储在同一个 MessageQueue中, 在分析完 Looper类的 loop() 方法后, 你就会知道, 其实每个 Message对象只能分发给发送他的那个 Handler对象(也就是该 Message对象内存储的 target 对象)来处理, 而不能分发给同一线程内的其他 Handler对象.

    下面我们来分析一下该方法的源码:

    final void removeCallbacksAndMessages(Handler h, Object object) {
        synchronized (this) {
            // 定义一个临时变量 p, 指向当前位于队首的消息对象
            Message p = mMessages;
    
            // 只要队列不为空, 那么就从队首的消息对象开始, 逐一向后判断, 只要消息对象是由给定的 Handler对象 h
            // 发送的, 并且该消息对象所携带的 obj参数也正是给定的参数 object 或者为 null, 那么就将该消息对象回
            // 收到消息池中, 并且队首指针也向后移动一位. 直到满足下列条件之一时, 才会结束该 while循环:
            // 1. 已经遍历完队列中所有的消息对象了
            // 2. 找到了某个消息对象不是由给定的 Handler对象 h所发送 (即: 是由其他 Handler对象发送的)
            // 3. 找到了某个消息对象所携带的字段 obj 与给定的参数 object 不相等并且也不为 null.
            //
            // 所以, 可能只判断到了一部分消息对象时就退出了该while循环, 此时也就只回收了前边的一部分消息对象. 该 
            // while循环退出时, 如果队列不为空, 那么 p 和 n 就一定都指向当前位于队首的消息对象.
            // 另外, 如果该消息队列中的消息对象都是由给定的 Handler对象 h发出的, 并且参数 object传入 null, 那
            // 么在该 while循环退出时, 消息队列将会被清空.
    
            // Remove all messages at front.
            while (p != null && p.target == h
                    && (object == null || p.obj == object)) {
                // 此时的 n总是指向原先位于队首的消息对象的下一个对象
                Message n = p.next;
                // 将队首指针指向原先位于队首的消息对象的下一个对象
                mMessages = n;
                // 将原队首位置上的消息对象回收到消息池中
                p.recycle();
                // 此时的 p总是指向当前位于队首的消息对象
                p = n;
            }
    
            // 下面的这个 while循环的功能是: 从第二个消息对象开始, 从队列中移除所有同时符合如下条件的消息对象
            // 并将它们都回收到消息池中(只要消息池还未满就可以回收):
            // 1. 该消息对象的 target 字段等于给定的参数 h (也就是只回收由 Handler h所发出的消息).
            // 2. 该消息对象的 obj 字段等于给定的参数 object 或为 null.
    
            // Remove all messages after front.
            // 如果在退出前面的 while循环后, 消息队列还不为空, 那么根据前边得出的结论, 此时的 p 和 n 就都指向当
            // 前位于队首的消息对象
            while (p != null) {
                // 将 n 指向队首消息对象的下一个消息对象
                Message n = p.next;
                // 如果队首的消息对象还不是队尾, 也就是他后边还有消息对象
                if (n != null) {
                    // 从队首的下一个消息对象开始, 只要有消息对象同时符合如下2个条件, 就会将该消息对象从队列中
                    // 移除并回收到消息池中.
                    if (n.target == h && (object == null || n.obj == object)) {
                        Message nn = n.next;
                        n.recycle();
                        p.next = nn;
                        continue;
                    }
                }
                // 将队首指针向后移一位, 然后继续重复该 while循环.
                p = n;
            }
        }
    }

    该方法的作用主要是移除消息队列中由某个 Handler对象所发出的符合特定要求的那些消息对象, 并在消息池未满的情况下将这些移除的消息对象进行回收. 如果我们为参数 Object传入 null, 则表示要移除并回收由某个 Handler对象发出的所有消息对象. 该方法因为是 package级别的访问权限, 所以我们 Android开发者无法直接调用该方法, 但我们可以通过调用 Handler的 removeCallbacksAndMessages(Object) 方法来实现同样的目的. 通常来说, 我们会在一个 Activity / Fragment的 onDestroy() 方法中, 会调用在该 Activity / Fragment中定义的所有的 Handler对象的 removeCallbacksAndMessages(Object) 方法, 并为参数 Object都赋值 null, 用于在页面退出时, 移除并回收消息队列中所有还未得到处理的消息对象.


4. Looper 源码分析

在介绍 Looper类之前, 必须先简要介绍下和 Looper类密切相关的一个类——ThreadLocal. 要想透彻地介绍 ThreadLocal 的含义还真不太容易, 不过我们可以将他和 Map 进行类比来帮助我们理解. ThreadLocal 类通常都带有一个泛型类型, 假设该泛型类型为 T, 那么就可以将 ThreadLocal 等效理解为 Map , 对于一个 ThreadLocal 的变量 threadLocal 来说, 他在某个线程内的数值与他在另外一个线程内的数值之间毫无任何关系, 改变该变量在线程A中的数值将不会改变他在线程B中的数值, 如果你不理解这句话, 那么你就可以将这个 threadLocal 变量的类型想象成是 Map , 再来重新理解前边这句话, 想必你应该能理解了吧, 如果依然不理解, 那你就要先学习一下 Map 的知识.

ThreadLocal 类中有两个常用的方法:

// 可以等效理解为 Map 的 get(Object key)方法, 只是需要将参数key换成当前线程对象即可. 
public T get();
// 可以等效理解为 Map 的 put(K key, V value)方法, 只是需要将参数key换成当前线程对象即可.
public void set(T value)

如果我们为 ThreadLocal 类的泛型类型设置为 Looper 类型, 那么这就表示每个线程都绑定着一个独立的 Looper对象, 每个线程各自所绑定的 Looper对象都不同, 改变线程A所绑定的 Looper对象的数值, 不会影响到线程B, C, D…..所绑定的 Looper对象的数值.

好了, 在简单了解了关于 ThreadLocal 类的知识后, 我们就可以进行 Looper类的源码分析了.

Looper类中常用的字段有:

// sThreadLocal.get() will return null unless you've called prepare().
static final ThreadLocal sThreadLocal = new ThreadLocal();
// 当前线程内的消息队列
final MessageQueue mQueue;
// 当前所在的线程对象
final Thread mThread;

Looper 类中有个 ThreadLocal 类型的字段 sThreadLocal, 表示该 Looper对象在不同线程中的值之间没有任何关联性, 默认情况下调用该字段的 get()方法获取到的当前线程下的该 Looper对象的值为 null, 除非调用了 prepare() 方法, 虽然我们还没有分析该方法, 但是我们可以合理猜测, 该方法一定是会调用 sThreadLocal 的 set()方法为该 Looper对象设置它在当前线程内的数值. 至于我们的猜想是否正确呢? 我们稍后分析prepare() 方法的源码时, 再来做验证吧.

下面介绍 Looper类中常用的方法:

  • prepare():

    从该方法的名称就能知道, 该方法的作用主要是用来做一些初始化的准备工作, 其源码如下:

    /** 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() {
       if (sThreadLocal.get() != null) {
           throw new RuntimeException("Only one Looper may be created per thread");
       }
       // 设置 Looper对象在当前线程内的数值.
       sThreadLocal.set(new Looper());
    }

    通过调用该方法, 可以在当前线程内创建一个该线程独有的 Looper对象, 由于该 Looper对象内也会包含一个 MessageQueue对象的引用 mQueue, 而该 Looper对象是当前线程独有的, 所以相当于为当前线程间接创建了一个 MessageQueue对象的引用, 而在该 Looper对象创建出来以后, 我们还可以根据这个 Looper对象创建一个 Handler对象, 创建出来的 Handler对象就可以向这个 mQueue 消息队列中发送消息了. 因为 Handler 要想向消息队列中发送消息, 那么就要求必须要消息队列事先已存在, 而消息队列又是直接采用的 Looper对象内部对某个消息队列的引用, 所以这就要求, 在 Handler发送消息前, 必须要事先存在一个 Looper对象. 所以, Android系统就强制要求, 我们在某个线程内要想创建 Handler对象, 就必须先要为该线程创建一个 Looper对象, 否则就会抛出异常不允许程序运行, 所以这也就解答了前边 疑问1中所提到的那几个问题. 在 疑问1中, 还有一个问题: 为什么我们在开发时没有调用这句 Looper.prepare() 代码, 多数时候都是没问题的, 但偶尔又会遇到这个问题呢? 这个问题可以在稍后分析 prepareMainLooper() 方法的源码时得到答案.


  • myLooper():

    该方法的作用是: 获取该线程内的 Looper对象. 其源码如下:

    /**
     * Return the Looper object associated with the current thread.  Returns
     * null if the calling thread is not associated with a Looper.
     */
    public static Looper myLooper() {
        return sThreadLocal.get();
    }

  • prepareMainLooper():

    该方法和 prepare()方法的作用类似, 但区别是, 该方法只用于为主线程(也就是 UI 线程)做初始化的准备工作, 我们可以通过查阅 ActivityThread类的 main()方法的源码来得到答案:

    // 主线程(UI线程)的入口方法
    public static final void main(String[] args) {
        SamplingProfilerIntegration.start();
    
        Process.setArgV0("");
        // 初始化主线程的 Looper对象, 并设置主线程内的消息队列不允许退出循环.
        Looper.prepareMainLooper();
    
        ActivityThread thread = new ActivityThread();
        thread.attach(false);
        // 开始循环进行消息的取出和分发处理.
        Looper.loop();
    
        if (Process.supportsProcesses()) {
            throw new RuntimeException("Main thread loop unexpectedly exited");
        }
    
        thread.detach();
        String name = (thread.mInitialApplication != null)
            ? thread.mInitialApplication.getPackageName()
            : "";
        Slog.i(TAG, "Main thread of " + name + " is now exiting");
    }

    ActivityThread类的 main()方法是主线程(UI线程)执行的入口方法, 而该方法内确实调用了 Looper.prepareMainLooper(); 来创建主线程内的 Looper对象 (见第7行), 所以, 我们如果在主线程内创建 Handler对象, 就不用事先调用 Looper.prepare(); 方法了, 因为主线程已经在上述 main()方法内帮我们完成了这一动作, 所以这也就解释了前面 疑问1中提到的 “为什么我们在开发时没有调用这句 Looper.prepare() 代码, 多数时候都是没问题的, 但偶尔又会遇到这个问题呢?” 这两个疑问了. 因为我们创建 Handler对象通常都是用来更新 UI, 所以将其创建在主线程中, 主线程已经自动帮我们完成了 Looper对象的创建工作, 所以就无需我们调用 Looper.prepare() 来手动创建 Looper对象了. 而如果我们是在子线程中创建 Handler对象, 由于子线程没有进行 Looper对象的创建, 所以我们就必须先调用 Looper.prepare() 来为该子线程创建 Looper对象, 然后才能创建 Handler对象.

    前面介绍了 prepareMainLooper() 方法的作用是创建主线程内的 Looper对象, 其实这只是该方法的其中一个作用, 他还有另外一个作用, 就是设置主线程内的消息队列不允许退出消息循环. 我们还是来看看他的源码吧:

    /**
     * Initialize the current thread as a looper, marking it as an
     * application's main looper. The main looper for your application
     * is created by the Android environment, so you should never need
     * to call this function yourself.  See also: {@link #prepare()}
     */
    public static void prepareMainLooper() {
        // 也是调用 prepare()方法, 为主线程创建一个 Looper对象.
        prepare();
        // 设置主线程内 Looper对象
        setMainLooper(myLooper());
        // 设置当前线程(也就是主线程)内的消息队列是不允许退出循环的.
        myLooper().mQueue.mQuitAllowed = false;
    }
    
    // 一个静态的 Looper对象, 专用于主线程内消息循环的 Looper对象.
    private static Looper mMainLooper = null;  // guarded by Looper.class
    
    // 设置主线程内 Looper对象
    private synchronized static void setMainLooper(Looper looper) {
        mMainLooper = looper;
    }

    源码很简单, 注释也很详细, 就不过多介绍了.


  • loop():

    该方法是 Looper类中最核心的方法, 他的作用让当前线程内的 Looper对象从该线程内的消息队列中不断地取出消息对象, 然后给到创建该消息对象的那个 Handler对象进行分发处理. 我们看看该方法的源码:

    /**
     * Run the message queue in this thread. Be sure to call
     * {@link #quit()} to end the loop.
     */
    public static void loop() {
        // 获取当前线程内的 Looper对象.
        Looper me = myLooper();
        if (me == null) {
            // 如果事先没有调用 Looper.prepare()为当前线程创建 Looper对象, 那么就会抛出此异常.
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }
        // 获取当前线程内的 MessageQueue对象.
        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();
    
        // 无限循环. 该 Looper对象会不断轮询检查是否有消息满足出队条件(比如: 当前时间是否已经到了队首的消息对象的执行时间), 
        // 如果有消息满足出队条件, 就将队首的消息对象出队, 然后由该 Looper对象将这个出队的消息对象分配给发送该消息对象的
        // 那个 Handler对象, 让该 Handler对象来对该消息对象进行进一步的分发处理. 如果没有满足出队条件的消息对象或者消息队列
        // 为空, 那么程序都将一直处于阻塞状态, 直到消息队列不为空并且有消息对象满足了出队条件为止.
        while (true) {
            // 如果消息队列不为空, 并且当前时间已经达到了位于队首的消息对象的执行时间, 那么就从消息队列中取出该消息对象.
            // 否则, 就将一直处于阻塞状态直到满足该条件为止.
            Message msg = queue.next(); // might block
            // 如果取出的消息对象不为 null
            if (msg != null) {
                // 如果找不到发送这个消息对象的 Handler对象, 那就表明这是我们发出的一个特殊信号, 表示我们要申请退出
                // 该消息循环, 所以就直接结束掉该方法, 也就结束了这个消息循环. 详情请参阅 Looper类中 quit()方法的源码.
                if (msg.target == null) {
                    // No target is a magic identifier for the quit message.
                    return;
                }
    
                long wallStart = 0;
                long threadStart = 0;
    
                // This must be in a local variable, in case a UI event sets the logger
                Printer logging = me.mLogging;
                if (logging != null) {
                    logging.println(">>>>> Dispatching to " + msg.target + " " +
                            msg.callback + ": " + msg.what);
                    wallStart = SystemClock.currentTimeMicro();
                    threadStart = SystemClock.currentThreadTimeMicro();
                }
    
                // 如果能够找到发送这个消息对象的 Handler对象, 那么就将该消息传递给该 Handler对象, 然后由该 Handler对象
                // 来对该消息进行分发处理. 我们前边提到过, Handler对象对消息的分发会先后判断三个优先级从高到低的方法, 只要
                // 有高优先级的方法满足条件, 就会由该方法来处理该消息对象, 那么其他低优先级的方法就不再对该消息进行处理了.
                msg.target.dispatchMessage(msg);
    
                if (logging != null) {
                    long wallTime = SystemClock.currentTimeMicro() - wallStart;
                    long threadTime = SystemClock.currentThreadTimeMicro() - threadStart;
    
                    logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
                    if (logging instanceof Profiler) {
                        ((Profiler) logging).profile(msg, wallStart, wallTime,
                                threadStart, threadTime);
                    }
                }
    
                // 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(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.recycle();
            }
            // 此后又会回到第27行, 再次执行这个循环.
        }
    }

    该方法的源码相对较复杂, 但是由于我们先前已经对该方法中所涉及到的其他几个类的相关方法都做过详细的分析, 所以只要我们对先前分析过的这些方法的作用都非常熟悉的话, 我们再来分析这个 loop() 方法的话, 那简直就是小菜一碟啊. 注释已经非常详细了, 我就不过多分析了.

    不过还是需要提一个小知识点, 那就是该方法源码中第 32~35行的代码. 我们在第30~31行的注释中也写到, 如果发现出队的消息对象的 target字段为 null, 那么这就是一个代表着”我们要申请当前线程退出该消息循环”的特殊信号, 因此在第34行就直接退出了该方法, 也就退出了该消息循环. 而我们也知道, 要用 Handler发送一个消息对象, 可以使用 Handler类中以 send或 post开头的那几个方法, 这些方法最终都会调用 sendMessageAtTime(Message msg, long uptimeMillis) 方法, 我们仔细观察该方法源码 (点击这里可查看其源码) 第24行的代码:

    msg.target = this;

    这表明, 凡是使用 Handler类中以 send或 post开头的那几个方法所发出的消息, 他们的 target字段都不为 null, 那么就不可能遇到退出消息循环的情况了. 但是我们怎样才能发送一个 target 为 null 的消息对象, 使得上述 loop() 方法第32行 msg.target == null 的条件得到满足, 从而退出消息循环呢? 其实, 通过 Handler发送消息的做法显然是不可能让消息循环退出的, 我们只能另辟蹊径, 那我们到底该怎么做呢? 其实这也就是我接下来要介绍的 quit() 方法的作用了.


  • quit():

    根据见名知意的命名原则, 显然该方法的作用就是, 退出一个消息循环. 我们来看看其源码:

    public void quit() {
        // 通过 Message.obtain()方法获取的 Message对象的 target字段为 null.
        Message msg = Message.obtain();
    
        // NOTE: By enqueueing directly into the message queue, the
        // message is left with a null target.  This is how we know it is
        // a quit message.
    
        // 入队一个 target为 null的消息对象, 就表示要退出该消息循环. 这其实就是一个"退出消息循环"的特殊信号. 而
        // 且还将第二个参数 when设置为0(也就是该消息对象的执行时间设置为0), 这就将该消息对象插入到消息队列中队首
        // 的位置, 表示要立即退出该消息循环, 消息队列中当前存储的那些消息对象全部不再处理.
        mQueue.enqueueMessage(msg, 0);
    }

    看到上述源码后, 我们应该知道如何入队一个 target为 null的消息对象, 从而退出消息循环了吧. 直接调用 quit() 方法即可. 当然, 需要注意的是, 主线程的消息循环是不允许退出的, 我们只能退出子线程的消息循环. 这又是为什么呢? 还记得我们在分析 Looper类的 prepareMainLooper()方法的源码时, 该源码第13行是如下代码:

    myLooper().mQueue.mQuitAllowed = false;

    也就是设置主线程的消息队列是不允许退出的, mQuitAllowed = false. 所以如果我们为主线程的消息队列入队一个 target为 null的消息对象时, 就一定会被主线程的消息队列拒绝入队的. 我们再来回顾一下前面介绍的 MessageQueue类的 enqueueMessage(Message msg, long when) 方法的源码, 我们看第10~12行的代码, 如下所示:

    if (msg.target == null && !mQuitAllowed) {
            throw new RuntimeException("Main thread not allowed to quit");
    }

    如果入列一个 target为 null的消息对象, 并且 mQuitAllowed 为 false(也就是不允许退出消息循环), 那么就直接抛出异常, 拒绝该消息对象入队. 这样, 主线程就能让消息循环一直进行下去而不会退出. 因为消息循环的运行对于 Android系统的正常运转是必不可少的, 所以 Android系统禁止主线程的消息循环被人为设置退出, 而只能由系统自己来决定其何时退出. 而如果我们是在子线程内使用了消息循环, 那么当我们不再使用该子线程时, 我们一定要记得调用我们在该线程内所创建的 Looper对象的 quit() 方法来退出消息循环, 这样可以让不再使用的那些消息对象得到回收, 避免资源浪费.


三. Android 消息系统的工作原理

在分析了上述几个类的源码以后, 我们就可以从宏观上来描述一下 Android消息系统的工作原理了.

首先, 不论是主线程, 还是子线程, 都需要调用 Looper.prepare() 方法 (主线程是自动调用 Looper.prepareMainLooper() 方法) 来为该线程创建一个 Looper对象, 随着这个 Looper对象的诞生, 该线程内的消息队列也随之一起诞生, 然后我们在该线程内创建一个 Handler对象, 紧接着再调用先前创建的 Looper对象的 loop() 方法让该 Looper对象运转起来, 这时 Looper已经准备就绪, 但由于消息队列此时为空, 所以 Looper暂时处于阻塞状态. 此后, 一旦需要进行线程间通信, 例如线程A需要传递数据给线程B, 那么就会让 Handler对象在线程A 中发送一条 (可能携带有一个Runnable参数的) 消息到消息队列中, 这时消息队列就不再为空了, Looper也就从阻塞状态恢复到运转状态, 开始从消息队列中不断取出消息, 然后交给 Handler去处理, Handler在线程B 中接收到消息后, 就会进行相应的处理. 只要消息队列不为空, Looper就会不断地从消息队列中取出消息, 再传递给 Handler去处理, 这样一直循环下去. 这就是 Android消息系统的工作原理.


四. 内存泄露问题的原因及解决措施

我们在使用 Android的消息系统时, 如果使用不当, 将很容易造成严重的内存泄露问题. 具体情况是: 如果我们在 Activity / Fragment中使用了某些匿名非静态内部类(例如: Handler, Runnable的匿名非静态内部类), 那么这些匿名非静态内部类就会隐式地持有外部的 Activity / Fragment的强引用, 如果我们要发送的消息或 Runnable对象不会立即执行, 而此时我们又退出了该 Activity / Fragment, 就会造成这些 Activity / Fragment因为仍有其他对象持有着他们的强引用, 而导致这些 Activity / Fragment 不会被回收, 这样这些 Activity / Fragment 内所持有的其他对象的引用也不会被回收, 这众多的对象都泄露到内存中, 会造成可用内存越来越少, 这就是内存泄露. 而造成该内存泄露的根源是, 我们没有合理地使用 Android的消息系统. 要想解决该内存泄露的问题, 可以根据如下几个措施来修正你的代码:

  • 将 Handler / Runnable 的子类直接定义在另外一个文件中或者定义成 Activity / Fragment的静态(static)内部类
  • 如果需要在该 Handler / Runnable 的子类内部调用外部的 Activity / Fragment 内的字段或方法, 那么就需要让该 Handler / Runnable 的子类持有对外部 Activity / Fragment 的一个弱引用 (WeakReference).

这样就能避免 Activity / Fragment 的内存泄露了. 而这样之所以能避免 Activity / Fragment 的内存泄露的原因是:

  • 定义在另外一个文件中, 或者定义成 Activity / Fragment 的静态(static)内部类, 这都是为了让 Handler / Runnable 的子类不再持有外部 Activity / Fragment 的引用.
  • 由于要调用外部 Activity / Fragment 内的字段或方法, 就必须要持有对外部 Activity / Fragment 的一个引用, 而引用通常分为四种: 强引用, 软引用(SoftReference), 弱引用(WeakReference), 虚引用(PhantomReference). 这四种引用各自的含义如下:

    1. 强引用: 不可回收, 即使内存空间已经严重不足也不回收, 直到发生 OutOfMemoryError(OOM)使系统挂掉为止.
    2. 软引用: 只要内存充足, 就一直不回收. 只有在内存不足时, 才会去回收. 也就是能晚点儿回收就尽量晚点儿, 只有内存不足而迫不得已必须得回收时才会去回收. 可以用来做缓存. JDK源码对其解释是:

      A SoftReference should be cleared and enqueued as late as possible , that is, in case the VM is in danger of running out of memory.

    3. 弱引用: 一旦发现该引用是弱引用, 那么就有可能被立即回收, 即使你还没使用完这个引用, 即使当前内存空间十分充足. JDK源码对其解释是:

      A WeakReference may be cleared and enqueued as soon as is known to be weakly-referenced.

    4. 虚引用: 通常是在一个对象被 GC之前用来做一些资源清理的工作. 该引用对于我们开发者来说一般不常用.

    基于以上四种引用各自的含义和适用场景, 我们就可以知道, 对外部的 Activity / Fragment 使用弱引用是最合适的.

关于在使用 Android的消息系统时如何避免内存泄漏, 我也写了个 Demo代码, 该 Demo也是包含在本文前边提到的那个 Demo工程中, 当然也可以在这里下载该工程的代码, 然后运行名称为 “MemoryLeakInMessageSystemDemo”的那个 module即可. 在该 module中, 还引入了 Square公司用于协助检测内存泄露的库 LeakCanary, 有兴趣的朋友可以下载并学习一下这个库的使用方法.


五. 本文总结

由于本文内容较多, 涉及到的知识点也较分散, 为便于彻底理清思路, 同时也为了让我们在今后的项目开发中, 能够借鉴 Android系统的一些优秀设计思想和设计方案, 有必要对本文进行一番简单的总结, 总结如下:

  1. Message

    • 该类内部有个 target字段, 表示发送该消息的那个 Handler对象.
    • 该类内部有一个可以回收并重复利用消息的消息池, 容量是10. 该消息池的底层数据结构是一个基于单向链表的栈.
    • 创建消息对象时,尽量使用 Message.obtain() 方法或 Handler.obtainMessage() 方法, 而不要直接 new Message().
    • 一个消息对象不再使用时, 需要调用其 recycle() 方法将其回收到消息池中(只要消息池未满, 就能回收), 而不是放任不管.
  2. Handler

    • 本文所描述的 Handler 是 android.os.Handler, 不是 java.util.logging.Handler. 如果 IDE 提示 Handler 类的某个方法不存在 (例如: obtainMessage()), 你需要去检查一下你导入的包是否正确, 这是经常容易发生的手误.
    • 如果 Handler对象创建在子线程中, 则必须在调用 Looper.prepare() 方法后才能创建 Handler对象, 然后还要调用 Looper.loop() 方法才能保证消息系统能够循环起来.——–当然, Android系统还设计了一个类, 能够简化在子线程中创建 Handler对象的操作步骤, 还能避免我们犯一些人为错误, 那就是 HandlerThread类. 有兴趣的朋友可以自己研究一下该类的源码, 只要你熟练掌握了本文介绍的内容, 那么分析这个类将会非常简单, 甚至只看一分钟就明白了.
    • Handler向消息队列中发送消息, 可以使用以 send或 post开头的那些方法, 其中, sendMessageAtFrontOfQueue(Message)postAtFrontOfQueue(Runnable) 较为特殊, 这两个方法可以将消息插入到消息队列的队首. 另外, 如果使用以 post开头的方法发送一个携带有 Runnable参数的消息, 只要该 Runnable不为null, 那么此后 Handler如果接收到该消息, 那么将会在该 Handler对象被创建时所在的那个线程中直接运行该 Runnable对象的 run()方法, 而并没有新建一个线程, 而且 Handler的 handleMessage(Message) 也不再执行.
    • Handler类有个 dispatchMessage(Message) 方法, 用于在接收到一条消息后对该消息进行分发, 分发给相应的处理者, 由他们自己去处理该消息. 而消息的分发过程会涉及到3个优先级不同的方法:

      优先级1: msg.callback.run()
      优先级2: Handler.mCallback.handleMessage(Message)
      优先级3: Handler.handleMessage(Message)

      对于每一个方法来说, 要想成功处理分发给他的消息, 都需要满足一定的前提条件, 具体如下:

      优先级 方法 成功处理消息需满足的前提条件
      1 msg.callback.run() msg.callback 不为 null
      2 Handler.mCallback.handleMessage(Message) 该方法返回 true
      3 Handler.handleMessage(Message) 该方法被重写了

      当 Handler对象接收到一条消息后, 他需要按照上述列表中的优先级顺序, 先将该消息分发给优先级最高的方法, 如果该方法满足上述表格中提到的前提条件, 那就直接由该方法处理掉这条消息, 消息分发就到此结束了, 不再继续向下分发了. 如果该方法不满足表格中的前提条件, 那么该方法就不能处理这条消息, 消息就会继续分发给优先级更低的方法, 然后再来判断这个方法能否处理消息, 判断思路还是相同的, 满足条件就处理, 否则就继续向下分发, 直到找到某个方法能够成功处理该消息为止. 为便于记忆, 这个优先级顺序简单来说, 就是如果一个消息能够自己处理自己的事情, 那么就不需要委托他人(也就是 Handler) 来处理, 否则就必须并且也只能委托他人(也就是 Handler) 来处理了.

    • Handler类中有个 removeCallbacksAndMessages(Object) 方法, 如果为参数赋值为 null, 则表示清理掉消息队列中所有未被处理的消息和 Runnable回调, 这些消息对象也将会被回收到消息池中. 通常在一个 Activity 或 Fragment内, 如果创建了 Handler对象, 那么就需要在该 Activity 或 Fragment的 onDestroy() 方法内调用该方法并未参数传入 null值, 用于做一些资源清理工作.
    • 在 Activity 或 Fragment 中使用 Handler, 一定要避免内存泄露. 可以将 Handler 或 Runnable的子类定义成 Activity 或 Fragment 的静态(static)内部类, 并在他们调用外部 Activity 或 Fragment 的字段或方法时, 让他们持有对外部 Activity 或 Fragment 的弱引用 (WeakReference).
  3. MessageQueue

    • 主线程中的消息队列是不允许退出的. 只有子线程的消息队列是可以退出的.
    • 该消息队列的底层数据结构是一个基于单向链表的优先级队列, 可以根据消息对象的执行时间(也就是消息对象的 when字段的数值)从小到大排列.
    • 该类内部有个字段 mMessages 表示位于队首的那个消息对象的引用.
    • 该类的enqueueMessage(Message, long) 方法表示消息入队的过程. 主线程的消息队列将会检查入队的消息对象的 target字段是否为 null, 如果为 null, 表示外部有申请要退出消息循环, 这是主线程所不允许的, 所以主线程会直接拒绝该消息入队并抛出异常. 只有设置为允许退出循环的子线程, 才允许一个 target字段为 null的消息对象入队, 并且直接将该消息对象放置在队首的位置, 在下一次出队操作时, 直接取出该消息对象, 并退出(即: 结束)该消息循环. 退出消息循环后, 就不能再向该消息队列中发送消息对象了, 即使发送也会被拒绝入队的.
    • 该类的 next() 方法表示消息出队的过程. 如果发现出队的消息对象中的 target字段为 null, 就会退出该消息循环. 退出消息循环后, 就不能再向该消息队列中发送消息对象了, 即使发送也会被拒绝入队的. 如果消息队列为空, 或者队首的那个消息对象的执行时间还未到, 那么就会进入到阻塞状态, 直到该队列不为空, 并且队首消息对象的执行时间已到, 才会将队首的消息对象出队.
  4. Looper

    • 一个线程内只允许有一个 Looper对象, 而这是基于 ThreadLocal 的原理来实现的. 我们可以将 ThreadLocal 近似理解为 Map. 由于我们前边描述的消息队列其实使用的都是该线程内那个唯一的 Looper对象内部的一个消息队列字段 mQueue, 所以, 一个线程内只能有一个消息队列, 但是却可以有多个 Handler对象, 同一个线程内的多个 Handler对象各自所发送的消息都存储在这一个消息队列中, 消息出队后只能由发送他的那个 Handler对象来进行分发处理.
    • Looper类中的prepare()prepareMainLooper() 方法的作用分别是: 为当前(子)线程或主线程创建该线程内那个唯一的 Looper对象. 只有完成了该线程内 Looper对象的创建, 才能在该线程内创建 Handler对象, 否则会报异常.
    • Looper类中的 loop() 方法是用于开启并不断执行消息循环. 如果消息队列无法将一个消息对象出队(通常是由于消息队列为空, 或者消息队列中位于队首的那个消息对象的执行时间还未到), 那么该方法就会进入阻塞状态, 直到有消息对象成功出队, 才会恢复到运行状态. 如果出队的消息对象的 target字段为 null, 就表示开发者需要让该消息循环结束掉, 所以就直接退出该方法并结束(也叫: 退出)该消息循环. 结束消息循环后, 就不能再向该消息队列中发送消息对象了, 即使发送也会被拒绝入队的.
    • Looper类中的 quit() 方法, 表示退出该 Looper对象所在线程中的消息循环. 消息循环退出后, 消息队列中未被处理的消息对象将会被回收到消息池中. 当然主线程是不允许退出该循环的, 所以即使我们使用 Looper.getMainLooper().quit() 方法试图退出主线程的消息循环, 也是会被拒绝的. 但是我们可以退出在子线程中的消息循环. 如果在一个子线程内创建了 Handler对象, 并使用了消息系统, 那么在我们确认该线程不再使用时, 就需要调用该线程内 Looper对象的 quit() 方法, 以退出该线程内的消息循环, 让消息对象得到回收, 避免了系统资源的浪费.
    • 最后提示一个小技巧: 我们可以借助 Looper类来判断当前线程是否是主线程, 判断办法如下:

      if (Looper.getMainLooper().getThread() == Thread.currentThread()) {
         // Current thread is the UI/Main thread
      }
      if (Looper.myLooper() == Looper.getMainLooper()) {
        // Current thread is the UI/Main thread
      }  

六. 参考资料

  • Android SDK API-8, API-14 源码
  • JDK 1.8 源码
  • 《Java编程思想》 (机械工业出版社 第4版) 17.12 持有引用
  • Understanding Android/Java Processes and Threads Related Concepts (Handlers, Runnables, Loopers, MessageQueue,
    HandlerThread) (非常好的文章, 强烈推荐)
  • 《Android开发艺术探索》 第10章 Android的消息机制
  • Android异步消息处理机制完全解析,带你从源码的角度彻底理解
  • How to Check/Detect Whether Current Thread is UI/Main Thread or Not in Android
  • 来自 Google工程师 Romain Guy 发表的官方 blog —- Avoiding memory leaks

你可能感兴趣的:(Android,android,源码)