爱收集资源网

Handler流程图:解密八股文的核心步骤

网络整理 2023-09-30 17:03

序言

Handler属于八股文中特别精典的一个试题了,造成这个知识点好多时侯,考官都懒得问了;这玩意儿许久之前就看过,然而过了一段时间,就很容易忘掉,并且处理显存泄露,aidlHandler之类的考点答案肯定很难忘。。。其实考官好多时侯揶揄问,而且要是问到了,你忘了且不晓得如何回答,那就很难堪了

#9:b:8:a:3:c:b:8:d:6:b:0:6:2:e:0:5:7:2:7:5:8:a:1:f:0:2:c:6:0:1:4#

小弟也来炒个剩菜,力求浅显易懂的来描述下Handler机制的整个流程,相关知识点,画了一些流程图,时序图来展示其运行机制,让本文图文并茂!

文章中关键技巧源码,可以直接点击方式名,跳转查看对应方式的源码

假如看了没收获,喷我!

#b:9:4:7:a:e:7:6:6:5:e:8:6:c:f:b:c:2:4:a:b:2:d:8:b:c:5:1:3:4:6:4#

总流程

开头须要构建个handler作用的总体印象,下边画了一个总体的流程图

#d:e:4:4:3:e:2:7:c:d:8:f:7:7:9:3:7:2:9:8:a:b:3:4:6:2:a:5:c:0:9:5#

Handler总流程

从里面的流程图可以看出,总体上是分几个大块的

相关知识点大约涉及到那些,下边详尽讲解下!

#0:7:5:3:5:b:d:6:8:c:3:f:f:d:c:8:6:f:6:4:0:e:d:3:1:0:2:1:f:d:e:a#

Handler流程

使用

先来看下使用,不然源码,原理图搞了一大堆,一时想不起如何用的,就难堪了

使用很简单,此处仅做个展示,你们可以熟悉下

演示代码尽量简单是为了演示,关于内部类持有弱引用或则销毁反弹中清空消息队列之类,就不在此处展示了

#f:9:7:1:1:e:4:f:d:a:6:6:7:a:7:b:8:f:1:6:a:a:2:3:e:d:8:e:f:2:0:c#

从里面源码可知,handler的使用总的来说,分俩大类,细分五小类

收发消息分开收发一体

#e:4:8:3:8:7:0:e:c:2:d:f:8:7:e:2:5:3:8:2:5:9:1:b:9:b:f:1:1:9:a:8#

收发分开mCallback.handleMessage(msg)

#6:d:e:5:b:4:2:d:1:a:0:3:4:b:4:7:d:1:2:1:5:c:9:f:3:2:1:8:2:7:3:5#

handleMessage(msg)

#2:0:5:5:b:3:e:c:b:3:7:0:6:2:0:d:1:b:e:4:6:f:4:2:1:7:a:d:f:8:c:e#

prepare和loop

你们肯定有印象,在子线程和子线程的通讯中,就必须在子线程中初始化Handler,必须这样写

#d:1:f:c:4:a:8:d:4:6:e:3:7:3:f:5:0:d:4:b:d:2:6:0:2:f:a:3:5:4:8:e#

#7:5:d:1:0:e:0:1:9:f:3:f:9:6:4:9:c:3:2:5:c:2:3:d:2:0:b:d:a:0:9:5#

为何要使用prepare和loop?我画了个图,先让你们有个整体印象

#2:b:b:9:7:b:d:0:2:d:b:b:a:d:b:5:7:6:c:b:6:4:e:d:c:7:8:4:0:4:f:f#

Handler---Looper

具体看下每位步骤的源码,这儿也会标定好链接,便捷你们随时过去查看

#c:3:6:f:a:5:d:c:a:5:1:c:0:4:4:2:1:e:2:a:7:3:3:9:e:4:3:a:e:d:e:5#

#e:6:7:d:e:b:c:7:3:9:e:d:2:8:a:e:c:4:c:a:f:d:4:a:2:9:7:1:7:8:5:6#

#c:d:b:f:3:a:6:6:e:7:f:6:8:8:b:a:9:e:6:a:5:5:6:2:d:f:6:d:4:a:e:e#

#7:4:f:7:b:e:a:f:a:9:b:5:b:9:b:a:a:d:2:7:b:d:3:9:5:f:3:5:9:6:4:5#

收发消息

收发消息的操作口都在Handler里,这是我们最直观的接触的点

下方的思维导图整体做了个概括

#5:4:7:a:f:e:6:1:d:a:0:c:d:b:7:2:d:8:2:d:4:2:4:1:2:d:9:c:6:2:a:b#

