作者:谷言,腾讯移动客户端开发工程师
商业转载请联系腾讯 WeTest 获得授权,非商业转载请注明出处。
原文链接:http://wetest.qq.com/lab/view/352.html

WeTest 导读

干货!干货!或许可以是一种处理问题的新思路哟!

前言

我们知道 android 是基于 Looper 消息循环的系统,我们通过 Handler 向 Looper 包含的 MessageQueue 投递 Message, 不过我们常见的用法是这样吧?

一般我们比较少接触 MessageQueue, 其实它内部的 IdleHandler 接口有很多有趣的用法,首先看看它的定义:

简而言之,就是在 looper 里面的 message 暂时处理完了,这个时候会回调这个接口,返回 false,那么就会移除它,返回 true 就会在下次 message 处理完了的时候继续回调,让我们看看它有哪些有趣的用法吧~~

一、提供一个 android 没有的声明周期回调时机

 如果有这种需求,想要在某个 activity 绘制完成去做一些事情,那这个时机是什么时候呢?有同学可能觉得 onResume() 是一个合适的机会,不是可是这个 onResume() 真的是各种绘制都已经完成才回调的吗?No, too naive  ~~

你看谷老师说了,onStart 是用户可见,onResume 是用户可交互,谷老师可没说 onResume 是绘制完成吧~那么 android 那些耗时的 measure, layout, draw 是在什么时候执行的呢?它们跟 onResume() 又有何关系呢?让我们先来看看源码吧~

1. ActivityThread.java
我们知道 app 的进程其实是 ActivityThread, 那么 activity 的生命周期自然是它来执行了,

performResumeActivity 就是回调 onResume 了, 我们继续看 wm.addView 方法, 这个 ViewManager 是一个接口,其实现者是 WindowManagerImpl

2.WindowManagerImpl.java

这个 mGlobal 是 WindowManagerGlobal 对象,我们继续

3.WindowManagerGlobal.java

这里我们 new 出了 ViewRootImpl 对象, 我们知道这个对象就是 android view 的根对象了,负责 view 绘制的 measure, layout, draw 的巨长的方法 performTraversals 就是这个类的,我们继续看 setView 方法

4.ViewRootImpl.java

这个函数调用了关键方法 requestLayout(),  我们继续跟踪,顺便说下,后面一连串的 BadTokenException 就是我们常常遇到的 dialog 相关抛出的,也有些特殊场景也会出这个异常,可以到这里查看线索。

 调用了 scheduleTraversals, 从名字就能看出来了吧:

它往 Choreographer 里面 post 了一个 runnable, 这个 Choreographer 是 android 负责帧率刷新相关的东西,我们暂时可以不关注它,可以理解为往主线程 post 一个消息是一样的,顺便说下这个 Choreographer 可以做帧率检测相关的东西,,可以用于卡顿检测什么的···

 我们看这个 runnable 果然是去执行了那个巨长无比的函数 performTraversals 函数,  现在我们可以总结下流程了:

结论:所以如果我们想在界面绘制出来后做点什么,那么在 onResume 里面显然是不合适的,它先于 measure 等流程了, 有人可能会说在 onResume 里面 post 一个 runnable 可以吗?还是不行,因为那样就会变成这个样子

所以你的行为一样会在绘制之前执行,这个时候我们的主角 IdleHandler 就发挥作用了,我们前面说了,它是在 looper 里面 message 暂时执行完毕了就会回调,顾名思义嘛,Idle 就是队列为空的意思,那么我们的 onResume 和 measure, layout, draw 都是一个个 message 的话,这个 IdleHandler 就提供了一个它们都执行完毕的回调了,大概就是这样

说了这么多,那么现在获取到这个时机有什么用呢? look!!

这个是我们地图的公交详情页面, 进入之后产品要求左边的页卡需要展示,可以看到左边的页卡是一个非常复杂的布局,那么进入之后的效果可以明显看到头部的展示信息是先显示空白再 100 毫秒左右之后才展示出来的,原因就是这个页卡的内容比较复杂,用数据向它填充的时候花了较长时间,代码如下:

可以看到这个 detailView 就是这个侧滑的页卡了,填充里面的数据花了 90ms,如果这个时间是用在了界面 view 绘制之前的话,就会出现以上的效果了,view 先是白的,再出现,这样就体验不好了,如果我们把它放到 IdleHandler 里面呢?代码如下:

效果是这样的:

