奇怪的疑惑
在日常工作或者学习时,总会生出各种奇怪的疑惑,寻找答案的过程(一把梭,搜)非常有意思,得到的答案(一把梭,猜)也非常有价值。那么这份意思和价值就应该分享出来~
疑问来源
看到项目老代码做了一件事,自定义了一个页面的RootView,在首次onDraw的时候进行一些初始化工作,伪代码如下:
RootView {
onDraw() {
if (firstDraw) {
mActivity.handler.postDoSomething();
}
}
}
乍一看,哟,这小伙儿想在页面首次绘制完成后(偷摸摸)干一些事儿,操作骚的很嘛
不过骚操作我也会,这种蜜汁需求我好像还有其他方法完成,诶,记得上次看了个IdleHandler可以用来做类似的事,待我翻翻以前看到的文章IdleHandler,页面启动优化神器
该文章分析到,我们可以通过IdleHandler实现在界面绘制完成后进行一个回调,从而(偷摸摸)干上一些事儿
问题来了,IdleHandler真这么骚?
验他一验
好嘛,这么骚的操作,我来验证一下:Open Demo Project
Activity :
@Override
protected void onResume() {
super.onResume();
Log.i("TEST_TAG", "onResume: ");
Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() {
@Override
public boolean queueIdle() {
Log.i("TEST_TAG", "queueIdle: ");
return false;
}
});
}
RootView :
RootView {
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
Log.i("TEST_TAG", "onDraw: ");
mAcrivity.handler.post(new Runnable() {
@Override
public void run() {
Log.i("TEST_TAG", "run: post");
}
});
}
}
Run It!
分析一下
情况符合预期,由于在ActivityThread内先调用Activity.onResume(),后调用window.addView(),所以输出onResume日志之后输出onDraw日志,而后因为在onDraw内post了一个任务,输出了run日志,在这之后由于主线程idle了(没活儿了)所以调用了IdleHandler的回调输出queueIdle日志
再生疑惑
之前测试在IdleHandler回调中返回了false,表示执行完成后从MessageQueue中移除掉自己,如果哪次手抽返回了true了?岂不是因为Looper死循环一直获取MessageQueue.next()
,导致一直判断没有下一个message
,从而一直疯狂的调用idleHandler?就像下面Message.next()
的源码所说:
Message next() {
...一坨代码
for (;;) {
...一坨代码
if (msg != null) {
if (now < msg.when) {
...省略
} else {
...省略
return msg;
}
}
...一坨代码
// Run the idle handlers.
// We only ever reach this code block during the first iteration.
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);
}
}
}
... 省略
}
}
源码说,如果当前时刻没有message需要处理,那我就应该翻牌IdleHandler。
但是,如果长时间没有message需要处理,处在for (;;)
死循环内的咱们,唯一离开循环的条件message != null
长时间不成立,岂不是一直在循环,一直在调用IdleHandler?
恍然大悟
哦不!我这该死的脑子🧠总是记不住东西!精华全忘,代码都少抠了!
Message next() {
...一坨代码
for (;;) {
...一坨代码
// 居然忘了你!
nativePollOnce(ptr, nextPollTimeoutMillis);
if (msg != null) {
if (now < msg.when) {
...省略
} else {
...省略
return msg;
}
}
}
}
居然忘了没有消息的时候线程通过nativePollOnce(ptr, nextPollTimeoutMillis);
进入了休眠状态
也就是说,IdleHandler的调用时机相关描述应该为:
每次消费掉一个有效message,在获取下一个message时,如果当前时刻没有需要消费的有效(需要立刻执行)的message,那么会执行IdleHandler一次,执行完成之后线程进入休眠状态,直到被唤醒
老有问题
那么问题又来了,学挖掘....(啪,老实点!)
哦不,问题是IdleHandler到底能用来解决什么问题?实现什么鬼需求?
不要怀疑Google的Coder
Google的Coder既然把IdleHandler放在了这,那么...那么我们就看看他用来干什么了。
打开MessageQueue.java
找到interface IdleHandler
,按住command点他一点
粗略看了看(挑简单不费事能看明白的看),比如ActivityThread内GcIdler用于某些情况下强制进行BinderInternal.GC
(虽然我不知道这是在干啥),比如在MonitoringInstrumentation内用作数据检测用,比如在TextToSpeechService
内结合HandlerThread用于任务完成后进行广播通知用。
我还需要更多答案
看Google大佬用IdleHandler还真发现一些用法,那他还有更多的骚操作嘛?
这个问题比较严肃,就需要交给Google了
[图片上传失败...(image-2eeb1f-1566198944827)]
<figcaption></figcaption>
经过检索提炼,发现每日一问 听说过Handler中的IdleHandler吗?链接内对这个问题有讨论,也分析了系统怎么用它,看起来有些货
来自链接内,建议进入阅读
@bigdevil 说
...省略部分...,目前可以想到的场景
- Activity启动优化:onCreate,onStart,onResume中耗时较短但非必要的代码可以放到IdleHandler中执行,减少启动时间
- 想要在一个View绘制完成之后添加其他依赖于这个View的View,当然这个用View#post()也能实现,区别就是前者会在消息队列空闲时执行
- 发送一个返回true的IdleHandler,在里面让某个View不停闪烁,这样当用户发呆时就可以诱导用户点击这个View,这也是种很酷的操作
- 一些第三方库中有使用,比如LeakCanary,Glide中有使用到,具体可以自行去查看
结语
如果我的文章确实有帮助到你,请不要忘了点一下👍
难免有很多地方理解不到位甚至解释错误,还请请直接指出
作者:不归路上的码渣
链接:https://juejin.im/post/5d55779b518825168e6a0430
关注+加群:
Android进阶技术交流 (895077617 )免费获取整理版的资料
群里可以与大神一起交流并走出迷茫。新手可进群免费领取学习资料,看看前辈们是如何在编程的世界里傲然前行!有想学习Android Java的,工作中想提升自己能力的,正在学习的小伙伴欢迎加入。(包括Java在Android开发中应用、APP框架知识体系、高级UI、全方位性能调优,ViewPager,Bitmap,组件化架构,四大组件等深入学习视频资料以及Android、Java全方面面试资料)
网友评论