Handler源码学习(三)MessageQueue入队插队
Handler源码学习(一)流程
Handler源码学习(二)Message对象池
Handler源码学习(三)MessageQueue入队插队
1.消息入队
消息队列与Message对象池的结构很像,也是通过对象之间通过next指向形成链表结构
这时候加入一个msg消息,先来看如果消息队列为空的情况
msg.markInUse(); msg.when = when; Message p = mMessages; boolean needWake; if (p == null || when == 0 || when < p.when) { msg.next = p; mMessages = msg; needWake = mBlocked; }
|
假设现在队列中已经有了两个消息,这两个消息的排练是按照执行时间,如果时间相同则是按照入队先后排列。来一步步分析,可以先看后面的图,把思路理清,再看源码
else { 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; prev.next = msg; }
|
2.取出消息
这里贴的不是完整代码,而是取出message的核心逻辑代码,这里其实分了两个部分,第一个部分是消息插队,这个在第三节叙述,第二个部分是正常的消息,其实取出消息的部分比较简单,注释也比较清晰
synchronized (this) { final long now = SystemClock.uptimeMillis(); Message prevMsg = null; Message msg = mMessages; if (msg != null) { 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; if (DEBUG) Log.v(TAG, "Returning message: " + msg); msg.markInUse(); return msg; } } else { nextPollTimeoutMillis = -1; }
|
3.消息插队
首先需要知道的是整个android系统其实主要是依赖消息机制来处理事件,比如说点击事件等等,都是通过handler发送消息进行处理,但是这些系统消息相较于程序自己发送的消息,应该要优先执行,所以就涉及到了消息插队
- isAsynchronous() — 如何将系统消息和程序发送的消息区分开来
在阅读Hanlder的源码的时候,可以看到一个@hide的构造方法,传入一个布尔值,从字面意思理解是异步
public Handler(boolean async) { this(null, async); } mAsynchronous = async; private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) { msg.target = this; if (mAsynchronous) { msg.setAsynchronous(true); } return queue.enqueueMessage(msg, uptimeMillis); }
|
为什么要标记为异步呢?是要新开一个线程来执行么?可是最后也是发送到looper所绑定的消息队列中啊,貌似这里并没有异步,接着看上一节提到的消息插队的源码,在这里看到调用了一个方法msg.isAsynchronous(),貌似这个布尔值并不是跟异步有关系,而是将这个消息做了一个标记而已
前面的消息入队分析可以知道,虽然被标记了系统消息,但是还是按照消息队列的规则去入队,那么如何做到优先取出这个任务呢,一步步看
Message prevMsg = null; Message msg = mMessages; if (msg != null && msg.target == null) { do { prevMsg = msg; msg = msg.next; } while (msg != null && !msg.isAsynchronous()); }
|
上面的分析已经插队成功,但是还有疑点,到底是怎么进入到if代码块中的
// Stalled by a barrier. Find the next asynchronous message in the queue.
作者在这里给了一条注释,通过一个阻塞块来停止正常的队列,找到队列中的第一个系统消息
在MessageQueue中试图搜索barrier,发现一个方法,
public int postSyncBarrier() { return postSyncBarrier(SystemClock.uptimeMillis()); } private int postSyncBarrier(long when) { synchronized (this) { final int token = mNextBarrierToken++; final Message msg = Message.obtain(); msg.markInUse(); msg.when = when; msg.arg1 = token; Message prev = null; Message p = mMessages; if (when != 0) { while (p != null && p.when <= when) { prev = p; p = p.next; } } if (prev != null) { msg.next = p; prev.next = msg; } else { msg.next = p; mMessages = msg; } return token; } }
|
加入队列的代码已经很熟悉了,可以看到在这段代码中其实入队了一个没有target的消息,而最上面那个public方法中可以看到传入的when是当前的系统时间,也就是说如果调用这个方法会在消息队列的头部插入一个没有target的message,到这里思路就比较清晰了,但是这个消息肯定不能一直在队列中,否则整个队列的正常消息就永远无法处理,所以相对应还有一个remove。注释中说明,这个方法应该跟插入的方法匹配使用。
public void removeSyncBarrier(int token) { synchronized (this) { Message prev = null; Message p = mMessages; while (p != null && (p.target != null || p.arg1 != token)) { prev = p; p = p.next; } if (p == null) { throw new IllegalStateException("The specified message queue synchronization " + " barrier token has not been posted or has already been removed."); } final boolean needWake; if (prev != null) { prev.next = p.next; needWake = false; } else { mMessages = p.next; needWake = mMessages == null || mMessages.target != null; } p.recycleUnchecked(); if (needWake && !mQuitting) { nativeWake(mPtr); } } }
|
当发送系统消息时 会在消息队列中插入一个target为空的message,在取出消息时如果发现了这个消息,就跳过所有的正常消息,返回最近的一个系统消息,然后将这个标记消息从队列中remove。
整个Handler源码学习系列笔记就完结了,当然现在也没有到特别深入的程度,但了解了整套Handler的消息机制后,相信对于源码的阅读能力和编程思路上也有挺大的提高。