前言
最近闲的时间比较多,浏览一些公众号和一些IT网站,突然看到一个问题:
Handler中loop方法为什么不会导致线程卡死?
我先浏览了一下源码,以下为截取部分片段:
/**Looper*/
public static void loop() {
...
for (;;) {
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
...
msg.recycleUnchecked();
}
}
/**MessageQuene*/
Message next() {
...
int pendingIdleHandlerCount = -1; // -1 only during first iteration
int nextPollTimeoutMillis = 0;
for (;;) {
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
...
}
// 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;
}
}
仔细一看,这确实是两个死循环,难道死循环不会阻塞了吗,科技都这么进步了吗?,果断尝试:
/**Activity*/
runOnUiThread {
while (true) {
Log.d("lzp", "while")
}
}
还是卡死了,页面一篇惨白,那为啥Handler中的loop不会卡死线程?
正文
为了解决这个问题,我开始在网上搜索相关的资料,但是都不能完全解决我的疑问,果然解决问题还得靠自己。
创建一台模拟器,选定Android系统版本,打上断点,问题就明朗了。
小建议:如果想要看源码,建议使用模拟器,这样断点能对得上源码,国产真机不推荐,具体原因大家都懂。
问题一:Handler中的loop真的是死循环吗?
了解过源码的朋友都知道,Handler与Looper、Messagequene的关系,这里就简要说一下:
Handler:消息处理器;
Looper:消息管理器;
MessageQuene:消息队列;
我们要理解的是Looper中的looper方法,仔细分析looper源码,其实它只完成几步工作:
public static void loop() {
// 从队列去除消息
final MessageQueue queue = me.mQueue;
// for死循环
for (;;) {
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
// 消息分发
msg.target.dispatchMessage(msg);
// 消息回收
msg.recycleUnchecked();
}
}
很尴尬,从上面的代码,我们得出的结论只有loop方法确实是个死循环。但是我们也找到了非常有价值的线索:
// 请注意注释:可能会锁住
Message msg = queue.next(); // might block
赶紧去看一眼MessageQuene.next方法:
Message next() {
int pendingIdleHandlerCount = -1; // -1 only during first iteration
// 下一次循环的时间
int nextPollTimeoutMillis = 0;
for (;;) {
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
// 此native方法很可疑
nativePollOnce(ptr, nextPollTimeoutMillis);
// 同步锁
synchronized (this) {
// Try to retrieve the next message. Return if found.
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
// 找到要取出的消息
if (msg != null && msg.target == null) {
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
// 消息不为空,直接返回msg
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);
} else {
// 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;
}
// 判断是否有空闲时执行的任务
if (pendingIdleHandlerCount < 0
&& (mMessages == null || now < mMessages.when)) {
pendingIdleHandlerCount = mIdleHandlers.size();
}
// 如果没有,直接锁住
if (pendingIdleHandlerCount <= 0) {
// No idle handlers to run. Loop and wait some more.
// 记录是否要锁住
mBlocked = true;
continue;
}
if (mPendingIdleHandlers == null) {
mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
}
mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
}
// 开始执行Hanlder空闲时的任务
for (int i = 0; i < pendingIdleHandlerCount; i++) {
final IdleHandler idler = mPendingIdleHandlers[i];
mPendingIdleHandlers[i] = null; // release the reference to the handler
// 如果这个任务不需要维持下去,会删除这个任务
boolean keep = false;
try {
keep = idler.queueIdle();
} catch (Throwable t) {
Log.wtf(TAG, "IdleHandler threw exception", t);
}
if (!keep) {
synchronized (this) {
mIdleHandlers.remove(idler);
}
}
}
// 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;
}
}
上面的代码有点长,但是每一句都有很重要的意义,这里直接总结一下我的发现:
- 每次遍历Msg,都会计算下一次的遍历时间,而这个时间作为参数传给了nativePollOnce;
- nativePollOnce会计算下一次的唤醒时间,如果是-1,表示锁住,等待唤醒;
- IdleHandler,表示空闲时执行的任务,即需要锁住线程的时候;
我还从没使用过IdleHandler,所以就尝试了一下:
// 版本判断
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
handler.looper.queue.addIdleHandler {
Log.d("lzp", "Handler in Idle")
// 返回true,表示该任务每次空闲时都会执行
// 返回false,表示该任务只会执行一次
true
}
}
经过测试,IdleHandler并不可靠,下面举例:
创建线程,每50毫秒向Handler插入一条Msg,10秒后结束,我希望IdleHandler执行;
很遗憾,上面的需求并不会得到满足,因为程序的执行是非常快的,我们刚处理完第一条Msg,第二条还没有进来,这个时候就已经进入到空闲状态,同理接下来每一条Msg都有可能执行IdleHandler。
问题二:MessageQuene的同步锁何时唤醒
我们已经了解了Handler的轮询处理虽然的确是死循环,但是内部有同步锁的机制,所以并不会锁死线程,但是MessageQuene的同步锁又是什么时候被唤醒的呢?
仔细思考,我们可以猜测:应该是在新消息插入的时候,唤醒了同步锁。我们跟踪一下Handler.post:
// 最后会跟踪到MessageQuene.enqueueMessage
boolean enqueueMessage(Message msg, long when) {
...
synchronized (this) {
...
msg.markInUse();
msg.when = when;
Message p = mMessages;
// 是否需要唤醒同步锁
boolean needWake;
if (p == null || when == 0 || when < p.when) {
// New head, wake up the event queue if blocked.
msg.next = p;
mMessages = msg;
// 立刻唤醒同步锁
needWake = mBlocked;
} else {
// Inserted within the middle of the queue. Usually we don't have to wake
// up the event queue unless there is a barrier at the head of the queue
// and the message is the earliest asynchronous message in the queue.
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;
}
// 唤醒同步锁
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}
Handler.postXXX方法最终都会执行到MessageQuene.enqueueMessage。
总结
最后做一个总结:
- Handler中的Looper和MessageQuene虽然是双死循环,但是MessageQuene中的死循环中使用了同步锁机制,避免资源的占用,所以并不会锁住线程;
- 当有新消息进入队列时,会唤醒同步锁;
以上内容仅供学习参考,如有错误欢迎指正。