Handler-收发消息

发送消息

发送消息涉及到俩个方式:post(...)和sendMessage(...)

#1:1:b:f:6:b:4:c:7:e:0:d:5:8:a:f:6:6:a:4:4:2:3:4:d:2:4:b:5:a:a:4#

#7:6:1:8:d:6:f:5:7:d:2:6:7:2:c:d:1:5:e:a:b:a:1:a:5:d:3:5:6:8:6:7#

#e:2:9:f:2:c:1:6:a:6:4:9:2:3:4:5:8:9:d:a:d:6:c:6:7:c:c:a:5:c:8:e#

Handler---消息插入消息队列

接收消息

接受消息相对而言就简单多

#6:b:b:1:f:9:1:2:6:c:b:2:c:a:6:d:a:8:0:e:8:6:0:b:5:d:d:9:c:2:5:0#

分发消息

消息分发是在loop()中完成的,来瞧瞧loop()这个重要的方式

#b:5:a:7:9:1:8:4:4:1:7:7:e:8:b:d:d:3:b:8:e:1:6:e:1:b:1:e:a:0:f:8#

遍历消息

遍历消息的关键技巧肯定是下边这个

来瞧瞧这个Message中的next()方式吧

#f:6:d:3:1:9:e:a:c:3:b:d:e:6:3:d:6:1:f:6:8:e:6:6:4:e:f:6:7:3:e:9#

总结下源码上面抒发的意思

next()内部是个死循环,你可能会困惑,只是拿下一节点的消息,为什么要死循环?为了消息执行历时以及同步屏障等等,这个死循环是必要的nativePollOnce阻塞方式:到了超时时间(nextPollTimeoutMillis)或则通过唤起形式(nativeWake),会解除阻塞状态nextPollTimeoutMillis小于等于零,会规定在此段时间内休眠,之后唤起消息队列为空时,nextPollTimeoutMillis为-1,步入阻塞;重新有消息步入队列,插入头结点的时侯会触发nativeWake唤起方式假如msg.target==null为零,会步入同步屏障状态会将msg消息死循环到末尾节点,除非遇到异步方式假如遇到同步屏障消息,理论上会仍然死循环里面操作,并不会返回消息,除非,同步屏障消息被移除消息队列假如当前时刻和返回消息的超时时刻判断当前时刻大于返回消息超时时间:步入阻塞,估算时间差,给nativePollOnce设置超时时间,超时时间一到,解除阻塞,重新循环取消息当前时刻小于返回消息超时时间:获取可用消息返回when这个变量可能比较疑虑,这玩意儿是如何来的,初始值不都是0吗?实际上我们发送消息的时侯,可以发送延时消息:sendMessageDelayed(@NonNullMessagemsg,longdelayMillis)MessageQueue类中的enqueueMessage方式,会把延时时间(SystemClock.uptimeMillis()+delayMillis)形参给Message对象的when数组消息返回后,会将mMessage形参为返回消息的下一节点(只针对不涉及同步屏障的同步消息)

这儿简单的画了个流程图

#a:2:8:4:e:8:f:5:e:0:c:f:9:4:1:5:2:3:a:7:e:d:5:3:e:e:6:1:6:b:1:7#

Handler---消息遍历

分发消息

分发消息主要的代码是:msg.target.dispatchMessage(msg);

也就是说这是Handler类中的dispatchMessage(msg)方式

#5:c:0:7:7:c:5:f:0:e:f:9:c:0:a:d:9:a:0:c:8:f:5:3:d:a:9:c:e:9:0:3#

可以看见,这儿的代码,在收发消息栏目的接受消息那块早已说明过了,这儿就无须重复了

消息池

msg.recycleUnchecked()是处理完成分发的消息,完成分发的消息并不会被回收掉,而是会步入消息池,等待被复用

#9:0:8:d:8:c:7:2:0:3:4:a:c:3:c:e:6:0:b:2:9:6:6:6:e:7:c:9:2:2:8:2#

来看下消息池回收消息图示

#a:a:6:0:7:0:3:7:4:3:1:7:3:b:e:9:6:c:5:8:c:c:d:e:f:d:3:4:c:c:5:3#

Handler---消息池(存)

既然有将已使用的消息回收到消息池的操作,那肯定有获取消息池上面消息的方式了

#9:a:6:7:3:5:e:5:8:7:c:c:b:c:d:0:c:0:0:a:8:f:9:e:4:d:a:7:9:4:d:f#

来看下从消息池取一个消息的图示