看出不同了吗?顶部的页卡先展示出来了,这样体验是不是会更好一些呢。虽然只有短短 90ms,不过我们做 app 也应该关注这种细节优化的,是吧~ 这个做法也提供了一种思路,android 本身提供的 activity 框架和 fragment 框架并没有提供绘制完成的回调,如果我们自己实现一个框架,就可以使用这个 IdleHandler 来实现一个 onRenderFinished 这种回调了。

二、可以结合 HandlerThread, 用于单线程消息通知器

我们先思考一个问题,如果有一个 model 数据管理模块,怎么设计?比如地图的收藏模块的 model 部分。就是下面这个图的小星星:

它原来的 model 设计大概是这个样子的:

 由于这个 model 是单例的,而且是多线程可以访问的,所以它的增删改查都加上了锁,而且由于外部访问需要遍历有哪些收藏点,所以外部遍历列表也需要加锁,大概是这样的:

 因为是多线程可访问的,如果遍历不加锁的话,其他线程删除了一个收藏,就会 crash 的,原来的这样设计有几个不好的地方:
1. 外部使用者需要关系锁的使用,增加了负担,不用还不安全
2. 如果在主线程加锁的话,可能另一个线程执行操作会阻塞主线程造成 anr

总之,多线程代码就是容易出错,而且真的出错的时候查起来太费劲了,目前收藏夹模块就有 N 多 bug,所以我想用单线程来解决这个问题,由于 model 层的访问需要数据库和网络等,所以需要异步线程,那么单线程队列 + 异步线程,首先想到的就是 HandlerThread, 大概架构如下:

 现在,我们把原来多线程的逻辑改到了单线程里面,各种收藏的 model 共用一个 HandlerThread,这样我们增删改查都不用加锁了,出错几率大大减小,而且这种 model 的设计有点类似插件的意思,可以很方便的增加其他收藏。

Ok, 那么跟我们的主题 IdleHandler 有什么关系呢?思考这样一个问题,地图上的小星星需要实时更新,也就是 model 的任何变化都需要显示到地图上,那么收藏的小星星就应该作为 model 的观察者,以前的做法是向收藏 model 注册监听,在每一个增删改查操作后都对观察者回调,大概是这样:

这样有一个小小的问题,就是如果有一个操作生成 10 个快速连续的增删改查操作,那么我们的 UI 就会收到 10 次回调,而这种场景下我们其实只需要最后一次回调就够了,中间操作其实不用刷新 UI 的。

那么现在改成单线程模型,我们又该如何处理这个问题呢?当然我们也能在每个 post 到异步线程的 runnable 里面去回调观察者,但这样未免不够优雅,所以这个时候 IdleHandler 不就又可以发挥作用了吗?它是在消息暂时处理完的时候回调的呀,不是很符合我们的时机么,对吧?

就是这个样子了,这里为什么不用第一个场景下的 Looper.myQueue().addIdleHandler() 呢?注意这个地方 Looper.myQueue() 如果在主线程调用就会使用主线程 looper 了,所以我选择反射这个 HandlerThread 的 looper 来设置它,这个 IdleHandler 我们返回了 true, 表示我们要长期监听消息队列,因为返回 false,下次就没有回调了哦。

好了,结论是这个地方 IdleHandler 用作了一个消息的触发器,是不是挺有意思的呢?

三、 结语

 如果你没有用过它,从今天开始试试吧,这篇文章只是我个人的一点小思路,说不定这个 IdleHandler 有很多其他的用法呢~~

腾讯 WeTest提供上千台真实手机,随时随地进行测试,保障应用/手游品质。节省百万硬件费用,加速敏捷研发流程。

同时腾讯 WeTest 兼容性测试团队积累了 10 年的手游测试经验,旨在通过制定针对性的测试方案,精准选取目标机型,执行专业、完整的测试用例,来提前发现游戏版本的兼容性问题,针对性地做出修正和优化,来保障手游产品的质量。目前该团队已经支持所有腾讯在研和运营的手游项目。

欢迎进入:http://wetest.qq.com/product/cloudphone
体验安卓真机

欢迎进入:http://wetest.qq.com/product/expert-compatibility-testing 使用专家兼容测试服务。WeTest 兼容性测试团队期待与您交流!You Create,We Test!

如果对使用当中有任何疑问,欢迎联系腾讯 WeTest 企业 QQ:800024531


↙↙↙阅读原文可查看相关链接,并与作者交流