Android消息机制(1)MessageQueue
文章目录
- 介绍
- enqueueMessage 重要源码
- next 重要源码
- Linux的pipe/epoll机制
介绍
基于Android 9.0
MessageQueue,消息队列,在消息机制的作用是维护一个Message的队列,供Looper使用。MessageQueuede 通过一个单链表(即mMessages)来实现队列,由于Message本身就有一个指向下一个Message的Message变量next,所以MessageQueue不需要再借助其他结构来实现队列,只需要一个mMessages变量指向链表的头节点就可以了。MessageQueue中的关键方法是enqueueMessage和next,前者用于增加Message,后者用于读取Message。
enqueueMessage 重要源码
boolean enqueueMessage(Message msg, long when) {if (msg.target == null) { throw new IllegalArgumentException("Message must have a target."); } if (msg.isInUse()) { throw new IllegalStateException(msg + " This message is already in use."); } synchronized (this) { ......... msg.markInUse(); msg.when = when; Message p = mMessages; boolean needWake; if (p == null || when == 0 || when < p.when) { msg.next = p;(1) mMessages = msg; needWake = mBlocked; } else { needWake = mBlocked && p.target == null && msg.isAsynchronous(); Message prev; for (;;) {(2) 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; }......... return true; }
可以看到,enqueueMessage的作用是将一个新的Message插入到链表中正确的位置,这个链表是有序的,属性when(应处理时间)越小,位置越前,所以 (1): 如果新的Message的时间点比根节点前,就成为新的根节点,否则 (2): 插入链表中的正确位置。而在插入之前,会检查Message是不是设置了target或者是否已经使用过了,所以Handle接收到的Message不能直接再次发送出去。
next 重要源码
Message next() { ...... int pendingIdleHandlerCount = -1; // -1 only during first iteration int nextPollTimeoutMillis = 0; for (;;) { ....... nativePollOnce(ptr, nextPollTimeoutMillis);(1) synchronized (this) { final long now = SystemClock.uptimeMillis(); Message prevMsg = null; Message msg = mMessages; if (msg != null && msg.target == null) { // Stalled by a barrier. Find the next asynchronous message in the queue. do { prevMsg = msg; msg = msg.next; } while (msg != null && !msg.isAsynchronous()); } if (msg != null) { if (now < msg.when) { // Next message is not ready. Set a timeout to wake up when it is ready. nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);(2) } else {(3) // Got a message. 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 { // No more messages. nextPollTimeoutMillis = -1; } // Process the quit message now that all pending messages have been handled. if (mQuitting) {(4) dispose(); return null; }....... } ........ // 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; } }
next中的大部分代码都在一个for循环中,以此来寻找链表中的可以处理的Message,(1)、(2) 位置的代码用于在当前没有Message需要处理时休眠(机制下节再说),所以next方法是堵塞的。在 (3) 处,获得当前时间可以处理的Message后返回;在 (4) 处,如果这个线程结束了,就返回null表示这个队列停止使用。
Linux的pipe/epoll机制
参考知乎问答:Android中为什么主线程不会因为Looper.loop()里的死循环卡死?
在主线程的MessageQueue没有消息时,便阻塞在loop的queue.next()中的nativePollOnce()方法里,此时主线程会释放CPU资源进入休眠状态,直到下个消息到达或者有事务发生,通过往pipe管道写端写入数据来唤醒主线程工作。这里采用的epoll机制,是一种IO多路复用机制,可以同时监控多个描述符,当某个描述符就绪(读或写就绪),则立刻通知相应程序进行读或写操作,本质同步I/O,即读写是阻塞的。 所以说,主线程大多数时候都是处于休眠状态,并不会消耗大量CPU资源。
更多相关文章
- Handler消息传送机制
- 解决-Android: ListView.setSelection无效
- Android(安卓)Handler的基本使用
- [Android开发] 代码code设置9.png/9-patch 图片背景后,此view中的
- 【Android】快速切换到主线程更新UI的几种方法
- Android(安卓)Java 线程池 ThreadPoolExecutor源码篇
- Android(安卓)内存管理机制
- android review--基础知识
- 细读《深入理解 Android(安卓)内核设计思想》(一)进程间通信与同步