#9:1:0:c:a:f:c:6:c:6:b:f:0:d:8:f:6:0:3:6:8:d:0:a:4:d:a:7:2:3:7:c#

Handler---消息池(取)

IdleHandler

在MessageQueue类中的next方式里,可以发觉有关于对IdleHandler的处理,你们可千万别以为它是哪些Handler特殊方式之类,这玩意儿就是一个interface,上面具象了一个方式,结构十分的简单

#3:a:5:c:a:5:3:b:1:2:3:a:6:4:1:8:d:2:3:8:2:5:2:e:3:6:9:0:3:2:8:b#

实际上从里面的代码上面,可以剖析出好多信息

IdleHandler相关信息

总结从其源码上,可以看下来,IdlerHandler是在消息处理的空闲时刻,专门拿来处理相关事物的同步屏障

来到最复杂的模块了

在理解同步屏障的概念前,我们须要先搞清几个后置知识

后置知识同步和异步消息

哪些是同步消息?哪些是异步消息?

异步消息:理论上只要依照位操作,左向右,第二位为1的数,isAsynchronous返回true;并且,Message上面基本只使用了:0,1,2,可得出推论

#d:6:f:8:9:b:b:c:2:5:e:0:3:2:8:4:2:4:0:2:b:e:6:6:e:2:0:4:a:5:9:e#

#1:1:5:d:e:8:0:b:0:6:5:3:9:e:1:b:3:0:d:6:1:5:6:f:3:4:c:8:1:8:e:d#

#2:3:e:4:e:c:3:2:8:d:b:c:6:4:a:c:e:7:0:0:b:2:2:3:b:2:f:4:3:c:6:e#

默认消息类型

我们正常情况下,甚少会使用setAsynchronous方式的,这么在不使用该技巧的时侯,消息的默认类型是哪些呢?

关于同步异步,可以看到和mAsynchronous息息相关

#9:a:3:1:6:a:1:2:d:f:5:d:f:5:8:a:c:6:f:6:b:e:7:8:a:5:b:e:3:8:0:0#

#2:2:b:6:0:5:4:6:b:9:3:d:4:5:0:e:1:f:6:9:2:4:0:a:c:c:f:2:f:9:7:3#

#2:5:0:2:5:3:d:5:e:d:6:9:5:5:a:d:7:e:a:c:d:4:9:a:8:3:1:3:e:0:d:2#

生成同步屏障消息

在next方式中发觉,target为null的消息被称为同步屏障消息,那他为什么叫同步屏障消息呢?

#9:7:1:6:f:5:a:1:b:a:3:b:3:d:b:9:9:c:c:4:4:d:8:d:0:f:8:c:a:7:1:f#

#9:1:7:2:2:c:1:a:2:e:f:4:6:0:2:a:8:f:b:0:9:0:d:3:3:7:3:a:d:d:7:4#

Handler---同步屏障入消息队列

同步屏障流程

#b:4:8:c:9:e:d:e:0:7:1:9:1:b:9:c:5:6:0:4:7:e:d:d:a:2:7:3:e:7:b:5#

去除大量我们无需关注的代码,发觉这也没啥嘛,就是一堆ifelesfor之类的,来剖析剖析

Messagemsg=mMessages:这步形参是十分重要的,表示虽然我们对msg一顿操作,mMessage还是保留消息队列头结点消息的位置msg.target==null:遇见同步屏障消息首先是一个while循环,内部逻辑,不断将msg节点的位置后移结束while的俩个条件msg移到尾结点,也就是移到了消息队列尾结点,将自身形参为null(尾结点的next)遇上标记为异步的消息,放行该消息进行后续分发剖析下,俩个放行条件形成的不同影响消息队列不含异步消息当我们在同步屏障逻辑上面,将msg自身移到尾结点,并形参为null(尾结点的next)msg为null,是难以进行后续分发操作,会重新进行循环流程mMessage头结点重新将自身位置形参给msg,继续上述的重复过程可以发觉,上述逻辑确实起到了同步屏障的作用,屏蔽了其所有后续同步消息的分发;只有移除消息队列中的该条同步屏障消息,能够继续进行同步消息的分发消息队列富含异步消息消息队列中若果有异步消息,同步屏障的逻辑会放行异步消息同步屏障上面堆prevMsg形参了!请记住在整个方式上面,只有同步屏障逻辑上面堆prevMsg形参了!这个参数为null与否,对消息队列节点影响很大prevMsg为空:会直接将msg的next形参给mMessage;说明分发完消息后,会直接移除头结点,将头结点的下一节点形参为头结点prevMsg不为空:不会对mMessage投节点操作;会将分发消息的上一节点的下一节点位置,换成份发节点的下一节点,有点绕通过前面剖析,可知;异步消息分发完后,会将其直接从消息队列中移除,头结点位置不变

