详细分析android的MessageQueue.IdleHandler

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

(0)

相关推荐

  • 详细分析android的MessageQueue.IdleHandler

    我们知道android是基于Looper消息循环的系统,我们通过Handler向Looper包含的MessageQueue投递Message, 不过我们常见的用法是这样吧? 一般我们比较少接触MessageQueue, 其实它内部的IdleHandler接口有很多有趣的用法,首先看看它的定义: 简而言之,就是在looper里面的message暂时处理完了,这个时候会回调这个接口,返回false,那么就会移除它,返回true就会在下次message处理完了的时候继续回调,让我们看看它有哪些有趣的用

  • 详细分析Android中onTouch事件传递机制

    onTach介绍 ontach是Android系统中整个事件机制的基础.Android中的其他事件,如onClick.onLongClick等都是以onTach为基础的. onTach包括从手指按下到离开手机屏幕的整个过程,在微观形式上,具体表现为action_down.action_move和action_up等过程. onTach两种主要定义形式如下: 1.在自定义控件中,常见的有重写onTouchEvent(MotionEvent ev)方法.如在开发中经常可以看到重写的onTouchEv

  • 详细分析Android中实现Zygote的源码

    概述 在Android系统中,所有的应用程序进程,以及用来运行系统关键服务的System进程都是由zygote进程负责创建的.因此,我们将它称为进程孵化器.zygote进程是通过复制自身的方式来创建System进程和应用程序进程的.由于zygote进程在启动时会在内部创建一个虚拟机实例,因此,通过复制zygote进程而得到的System进程和应用程序进程可以快速地在内部获得一个虚拟机实例拷贝. zygote进程在启动完成之后,会马上将System进程启动起来,以便它可以将系统的关键服务启动起来.

  • Android Loop机制中Looper与handler详细分析

    目录 Looper是什么 简单使用 从源码了解loop原理 loop分析 myLooper() next() handler.dispatchMessage handler分析 消息入队 同步屏障 总结 Looper是什么 用于为线程运行消息循环的类.默认情况下,线程没有与之关联的消息循环.要创建一个,在要运行循环的线程中调用 prepare(),然后调用loop()让它处理消息,直到循环停止为止.与消息循环的大多数交互是通过 Handler类进行的. 意思大概就是让线程有处理消息的能力,并且这

  • Android编程之微信SDK分享功能过程步骤详细分析

    本文实例讲述了Android编程之微信SDK分享功能过程步骤详细分析.分享给大家供大家参考,具体如下: 之前已经分析过怎么用官方的demo分享微信信息了,在这里我就不再多说,其中关于在自己应用分享说得很简单,本文作者也是经过一番折腾才弄成功,为了以后让大家都少走弯路,决定在这里从头到尾介绍怎么在自己的应用中分享功能 注意:顺序不能乱!! 1.建立自己的应用 TestShareWX (1)应用包名是com.freeson.test,然后建立一个测试Activity,名字为TestActivity,

  • Android关键字persistent详细分析

    目录 Android关键字persistent原理分析​ persistent属性作用 使用 二.原理分析 1.persistent属性的解析 2.系统启动时启动persistent为true的app 3.app被异常结束后系统重新启动persistent为true的app 总结 Android关键字persistent原理分析​ 在Android程序开发时我们会接触到一些系统为了某些功能而定义的关键属性,例如在AndroidManifest.xml文件中 经常看到的persistent.pro

  • Android Choreographer源码详细分析

    目录 一.首先介绍一些基础知识 二.android源码中Choreographer是如何运行 一.首先介绍一些基础知识 1.刷新率(Refresh Rate): 刷新率代表屏幕在一秒内刷新屏幕的次数,用赫兹来表示.赫兹是频率的单位,一秒震动的次数.这个刷新率取决于硬件固定的参数.这个值一般是60Hz.即每16.66ms刷新一次屏幕. 2.帧速率(Frame Rate): 帧速率代表了GPU在一秒内绘制操作的帧数.比如30FPS.60FPS.Frame Per Second. 3.如果两个设备独立

  • Android用于加载xml的LayoutInflater源码超详细分析

    1.在view的加载和绘制流程中:文章链接 我们知道,定义在layout.xml布局中的view是通过LayoutInflate加载并解析成Java中对应的View对象的.那么具体的解析过程是哪样的. 先看onCreate方法,如果我们的Activity是继承自AppCompactActivity.android是通过getDelegate返回的对象setContentView,这个mDelegate 是AppCompatDelegateImpl的实例. @Override protected

  • 深入Android Handler,MessageQueue与Looper关系

    关联篇:HandlerThread 使用及其源码完全解析 关联篇:Handler内存泄漏详解及其解决方案 一说到Android的消息机制,自然就会联想到Handler,我们知道Handler是Android消息机制的上层接口,因此我们在开发过程中也只需要和Handler交互即可,很多人认为Handler的作用就是更新UI,这也确实没错,但除了更新UI,Handler其实还有很多其他用途,比如我们需要在子线程进行耗时的I/O操作,可能是读取某些文件或者去访问网络等,当耗时操作完成后我们可能需要在U

  • 从源码角度分析Android的消息机制

    前言 说到Android的消息机制,那么主要的就是指的Handler的运行机制.其中包括MessageQueue以及Looper的工作过程. 在开始正文之前,先抛出两个问题: 为什么更新UI的操作要在主线程中进行? Android中为什么主线程不会因为Looper.loop()里的死循环卡死? UI线程的判断是在ViewRootImpl中的checkThread方法中完成的. 对于第一个问题,这里给一个简单的回答: 如果可以在子线程中修改UI,多线程的并发访问可能会导致UI控件的不可预期性,采用

随机推荐