Android消息机制(1)MessageQueue

介绍

基于Android 9.0

MessageQueue,消息队列,在消息机制的作用是维护一个Message的队列,供Looper使用。MessageQueuede 通过一个单链表(即mMessages)来实现队列,由于Message本身就有一个指向下一个Message的Message变量next,所以MessageQueue不需要再借助其他结构来实现队列,只需要一个mMessages变量指向链表的头节点就可以了。MessageQueue中的关键方法是enqueueMessagenext,前者用于增加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资源。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值