Handler原理分析

作者: 却把清梅嗅 | 来源:发表于2019-04-03 00:36 被阅读30次

    Handler的原理分析这个标题,很多文章都写过,最近认真将源码逐行一字一句研究,特此也简单总结一遍。

    首先是Handler整个Android消息机制的简单概括:

    分三部分对消息机制的整个流程进行阐述:

    • Handler的创建,包括LooperMessageQueue的创建;
    • Handler发送消息,Message是如何进入消息队列MessageQueue的(入列);
    • Looper轮询消息,Message出列,Handler处理消息。

    一、Handler创建流程分析

    1.Handler如何被创建的

    // 最简单的创建方式
    public Handler() {
        this(null, false);
    }
    
    // ....还有很多种方式,但这些方式最终都执行这个构造方法
    public Handler(Callback callback, boolean async) {
      // 1.检查内存泄漏
      if (FIND_POTENTIAL_LEAKS) {
          final Class<? extends Handler> 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());
          }
      }
    
      // 2.通过Looper.myLooper()获取当前线程的Looper对象
      mLooper = Looper.myLooper();
      // 3.如果Looper为空,抛出异常
      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;
    }
    

    首先,如何避免Handler的内存泄漏是一个非常常见的面试题,其实Handler的源码中已经将答案非常清晰告知给了开发者,即让Handler的导出类保证为static的,如果需要,将Context作为弱引用的依赖注入进来。

    同时,在Handler创建的同时,会尝试获取当前线程唯一的Looper对象:

    public final class Looper {
    
      static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
    
      public static @Nullable Looper myLooper() {
          return sThreadLocal.get();
      }
    }
    

    关于ThreadLocal,我在上一篇文章中已经进行了分析,现在我们知道了ThreadLocal保证了当前线程内有且仅有唯一的一个Looper

    2.Looper是如何保证线程单例的

    那就是需要调用Looper.prepare()方法:

    public final class Looper {
    
       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));
       }
    
       private Looper(boolean quitAllowed) {
            mQueue = new MessageQueue(quitAllowed);
            mThread = Thread.currentThread();
        }
     }
    

    这也就说明了,为什么当前线程没有Looper的实例时,会抛出一个异常并提示开发者需要调用Looper.prepare()方法了。

    也正如上述代码片段所描述的,如果当前线程已经有了Looper的实例,也会抛出一个异常,提示用户每个线程只能有一个Looperthrow new RuntimeException("Only one Looper may be created per thread");)。

    此外,在Looper实例化的同时,也创建了对应的MessageQueue,这也就说明,一个线程有且仅有一个Looper,也仅有一个MessageQueue

    二、发送消息流程分析

    1.sendMessage()分析

    sendMessage()流程如下:

    // 1.发送即时消息
    public final boolean sendMessage(Message msg)
    {
        return sendMessageDelayed(msg, 0);
    }
    
    // 2.实际上是发射一个延时为0的Message
    public final boolean sendMessageDelayed(Message msg, long delayMillis)
    {
       if (delayMillis < 0) {
           delayMillis = 0;
       }
       return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
    }
    
    // 3.将消息和延时的时间进行入列(消息队列)
    public boolean sendMessageAtTime(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);
    }
    
    // 4.内部实际上还是执行了MessageQueue的enqueueMessage()方法
    private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        msg.target = this;
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }
    

    注意第四步实际上将Handler对象最为target,附着在了Message之上;接下来看MessageQueue类内部是如何对Message进行入列的。

    2.MessageQueue消息入列

    boolean enqueueMessage(Message msg, long when) {
        //... 省略部分代码
        synchronized (this) {
            msg.markInUse();
            msg.when = when;
            // 获得链表头的Message
            Message p = mMessages;
            boolean needWake;
            if (p == null || when == 0 || when < p.when) {
                // 若有以下情景之一,将Message置于链表头
                // 1.头部Message为空,链表为空
                // 2.消息为即时Message
                // 3.头部Message的时间戳大于最新Message的时间戳
                msg.next = p;
                mMessages = msg;
                needWake = mBlocked;
            } else {
                // 反之,将Message插入到链表对应的位置
                Message prev;
                // for循环就是找到合适的位置,并将最新的Message插入链表
                for (;;) {
                    prev = p;
                    p = p.next;
                    if (p == null || when < p.when) {
                        break;
                    }
                }
                msg.next = p; // invariant: p == prev.next
                prev.next = msg;
            }
            if (needWake) {
                nativeWake(mPtr);
            }
        }
        return true;
    }
    

    MessageQueue的数据结构本身是一个单向链表

    三、接收消息分析

    Handler创建好后,若在此之前调用了Looper.prepare()初始化Looper,还需要调用Looper.loop()开始该线程内的消息轮询。

    1.Looper.loop()

    public static void loop() {
        // ...省略部分代码
        // 1. 获取Looper对象
        final Looper me = myLooper();
        if (me == null) {
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }
        // 2.获取messageQueue
        final MessageQueue queue = me.mQueue;
        // 3. 轮询消息,这里是一个死循环
        for (;;) {
            // 4.从消息队列中取出消息,若消息队列为空,则阻塞线程
            Message msg = queue.next();
            if (msg == null) {
                return;
            }
    
            // 5.派发消息到对应的Handler
            msg.target.dispatchMessage(msg);
            // ...
        }
    }
    

    比较简单,需要注意的一点是MessageQueue.next()是一个可能会阻塞线程的方法,当有消息时会轮询处理消息,但如果消息队列中没有消息,则会阻塞线程。

    2.MessageQueue.next()

    private native void nativePollOnce(long ptr, int timeoutMillis);
    
    
    Message next() {
    
        // ...省略部分代码
        int nextPollTimeoutMillis = 0;
    
        for (;;) {
          // ...
    
        // native方法
        nativePollOnce(ptr, nextPollTimeoutMillis);
    
        synchronized (this) {
    
            final long now = SystemClock.uptimeMillis();
            Message prevMsg = null;
            Message msg = mMessages;
    
            // 从消息队列中取出消息
            if (msg != null) {
                // 当时间小于message的时间戳时,获取时间差
                if (now < msg.when) {
                    // 该值将会导致在下次循环中阻塞对应时间
                    nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                } else {
                    // 取出消息并返回
                    mBlocked = false;
                    if (prevMsg != null) {
                        prevMsg.next = msg.next;
                    } else {
                        mMessages = msg.next;
                    }
                    msg.next = null;
                    msg.markInUse();
                    return msg;
                }
            }
            // ...
       }
    }
    

    注意代码片段最上方的native方法——循环体内首先调用nativePollOnce(ptr, nextPollTimeoutMillis),这是一个native方法,实际作用就是通过Native层的MessageQueue阻塞nextPollTimeoutMillis毫秒的时间:

    • 1.如果nextPollTimeoutMillis=-1,一直阻塞不会超时。
    • 2.如果nextPollTimeoutMillis=0,不会阻塞,立即返回。
    • 3.如果nextPollTimeoutMillis>0,最长阻塞nextPollTimeoutMillis毫秒(超时),如果期间有程序唤醒会立即返回。

    搞清楚这一点,其它就都好理解了。

    3.最终将消息发送给Handler

    正如上文所说的,msg.target.dispatchMessage(msg)实际上就是调用Handler.dispatchMessage(msg),内部最终也是执行了Handler.handleMessage()回调:

    public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            // 如果消息没有定义callBack,或者不是通过
            // Handler(Callback)的方式实例化Handler,
            // 最终会走到这里
            handleMessage(msg);
        }
    }
    

    参考&感谢


    关于我

    Hello,我是却把清梅嗅,如果您觉得文章对您有价值,欢迎 ❤️,也欢迎关注我的博客或者Github

    如果您觉得文章还差了那么点东西,也请通过关注督促我写出更好的文章——万一哪天我进步了呢?

    相关文章

      网友评论

        本文标题:Handler原理分析

        本文链接:https://www.haomeiwen.com/subject/rvuobqtx.html