MessageQueue没有消息时候会怎样?阻塞之后怎么唤醒呢?说说pipe/epoll机制?
接着上文的逻辑,当消息不可用或者没有消息的时候就会阻塞在next方法,而阻塞的办法是通过pipe/epoll机制
epoll机制
是一种IO多路复用的机制,具体逻辑就是一个进程可以监视多个描述符,当某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作,这个读写操作是阻塞的。在Android中,会创建一个Linux管道(Pipe)
来处理阻塞和唤醒。
- 当消息队列为空,管道的读端等待管道中有新内容可读,就会通过
epoll
机制进入阻塞状态。 - 当有消息要处理,就会通过管道的写端写入内容,唤醒主线程。
那什么时候会怎么唤醒消息队列线程呢?
还记得刚才插入消息的enqueueMessage
方法中有个needWake
字段吗,很明显,这个就是表示是否唤醒的字段。
其中还有个字段是mBlocked
,看字面意思是阻塞的意思,去代码里面找找:
Message next() { for (;;) { synchronized (this) { if (msg != null) { if (now < msg.when) { nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE); } else { // Got a message. mBlocked = false; return msg; } } if (pendingIdleHandlerCount <= 0) { // No idle handlers to run. Loop and wait some more. mBlocked = true; continue; } } } }
在获取消息的方法next
中,有两个地方对mBlocked
赋值:
- 当获取到消息的时候,
mBlocked
赋值为false
,表示不阻塞。 - 当没有消息要处理,也没有
idleHandler
要处理的时候,mBlocked
赋值为true
,表示阻塞。
好了,确实这个字段就表示是否阻塞的意思,再去看看enqueueMessage
方法中,唤醒机制:
boolean enqueueMessage(Message msg, long when) { synchronized (this) { 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; } if (needWake) { nativeWake(mPtr); } } return true; }
- 当链表为空或者时间小于表头消息时间,那么就插入表头,并且设置是否唤醒为
mBlocked
。
再结合上述的例子,也就是当有新消息要插入表头了,这时候如果之前是阻塞状态(mBlocked=true),那么就要唤醒线程了。
- 否则,就需要取链表中找到某个节点并插入消息,在这之前需要赋值
needWake = mBlocked && p.target == null && msg.isAsynchronous()
也就是在插入消息之前,需要判断是否阻塞,并且表头是不是屏障消息,并且当前消息是不是异步消息。也就是如果现在是同步屏障模式下,那么要插入的消息又刚好是异步消息,那就不用管插入消息问题了,直接唤醒线程,因为异步消息需要先执行。
- 最后一点,是在循环里,如果发现之前就存在异步消息,那就还是设置是否唤醒为
false
。
意思就是,如果之前有异步消息了,那肯定之前就唤醒过了,这时候就不需要再次唤醒了。
最后根据needWake
的值,决定是否调用nativeWake
方法唤醒next()
方法。
同步屏障和异步消息是怎么实现的?
其实在Handler
机制中,有三种消息类型:
同步消息
。也就是普通的消息。异步消息
。通过setAsynchronous(true)设置的消息。同步屏障消息
。通过postSyncBarrier方法添加的消息,特点是target为空,也就是没有对应的handler。
这三者之间的关系如何呢?
- 正常情况下,同步消息和异步消息都是正常被处理,也就是根据时间when来取消息,处理消息。
- 当遇到同步屏障消息的时候,就开始从消息队列里面去找异步消息,找到了再根据时间决定阻塞还是返回消息。
也就是说同步屏障消息不会被返回,他只是一个标志,一个工具,遇到它就代表要去先行处理异步消息了。
所以同步屏障和异步消息的存在的意义就在于有些消息需要“加急处理”
。
同步屏障和异步消息有具体的使用场景吗?
使用场景就很多了,比如绘制方法scheduleTraversals
。
void scheduleTraversals() { if (!mTraversalScheduled) { mTraversalScheduled = true; // 同步屏障,阻塞所有的同步消息 mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier(); // 通过 Choreographer 发送绘制任务 mChoreographer.postCallback( Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null); } } Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action); msg.arg1 = callbackType; msg.setAsynchronous(true); mHandler.sendMessageAtTime(msg, dueTime);
在该方法中加入了同步屏障,后续加入一个异步消息MSG_DO_SCHEDULE_CALLBACK
,最后会执行到FrameDisplayEventReceiver
,用于申请VSYNC信号。
更多Choreographer
相关内容可以看看这篇文章——https://www.jianshu.com/p/86d00bbdaf60
Message消息被分发之后会怎么处理?消息怎么复用的?
再看看loop方法,在消息被分发之后,也就是执行了dispatchMessage
方法之后,还偷偷做了一个操作——recycleUnchecked
。
public static void loop() { for (;;) { Message msg = queue.next(); // might block try { msg.target.dispatchMessage(msg); } msg.recycleUnchecked(); } } //Message.java private static Message sPool; private static final int MAX_POOL_SIZE = 50; void recycleUnchecked() { flags = FLAG_IN_USE; what = 0; arg1 = 0; arg2 = 0; obj = null; replyTo = null; sendingUid = UID_NONE; workSourceUid = UID_NONE; when = 0; target = null; callback = null; data = null; synchronized (sPoolSync) { if (sPoolSize < MAX_POOL_SIZE) { next = sPool; sPool = this; sPoolSize++; } } }
在recycleUnchecked
方法中,释放了所有资源,然后将当前的空消息插入到sPool表头。
这里的sPool
就是一个消息对象池,它也是一个链表结构的消息,最大长度为50。
那么Message又是怎么复用的呢?在Message的实例化方法obtain
中:
public static Message obtain() { synchronized (sPoolSync) { if (sPool != null) { Message m = sPool; sPool = m.next; m.next = null; m.flags = 0; // clear in-use flag sPoolSize--; return m; } } return new Message(); }
直接复用消息池sPool
中的第一条消息,然后sPool指向下一个节点,消息池数量减一。
Looper是干嘛呢?怎么获取当前线程的Looper?为什么不直接用Map存储线程和对象呢?
在Handler发送消息之后,消息就被存储到MessageQueue
中,而Looper
就是一个管理消息队列的角色。Looper会从MessageQueue
中不断的查找消息,也就是loop方法,并将消息交回给Handler进行处理。
而Looper的获取就是通过ThreadLocal
机制:
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>(); 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)); } public static @Nullable Looper myLooper() { return sThreadLocal.get(); }
通过prepare
方法创建Looper并且加入到sThreadLocal中,通过myLooper
方法从sThreadLocal中获取Looper。
ThreadLocal运行机制?这种机制设计的好处?
下面就具体说说ThreadLocal
运行机制。
//ThreadLocal.java public T get() { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) { ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) { @SuppressWarnings("unchecked") T result = (T)e.value; return result; } } return setInitialValue(); } public void set(T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); }
从ThreadLocal
类中的get和set方法可以大致看出来,有一个ThreadLocalMap
变量,这个变量存储着键值对形式的数据。
key
为this,也就是当前ThreadLocal变量。value
为T,也就是要存储的值。
然后继续看看ThreadLocalMap
哪来的,也就是getMap方法:
//ThreadLocal.java ThreadLocalMap getMap(Thread t) { return t.threadLocals; } //Thread.java ThreadLocal.ThreadLocalMap threadLocals = null;
原来这个ThreadLocalMap
变量是存储在线程类Thread中的。
所以ThreadLocal
的基本机制就搞清楚了:
在每个线程中都有一个threadLocals变量,这个变量存储着ThreadLocal和对应的需要保存的对象。
这样带来的好处就是,在不同的线程,访问同一个ThreadLocal对象,但是能获取到的值却不一样。
挺神奇的是不是,其实就是其内部获取到的Map不同,Map和Thread绑定,所以虽然访问的是同一个ThreadLocal
对象,但是访问的Map却不是同一个,所以取得值也不一样。
这样做有什么好处呢?为什么不直接用Map存储线程和对象呢?
打个比方:
ThreadLocal
就是老师。Thread
就是同学。Looper
(需要的值)就是铅笔。
现在老师买了一批铅笔,然后想把这些铅笔发给同学们,怎么发呢?两种办法:
- 1、老师把每个铅笔上写好每个同学的名字,放到一个大盒子里面去(map),用的时候就让同学们自己来找。
这种做法就是Map里面存储的是同学和铅笔
,然后用的时候通过同学来从这个Map里找铅笔。
这种做法就有点像使用一个Map,存储所有的线程和对象,不好的地方就在于会很混乱,每个线程之间有了联系,也容易造成内存泄漏。
- 2、老师把每个铅笔直接发给每个同学,放到同学的口袋里(map),用的时候每个同学从口袋里面拿出铅笔就可以了。
这种做法就是Map里面存储的是老师和铅笔
,然后用的时候老师说一声,同学只需要从口袋里拿出来就行了。
很明显这种做法更科学,这也就是ThreadLocal
的做法,因为铅笔本身就是同学自己在用,所以一开始就把铅笔交给同学自己保管是最好的,每个同学之间进行隔离。
还有哪些地方运用到了ThreadLocal机制?
比如:Choreographer。
public final class Choreographer { // Thread local storage for the choreographer. private static final ThreadLocal<Choreographer> sThreadInstance = new ThreadLocal<Choreographer>() { @Override protected Choreographer initialValue() { Looper looper = Looper.myLooper(); if (looper == null) { throw new IllegalStateException("The current thread must have a looper!"); } Choreographer choreographer = new Choreographer(looper, VSYNC_SOURCE_APP); if (looper == Looper.getMainLooper()) { mMainInstance = choreographer; } return choreographer; } }; private static volatile Choreographer mMainInstance;
Choreographer
主要是主线程用的,用于配合 VSYNC
中断信号。
所以这里使用ThreadLocal
更多的意义在于完成线程单例的功能。
可以多次创建Looper吗?
Looper的创建是通过Looper.prepare
方法实现的,而在prepare方法中就判断了,当前线程是否存在Looper对象,如果有,就会直接抛出异常:
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中的quitAllowed字段是啥?有什么用?
按照字面意思就是是否允许退出,我们看看他都在哪些地方用到了:
void quit(boolean safe) { if (!mQuitAllowed) { throw new IllegalStateException("Main thread not allowed to quit."); } synchronized (this) { if (mQuitting) { return; } mQuitting = true; if (safe) { removeAllFutureMessagesLocked(); } else { removeAllMessagesLocked(); } } }
哦,就是这个quit
方法用到了,如果这个字段为false
,代表不允许退出,就会报错。
但是这个quit
方法又是干嘛的呢?从来没用过呢。还有这个safe
又是啥呢?
其实看名字就差不多能了解了,quit方法就是退出消息队列,终止消息循环。
- 首先设置了
mQuitting
字段为true。 - 然后判断是否安全退出,如果安全退出,就执行
removeAllFutureMessagesLocked
方法,它内部的逻辑是清空所有的延迟消息,之前没处理的非延迟消息还是需要取处理,然后设置非延迟消息的下一个节点为空(p.next=null)。 - 如果不是安全退出,就执行
removeAllMessagesLocked
方法,直接清空所有的消息,然后设置消息队列指向空(mMessages = null)
然后看看当调用quit方法之后,消息的发送和处理:
//消息发送 boolean enqueueMessage(Message msg, long when) { 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; } }
当调用了quit方法之后,mQuitting
为true,消息就发不出去了,会报错。
再看看消息的处理,loop和next方法:
Message next() { for (;;) { synchronized (this) { if (mQuitting) { dispose(); return null; } } } } public static void loop() { for (;;) { Message msg = queue.next(); if (msg == null) { // No message indicates that the message queue is quitting. return; } } }
很明显,当mQuitting
为true的时候,next方法返回null,那么loop方法中就会退出死循环。
那么这个quit
方法一般是什么时候使用呢?
- 主线程中,一般情况下肯定不能退出,因为退出后主线程就停止了。所以是当APP需要退出的时候,就会调用quit方法,涉及到的消息是EXIT_APPLICATION,大家可以搜索下。
- 子线程中,如果消息都处理完了,就需要调用quit方法停止消息循环。
Looper.loop方法是死循环,为什么不会卡死(ANR)?
关于这个问题,强烈建议看看Gityuan的回答:https://www.zhihu.com/question/34652589
我大致总结下:
- 1、主线程本身就是需要一只运行的,因为要处理各个View,界面变化。所以需要这个死循环来保证主线程一直执行下去,不会被退出。
- 2、真正会卡死的操作是在某个消息处理的时候操作时间过长,导致掉帧、ANR,而不是loop方法本身。
- 3、在主线程以外,会有其他的线程来处理接受其他进程的事件,比如
Binder线程(ApplicationThread)
,会接受AMS发送来的事件 - 4、在收到跨进程消息后,会交给主线程的
Hanlder
再进行消息分发。所以Activity的生命周期都是依靠主线程的Looper.loop
,当收到不同Message时则采用相应措施,比如收到msg=H.LAUNCH_ACTIVITY
,则调用ActivityThread.handleLaunchActivity()
方法,最终执行到onCreate方法。 - 5、当没有消息的时候,会阻塞在loop的
queue.next()
中的nativePollOnce()
方法里,此时主线程会释放CPU资源进入休眠状态,直到下个消息到达或者有事务发生。所以死循环也不会特别消耗CPU资源。
Message是怎么找到它所属的Handler然后进行分发的?
在loop方法中,找到要处理的Message
,然后调用了这么一句代码处理消息:
msg.target.dispatchMessage(msg);
所以是将消息交给了msg.target
来处理,那么这个target是啥呢?
找找它的来头:
//Handler private boolean enqueueMessage(MessageQueue queue,Message msg,long uptimeMillis) { msg.target = this; return queue.enqueueMessage(msg, uptimeMillis); }
在使用Hanlder发送消息的时候,会设置msg.target = this
,所以target就是当初把消息加到消息队列的那个Handler。
Handler 的 post(Runnable) 与 sendMessage 有什么区别
Hanlder中主要的发送消息可以分为两种:
- post(Runnable)
- sendMessage
public final boolean post(@NonNull Runnable r) { return sendMessageDelayed(getPostMessage(r), 0); } private static Message getPostMessage(Runnable r) { Message m = Message.obtain(); m.callback = r; return m; }
通过post的源码可知,其实post和sendMessage
的区别就在于:
post方法给Message设置了一个callback
。
那么这个callback有什么用呢?我们再转到消息处理的方法dispatchMessage
中看看:
public void dispatchMessage(@NonNull Message msg) { if (msg.callback != null) { handleCallback(msg); } else { if (mCallback != null) { if (mCallback.handleMessage(msg)) { return; } } handleMessage(msg); } } private static void handleCallback(Message message) { message.callback.run(); }
这段代码可以分为三部分看:
- 1、如果
msg.callback
不为空,也就是通过post方法发送消息的时候,会把消息交给这个msg.callback进行处理,然后就没有后续了。 - 2、如果
msg.callback
为空,也就是通过sendMessage发送消息的时候,会判断Handler当前的mCallback是否为空,如果不为空就交给Handler.Callback.handleMessage处理。 - 3、如果
mCallback.handleMessage
返回true,则无后续了。 - 4、如果
mCallback.handleMessage
返回false,则调用handler类重写的handleMessage方法。
所以post(Runnable) 与 sendMessage的区别就在于后续消息的处理方式,是交给msg.callback
还是 Handler.Callback
或者Handler.handleMessage
。
Handler.Callback.handleMessage 和 Handler.handleMessage 有什么不一样?为什么这么设计?
接着上面的代码说,这两个处理方法的区别在于Handler.Callback.handleMessage
方法是否返回true:
- 如果为
true
,则不再执行Handler.handleMessage - 如果为
false
,则两个方法都要执行。
那么什么时候有Callback
,什么时候没有呢?这涉及到两种Hanlder的 创建方式:
val handler1= object : Handler(){ override fun handleMessage(msg: Message) { super.handleMessage(msg) } } val handler2 = Handler(object : Handler.Callback { override fun handleMessage(msg: Message): Boolean { return true } })
常用的方法就是第1种,派生一个Handler的子类并重写handleMessage方法。而第2种就是系统给我们提供了一种不需要派生子类的使用方法,只需要传入一个Callback即可。
Handler、Looper、MessageQueue、线程是一一对应关系吗?
- 一个线程只会有一个
Looper
对象,所以线程和Looper是一一对应的。 MessageQueue
对象是在new Looper的时候创建的,所以Looper和MessageQueue是一一对应的。Handler
的作用只是将消息加到MessageQueue中,并后续取出消息后,根据消息的target字段分发给当初的那个handler,所以Handler对于Looper是可以多对一的,也就是多个Hanlder对象都可以用同一个线程、同一个Looper、同一个MessageQueue。
总结:Looper、MessageQueue、线程是一一对应关系,而他们与Handler是可以一对多的。