文字写了一大堆,我也是尽可能详尽描述,同步屏障逻辑代码块会形成的影响,整个图,加深下印象!

#6:6:8:9:7:c:9:9:8:3:9:f:0:3:c:7:3:5:2:a:7:e:e:5:e:0:3:8:3:1:d:f#

同步屏障流程

同步屏障作用

这么这个同步屏障有哪些作用呢?

有个急需的问题,就是哪些地方用到了postSyncBarrier(longwhen)方式,这个方式对外是不曝露的,只有内部包才能调用

搜索了整个源码包,发觉只有几个地方使用了它,剔除测试类,MessageQueue类,有作用的就是:ViewRootImpl类和Device类

Device类ViewRootImpl类

该栏目的剖析,必须引用一个极其重要的推论,给出该推论的文章:源码剖析_AndroidUI何时刷新_Choreographer

我们那边早已有了大神给出的推论,我们晓得了界面刷新(requestLayout或则invalidate)的过程一定会触发scheduleTraversals()方式,这说明会添加同步屏障消息,那肯定有移除同步屏障消息的步骤,这个步骤很有可能存在doTraversal()方式中,来看下这个技巧

#f:a:4:5:d:f:2:a:d:e:5:d:8:7:8:7:9:e:b:9:1:c:a:4:5:3:1:c:e:1:5:2#

总结

调用View的requestLayout或则invalidate时,最终就会执行scheduleTraversals(),此时会在主线程消息队列中插入一个同步屏障消息(停止所有同步消息分发),会将mTraversalRunnable添加到mCallbackQueues中,并注册接收Vsync的窃听,当接受到Vsync通知后,会发送一个异步消息,触发遍历执行mCallbackQueues的方式,这会执行我们添加的反弹mTraversalRunnable,进而执行doTraversal(),此时会移除主线程消息队列中同步屏障消息,最后执行勾画操作

调用requestLayout或则invalidate时,会在主线程消息队列中插入一个同步屏障消息,同时注册接收Vsync的窃听;当接受到Vsync通知,会发送一个异步消息,执行真正的勾画风波:此时会移除消息队列中的同步屏障消息,之后才能执行勾画操作

#f:a:d:2:e:3:a:3:7:e:6:f:8:6:1:d:6:b:4:5:e:3:c:2:3:f:0:a:4:b:c:2#

Handler---同步屏障作用

总结消息插入区别

#f:8:6:7:f:f:3:4:4:c:9:7:9:8:b:7:f:0:3:8:4:c:c:f:b:2:b:1:e:8:b:8#

正常发消息和同步屏障

Vsync

#1:8:8:d:7:3:9:c:0:9:0:f:5:1:5:3:4:b:6:0:c:f:6:c:2:f:2:b:e:6:2:d#

同步屏障在ViewRootImpl中作用

总结

相关总结

同步屏障能确保在UI刷新中:Vsync讯号到来后,就能立刻执行真正的勾画页面操作

同步消息和异步消息使用建议

在正常的情况,肯定不建议使用异步消息,此处假定一个场景:由于某种需求,你发送了大量的异步消息,因为消息步入消息队列的特殊性,系统发送的异步消息,也只能乖乖的排在你的异步消息旁边,假定你的异步消息抢占了大量的时间片,甚至占用了几帧,造成系统UI刷新的异步消息未能被及时执行,此时很有可能发生闪退

其实,假如你能看明白这个同步屏障栏目所写的东西,相信哪些时侯设置消息为异步,心里肯定有数

考点

里面源码基本就剖析到那边了,俺们瞧瞧能按照这种知识点,能提一些哪些问题呢?

1、先来个自己想的问题:Handler中主线程的消息队列是否有数目上限?为何?

这问题整的有点鸡贼,可能会让你想到,是否有上限这方面?而不是直接想到到上限数目是多少?

解答:Handler主线程的消息队列肯定是有上限的,每位线程只能实例化一个Looper实例(前面讲了,Looper.prepare只能使用一次),不然会抛异常,消息队列是存在Looper()中的,且仅维护一个消息队列

重点:每位线程只能实例化一次Looper()实例、消息队列存在Looper中

拓展:MessageQueue类,虽然都是在维护mMessage,只须要维护这个头结点,能够维护整个消息数组

2、Handler中有Loop死循环,为何没有卡死?为何没有发生ANR?

