Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Handler原理分析 #21

Open
qingmei2 opened this issue Jul 30, 2019 · 0 comments
Open

Handler原理分析 #21

qingmei2 opened this issue Jul 30, 2019 · 0 comments

Comments

@qingmei2
Copy link
Owner

Handler原理分析

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

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant