多应用+插件架构,代码干净,二开方便,首家独创一键云编译技术,文档视频完善,免费商用码云13.8K 广告
[TOC] ## MessageQueue MessageQueue中最重要的就是两个方法: 1.enqueueMessage向队列中插入消息 2.next 从队列中取出消息 MessageQueue的底层数据结构是单向链表,MessageQueue中的成员变量mMessages指向的就是该链表的头部元素。 ### enqueueMessage 先分析enqueueMessage: ~~~ boolean enqueueMessage(Message msg, long when) { if (msg.target == null) {//msg.target就是发送此消息的Handler throw new IllegalArgumentException("Message must have a target."); } if (msg.isInUse()) {//表示此消息正在被使用 throw new IllegalStateException(msg + " This message is already in use."); } synchronized (this) { if (mQuitting) {//表示此消息队列已经被放弃了 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;//将延迟时间封装到msg内部 Message p = mMessages;//消息队列的第一个元素 boolean needWake; if (p == null || when == 0 || when < p.when) { //如果此队列中头部元素是null(空的队列,一般是第一次),或者此消息不是延时的消息,则此消息需要被立即处理,此时会将这个消息作为新的头部元素,并将此消息的next指向旧的头部元素,然后判断如果Looper获取消息的线程如果是阻塞状态则唤醒它,让它立刻去拿消息处理 msg.next = p; mMessages = msg; needWake = mBlocked; } else { //如果此消息是延时的消息,则将其添加到队列中,原理就是链表的添加新元素,按照when,也就是延迟的时间来插入的,延迟的时间越长,越靠后,这样就得到一条有序的延时消息链表,取出消息的时候,延迟时间越小的,就被先获取了。插入延时消息不需要唤醒Looper线程。 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 mQuitting is false. if (needWake) {//唤醒线程 nativeWake(mPtr); } } return true; } ~~~ 源码中主要的地方我给了注释,可以参考参考。 由此可以看出: MessageQueue中enqueueMessage方法的目的有两个: 1.插入消息到消息队列 2.唤醒Looper中等待的线程(如果是及时消息并且线程是阻塞状态) 同时我们知道了MessageQueue的底层数据结构是单向链表,MessageQueue中的成员变量mMessages指向的就是该链表的头部元素 ### next ~~~java Message next() { final long ptr = mPtr; if (ptr == 0) { //从注释可以看出,只有looper被放弃的时候(调用了quit方法)才返回null,mPtr是MessageQueue的一个long型成员变量,关联的是一个在C++层的MessageQueue,阻塞操作就是通过底层的这个MessageQueue来操作的;当队列被放弃的时候其变为0。 return null; } int pendingIdleHandlerCount = -1; // -1 only during first iteration int nextPollTimeoutMillis = 0; for (;;) { if (nextPollTimeoutMillis != 0) { Binder.flushPendingCommands(); } //阻塞方法,主要是通过native层的epoll监听文件描述符的写入事件来实现的。 //如果nextPollTimeoutMillis=-1,一直阻塞不会超时。 //如果nextPollTimeoutMillis=0,不会阻塞,立即返回。 //如果nextPollTimeoutMillis>0,最长阻塞nextPollTimeoutMillis毫秒(超时),如果期间有程序唤醒会立即返回。 nativePollOnce(ptr, nextPollTimeoutMillis); synchronized (this) { final long now = SystemClock.uptimeMillis(); Message prevMsg = null; Message msg = mMessages; if (msg != null && msg.target == null) { //msg.target == null表示此消息为消息屏障(通过postSyncBarrier方法发送来的) //如果发现了一个消息屏障,会循环找出第一个异步消息(如果有异步消息的话),所有同步消息都将忽略(平常发送的一般都是同步消息) do { prevMsg = msg; msg = msg.next; } while (msg != null && !msg.isAsynchronous()); } if (msg != null) { if (now < msg.when) { // 如果消息此刻还没有到时间,设置一下阻塞时间nextPollTimeoutMillis,进入下次循环的时候会调用nativePollOnce(ptr, nextPollTimeoutMillis)进行阻塞; nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE); } else { //正常取出消息 //设置mBlocked = false代表目前没有阻塞 mBlocked = false; if (prevMsg != null) { prevMsg.next = msg.next; } else { mMessages = msg.next; } msg.next = null; msg.markInUse(); return msg; } } else { //没有消息,会一直阻塞,直到被唤醒 nextPollTimeoutMillis = -1; } if (mQuitting) { dispose(); return null; } pendingIdleHandlerCount = 0; nextPollTimeoutMillis = 0; } } ~~~ 由此可以看出: 1.当首次进入或所有消息队列已经处理完成,由于此刻队列中没有消息(mMessages为null),这时nextPollTimeoutMillis = -1 ,然后会处理一些不紧急的任务(IdleHandler),之后线程会一直阻塞,直到被主动唤醒(插入消息后根据消息类型决定是否需要唤醒)。 2.读取列表中的消息,如果发现消息屏障,则跳过后面的同步消息。 3.如果拿到的消息还没有到时间,则重新赋值nextPollTimeoutMillis = 延时的时间,线程会阻塞,直到时间到后自动唤醒 4.如果消息是及时消息或延时消息的时间到了,则会返回此消息给looper处理。 通过enqueueMessage和next两个方法的分析我们不难得出: 消息的入列和出列是一个生产-消费者模式,Looper.loop()在一个线程中调用next()不断的取出消息,另外一个线程则通过enqueueMessage向队列中插入消息,所以在这两个方法中使用了synchronized (this) {}同步机制,其中this为MessageQueue对象,不管在哪个线程,这个对象都是同一个,因为Handler中的mQueue指向的是Looper中的mQueue,这样防止了多个线程对同一个队列的同时操作。 ### Handler.sendMessageDelayed()怎么实现延迟的? 前面我们分析了如果拿到的消息还没有到时间,则会重新设置超时时间并赋值给nextPollTimeoutMillis,然后调用nativePollOnce(ptr, nextPollTimeoutMillis)进行阻塞,这是一个本地方法,会调用底层C++代码,C++代码最终会通过Linux的epoll监听文件描述符的写入事件来实现延迟的。 ### 为什么这个死循环不会造成ANR异常呢? 我们知道Android 的是由事件驱动的,looper.loop() 不断地接收事件、处理事件,每一个点击触摸或者说Activity的生命周期都是运行在 Looper的控制之下,如果它停止了,应用也就停止了。只能是某一个消息或者说对消息的处理阻塞了 Looper.loop(),而不是 Looper.loop() 阻塞它,这也就是我们为什么不能在UI线程中处理耗时操作的原因。 主线程Looper从消息队列读取消息,当读完所有消息时,主线程阻塞。子线程往消息队列发送消息,唤醒主线程,主线程被唤醒只是为了读取消息,当消息读取完毕,再次睡眠。因此loop的循环并不会对CPU性能有过多的消耗。 ## 参考资料 [从源码角度分析java层Handler机制](https://blog.csdn.net/andywuchuanlong/article/details/48160127) [从源码角度分析native层消息机制与java层消息机制的关联](https://blog.csdn.net/andywuchuanlong/article/details/48179165) [深入理解MessageQueue](https://www.jianshu.com/p/8c829dc15950)