先说下ANR:5秒内未能响应屏幕触摸风波或按键输入风波;广播的onReceive()函数时10秒没有处理完成;前台服务20秒内,后台服务在200秒内没有执行完毕;ContentProvider的publish在10s内没进行完。所以大致上Loop死循环和ANR联系不大,问了个正确的屁话,所以触发风波后,历时操作还是要放到子线程处理,handler将数据通信到主线程,进行相关处理。

线程实质上是一段可运行的代码片,运行完以后,线程都会手动销毁。其实,我们肯定不希望主线程被over,所以整一个死循环让线程保活。

为何没被卡死:在风波分发上面剖析了,在获取消息的next()方式中,假如没有消息,会触发nativePollOnce方式步入线程休眠状态,释放CPU资源,MessageQueue中有个原生方式nativeWake方式,可以解除nativePollOnce的休眠状态,ok,俺们在这俩个方式的基础上来给出答案

3、为什么不建议在子线程中更新UI?

多线程操作,在UI的勾画方式表示这不安全,不稳定。

假定一种场景:我会须要对一个圆进行改变,A线程将圆减小俩倍,B改变圆颜色。A线程降低了圆三分之一体积的时侯,B线程此时,读取了圆此时的数据,进行改变颜色的操作;最后的结果,可能会造成,大小颜色都不对。。。

4、可以让自己发送的消息优先被执行吗?原理是哪些?

这个问题,我觉得只能说:在有同步屏障的情况下是可以的。

同步屏障作用:在富含同步屏障的消息队列,会及时的屏蔽消息队列中所有同步消息的分发,放行异步消息的分发。

在富含同步屏障的情况,我可以将自己的消息设置为异步消息,可以起到优先被执行的疗效。

5、子线程和子线程使用Handler进行通讯,存在哪些恶果?

子线程和子线程使用Handler通讯,某个接受消息的子线程肯定使用实例化handler,肯定会有Looper操作,Looper.loop()内部富含一个死循环,会造成线程的代码块难以被执行完,该线程一直存在。

假如在完成通讯操作,我们通常可以使用:mHandler.getLooper().quit()来结束分发操作

6、Handler中的阻塞唤起机制?

这个阻塞唤起机制是基于Linux的I/O多路复用机制epoll实现的,它可以同时监控多个文件描述符,当某个文件描述符就绪时,会通知对应程序进行读/写操作.

MessageQueue创建时会调用到nativeInit,创建新的epoll描述符,之后进行一些初始化并窃听相应的文件描述符,调用了epoll_wait方式后,会步入阻塞状态;nativeWake触发对操作符的write方式,窃听该操作符被反弹,结束阻塞状态

详尽请查看:同步屏障?阻塞唤起?和我一起重读Handler源码

7、什么是IdleHandler?哪些条件下触发IdleHandler?

IdleHandler的本质就是插口,为了在消息分发空闲的时侯,能处理一些事情而设计下来的

具体条件:消息队列为空的时侯、发送延时消息的时侯

8、消息处理完后,是直接销毁吗?还是被回收?倘若被回收,有最大容量吗?

Handler存在消息池的概念,处理完的消息会被重置数据,采用头插法步入消息池,取的话也直接取头结点,这样会节约时间

消息池最大容量为50,达到最大容量后,不再接受消息步入

9、不当的使用Handler,为何会出现显存泄露?如何解决?

先说明下,Looper对象在主线程中,整个生命周期都是存在的,MessageQueue是在Looper对象中,也就是消息队列也是存在在整个主线程中;我们晓得Message是须要持有Handler实例的,Handler又是和Activity存在强引用关系

存在某种场景:我们关掉当前Activity的时侯,当前Activity发送的Message,在消息队列还未被处理,Looper间接持有当前activity引用,由于俩者直接是强引用,难以断掉,会造成当前Activity难以被回收

思路:断掉俩者之间的引用、处理完分发的消息,消息被处理后,之间的引用会被重置断掉

解决:使用静态内部类弱引Activity、清空消息队列

最后

倘若您都耐心听到这里了,想必一定是有所收获,舍不得喷我了吧!那既然你这么好学,那我就再帮你一把吧。整理了一份455页的《AndroidFramework精编内核解析》pdf学习指南,想要继续进阶学习的男子伴可以后台私信我获取!

#9:8:2:a:9:3:2:5:3:c:b:9:c:3:8:a:1:1:1:1:6:a:8:b:9:7:0:8:2:c:d:c#

有源代码是什么意思
上一篇:数字信号处理入门(Matlab) 下一篇:没有了
相关文章