目录
一、插曲
最近项目中遇到一个问题:
在某个子线程的回调中,用handler(主线程中创建的)post去更新了一个UI,类似下面的样例代码:
Handler mHandler = new Handler(Looper.getMainLooper());
// onPageLoad在子线程中调用
public void onPageLoad(){
Log.d("bcc", "onPageLoad1: "+System.currentTimeMillis());
mHandler.post(new Runnable() {
@Override
public void run() {
Log.d("bcc", "onPageLoad2: "+System.currentTimeMillis());
// 下面是刷新view的代码
.....
}
});
}
结果发现:从onPageLoad1到onPageLoad2间隔了好有5~6秒。
于是很快分析出了原因:在我们app的首页刚进来,绘制任务比较重,主线程的looper比较繁忙时,确实会发生这样的情况。
如何优化?
二、优化
经过一顿xxx,发现了handler还有一个这样的api
尼玛 一堆英文注释!!
重点:
Causes the Runnable r to executed on the next iteration through the message queue
意思就是:looper下一次拿到的消息用着这个方法post过去的
嘿嘿!如果用这个岂不是很快就执行了呢?算了还是继续跟一下源码吧,万一理解错了。
handler.java:
这个enqueueMessage中的第三个参数uptimeMillis 为0,读者们需要重点关注一下,下文分析要提到。
MessageQueue.java:
好嘛!还挺长。。。最终调用到MessageQueue中的enqueueMessage方法。
还记得上文提到了uptimeMillis 吗?值为0,也就是这里的when,显然会执行下面逻辑:
//
if (p == null || when == 0 || when < p.when) { // New head, wake up the event queue if blocked. msg.next = p; mMessages = msg; needWake = mBlocked; }
///
消息队列其实就是一个单向链表,p就是当前链表的头部(c里面就是链表头部指针),上面的这段逻辑就是把新来的消息插入了链表的头部,也就是消息队里的头部。
哈,果然是这样。于是换成postAtFrontOfQueue后,效果果然明显,间隔只有200多ms了。
等等?还有200ms?这样又是什么原因呢?
三、继续深入,锁?
理论上新的消息被添加队列头部(不考虑后面又有新的消息被添加到头部),当正在执行的任务完成后,新的消息很快会被执行。带着上面的疑惑,我们再回过头看一下MessageQueue#enqueueMessage的源码:
原来这里有个锁,锁住是当前looper中的消息队里,也就是主线程的消息队里,可想而知主线程的消息对列一般是很“忙”的,而我们是在子线程中调用了postAtFrontOfQueue,大概率子线是无法竞争过主线程的,因此消息被添加到消息队列头部是有一定的延迟的。
ok,到此分析完结。
总结
1、如果想要加快handler对某个特定任务的执行,可以采用postAtFrontOfQueue方法。
2、如果在子线程或者优先级比较低的线程中使用postAtFrontOfQueue,那就要考虑到postAtFrontOfQueue还是会出现一定的“延迟”。所以想要达到立即执行的效果,不能跨线程使用。