2024年HarmonyOS鸿蒙最新彻底理解Handler的设计之传送带模型,2024年最新如何面试鸿蒙工程师

深知大多数程序员,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!


img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上鸿蒙开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

需要这份系统化的资料的朋友,可以戳这里获取

2.3 Message

Message,就是放货物的货槽(篮筐)。

你以为Message是货物?那就错了。Message更准确的理解应该是装货物的货槽(就好像超市里那种购物推车)。

什么是放货物的货槽/篮筐?货物自然是我们想要运输的东西,但是,直接把一个货物扔到传送带上输送,是不是有点不太合理?比如,这个货物要输送到哪?它的标识信息又是什么?

再说具体些,假设我们这里的货物是苹果。那么,我们是把苹果放到货槽 (或者叫篮筐、货框之类的吧,我也不知道叫什么,自己意会一下) 里面,然后再扔到传送带上,而非直接扔到传送带上去运输的。这有什么好处?

这样,你的货槽就能装任意类型的货物,而货槽本身,也就可以携带一些信息,例如:这个货物发往何处、什么时候该被送达、这个货物是什么(货物本身的信息)。

让我们看看Message类的属性:

  • what: int 用于标识Message
    • 这个就代表了货物本身的简要信息,表明了这个货物是什么
  • arg1、arg2: int 少量数据
    • 如果这一个货槽只需要装少量的货物,那就装在这里
  • obj: Object 任意数据
    • 如果这一个货槽想装任意类型的货物,可以装在这里
  • data: Bundle 大量数据
    • 如果这一个货槽想装大量的货物,可以装在这里
  • flag: int 标志,该标志实际上是两个标志,为了节省变量,合二为一了,一是该Message是否为异步消息,二是该Message是否被消费
    • 异步的意思是这个货槽有能力在传送带上插队(优先被处理)
    • 是否被消费即这个货槽是不是空的
  • when: long 要被处理的目标时间
    • 这个货槽应该在什么时间到达传送带的尽头然后被分发处理
  • target: Handler 交给谁去处理(即分发到哪里、目的地)
    • 货槽到了传送带的尽头,然后要开始处理了,target标志了要交给谁去处理(可以交给自己处理,所以说Handler又是生产者、又可以是消费者)
  • callback: Runnable 消息处理的回调
    • 这个属性表明,货物的类型不仅仅局限于一个具体的货物,更可以灵活地是一个“事件”,可被执行。
  • next: Message 下一个货物
    • 这就意味着,每个货槽的尾部,有一个钩子,能直接勾住下一个货槽,把所有货槽这样链起来,从第一个货槽开始,就是一条货槽的链表(队列)

我们继续想,货槽本身应该是类似于筐子一样的东西,那么,它是随随便便就能生产(new)的吗?在这座工厂内,应该要有一个专门的取出货槽的点(就像超市的购物推车,它有一个专门的存放点,然后我们购物是去那里取一个出来用,而不是自己造一个出来,就算一开始没有任何推车,需要造,那也不是我们去造,我们只是取来用。)

好,那既然对于我们来说,我们只是取货槽来用的话,那么这个工厂就得给我们提供一个“取”的方法(Message.obtain),而这样,也就自然而然实现了货槽的复用,货槽只是货物的载体,又不是货物,那我肯定是可以复用的嘛,当货槽的货物被分发处理后,货槽空了,就回到了工厂的货槽存放点(sPool),下次要取,又可以从工厂的货槽存放点来取货槽用。

而货槽存放点在整个工厂内就只有一处,但工厂可能有多个车间(线程),那么不同车间如果同时去这个存放点取货槽,是不是得有个先后顺序?是不是得排队?同时如果多个车间同时用完了货槽,想把它扔回存放处,是不是也得有个先后顺序?ok,所以代码中对sPool的访问需要synchronized(sPoolSync)。

关于货槽(Message),就先说到这里。

2.4 Looper

Looper,就是一个车间里的一条传送带。

  • 为了每个车间内的生产消费工作能有条不紊地有序进行,设计者决定,一个车间内,只能有一条传送带——一个线程只能有一个Looper
  • 既然只有一条传送带,那么自然而然地,也就只有一条货槽链。(刚刚2.3已经说过了,货物装在货槽里,每个货槽后面都有个钩子,构成了一条链)

Looper是这个车间的传送带,翻译一下就是,Looper当前线程消息循环的实际控制者。

还有一件事,既然只有一条传送带,那么怎么样最恰当地决定传送带上的货槽的顺序?那当然是按货槽预定要被处理的时间去排序咯。你想,如果我们在把货槽加入货槽链的时候,就按照预定要被处理的目标时间去排好序,那传送带是不是只管往前转就行了?传送带往前转,到达尽头的就一定是下一个要被处理的货物,预定时间一到,就可以把它取出来进行后续处理,而如果还没到预定的处理时间,我们也仅仅只需要把传送带停下,让它等在那儿,等时间到就好了,不再需要任何多余的操作了。这就是为什么message在enqueue时要按照message.when进行排序,因为这是最方便的。

Looper就先说这么多。

2.5 MessageQueue

MessageQueue,就是操纵货槽链的操纵器。

我们如果想往传送带上扔货物,之前提了,得有个按when排序的操作,如果每次都是我们亲自来进行这个操作,那就太麻烦啦,于是我们只管把货槽交给控制器,让控制器去帮我们搞定(enqueue),同理,当货槽到达传送带的尽头,需要取出来用时,也是控制器去帮我们去取(next),如果我们在货槽还没到达传送带尽头时,中途不想要它了,那也是控制器去代操作(remove)。所以说我们是需要这样一个货槽链的控制器的。

那么,因为货槽链的控制器需要能直接对货槽链去操作,所以它(MessageQueue类)自然需要能够持有货槽链(Message)的引用。

此外,一条传送带也只有一条货槽链,那也就只需要一个控制器,那么也就是,一个车间只有一条传送带、一条货槽链、一个控制器,翻译:一个Thread只有一个Looper、一个头部Message节点、一个MessageQueue。

好,MessageQueue就先说这么多,接下来就是整个Handler机制最后的一个主要角色了。

2.6 Handler

Handler,就是车间里的打工人。

有了工厂、有了车间、有了传送带、有了传送带的操作装置、有了货槽货物,还差啥啊?那当然是打工人了。

打工人(Handler)先去货槽存放处取一个空货槽(obtain),把货物包封装好,放进货槽(得到了Message),然后设置好各种参数,然后交给货槽链控制器(Handler的各类postXX、sendXX等),然后控制器负责把新的货槽添加进货槽链(messageQueue.enqueue)。与此同时传送带(Looper)也一直在运行(Looper.loopOnce),然后当传送带上有货槽到达了尽头,如果预定的处理时间没到,就暂停传送带,等时间到,而如果到了,控制器就会把这个货槽交给传送带下游的打工人(Handler),打工人取到货槽,就去作处理。而传送带则是进入下一个循环,如此往复(messageQueue.next返回,继续loopOnce)。

2.7 总结

我画了一张草图,大概描绘了一个车间内的工作,辅助理解(画的很抽象,不要介意)。

那么以上就是以传送带模型的视角讲述的Handler机制的最基本的完整的流程,现在我们已经非常清楚了。接下来我们再以传送带模型的视角来思考一些问题,或者来看看,这些所谓的面试题,会瞬间感觉非常简单、理所当然。

3 尝试随便解释几个问题

随便解释几个问题,一部分是理解上的问题(我自己编的问题),一部分是一些常见面试题。

当然你面试时可不能这么答,你说传送带面试官肯定不鸟你,还是得讲代码的,但代码就自己去看了。

Q1:同一线程内,Handler和Looper都持有同一个MessageQueue对象?

是的,这个车间里每个打工人都需要与货槽链交互(放货物、取货物),因此都会需要操纵货槽链控制器(MessageQueue),同时,Looper是传送带,它也需要操纵货槽链,因此也需要有同一个控制器。

Q2:Handler的作用仅仅是更新UI?

NoNo,格局小了,你这个工厂,不会只做UI吧?你做的可是一个完整的APP。因此,UI事件只是一种类别的货物,那肯定还有其它多种多样的货物啊,整个工厂都依赖于这套Handler机制来进行协作呢。去看AOSP,处处有Handler机制。可以说,Binder负责的是工厂之间的交互,那么工厂内不同车间的很大一部分交互,都是交给Handler机制的,所以问起Handler机制的作用,说它是用于更新UI的,未免小看它了。

Q3:Handler.send和post的区别?

send的是货槽(Message),已经封装好了的货槽,直接进行后续的入队操作就行。而post的是一个“事件”,这个“事件”就是一个抽象的货物,它被记录在货槽(Message)的callback属性上,下游打工人(Handler)收到这个货槽,一看,哦,发现它表示着一个事件要被执行了,那么接下来就是去执行这个事件。

Q4:Handler如何分发处理消息?

这就是在问下游打工人收到货槽怎么办(Handler.dispatchMessage),那自然是:

  • 如果货槽本身意味着一个待处理的事件,那就直接处理这个事件(handleCallback)
  • 但如果不是,就由打工人去处理:
    • 打工人如果自己能处理,就自己处理(mCallback.handleMessage)。
    • 如果打工人不能自己处理,则交给特定工种打工人(即Handler的子类,重写了handleMessage方法)去处理。

Q5:Handler机制如何实现线程的切换?

就是在问,一个货物,是怎么从A车间分发到B车间的。

打工人A(handlerA)在A车间(ThreadA)工作,打工人B(handlerB)在B车间(ThreadB)工作,打工人A封装了一个货槽,把货槽的目的地(Message.target)设置给打工人B,那好,当货槽到达A车间的LooperA的下游且要处理时,就会被交给B车间的打工人B(同一进程不同线程数据共享),那打工人B在车间B拿到了货槽,这不就完成了车间之间的交互了吗?也就是实现了线程的切换。

Q6:写代码时怎么创建Message?

Message是货槽,之前也分析了,那自然是要从货槽存放处去获取(obtain,更具体地,可以Handler去obtain,也可以Message.obtain),而不是我们自己去创建(new),就算要造新货槽,也是存放处的职责(obtain时sPool里没有了,则new),这样的好处就是避免了频繁的创建和销毁的开销(对象复用,以减少内存抖动),货槽就像购物推车,买完东西就放回存放点,本来就是可以复用的。

Q7:Handler机制是如何保证多线程下,MessageQueue的有序性的?

那答案肯定是加锁嘛。不过我们再用传送带模型想想,假设有两个不同车间(Thread)的打工人(Handler)都需要去往同一个车间的货槽链放东西,那就得保证先来后到嘛,因为货槽链必须是按when顺序排列的。所以就要加锁,锁谁?锁货槽链的操纵装置就行了呗,也就是synchronized(MessageQueue.this)。

Q8:为什么主线程可以直接new Handler(),子线程不行?

new Handler就是说,在这个车间(线程)新入职一个打工人,那为了不出错误,他入职这个车间的时候,就要提前先跟他说好:你主要就负责这个车间的传送带工作。并且,为了不出错,在他入职(new Handler)之前,这个传送带得先跑起来(Looper.prepare&loop)。

而主车间(主线程)的传送带在整个工厂一开工的时候就跑起来了(App进程的main调用了Looper.prepare&loop),自然之后主车间不需要再去启动了,如果有新打工人,直接开始干活就行了。

但一个新的车间(子线程)不行,它想开始传送货槽,肯定得先开传送带嘛,然后再让新打工人来干活,这样就万无一失了。

Q9:Handler所发送的Delayed消息时间一定准确吗?

基本上是准确的,但也有很少数的例外,比如,多个车间的打工人(不同线程的Handler)同时想去取同一个货槽链的货槽(同时MessageQueue.next),那总得排个先后顺序(即加锁,synchronized(MessageQueue.this)),这时,可能会因此而产生小的延迟。

Q10:主线程的Looper什么时候退出?为什么主线程Looper没消息不会ANR?

主线程Looper正常情况下不会退出。如果连你工厂的主车间都倒了,那你工厂也倒了得了。

顶多货槽链为空(没有消息)时,打工人终于可以休息一下了(休眠)。至于ANR,这里不深入展开,大概的理解就是,ANR是由于打工人没有及时处理消息(但是这个说法是很不准确的啊,只是为了大概理解一下,ANR的细节很多,往深了说可以很深,不展开了不展开了)导致的,这么看,主线程一直loop死循环,和ANR一点关系都没有,反而主线程必须一直靠死循环来运行,否则程序就退出了,所以这个问题就是很无聊的一个问题,但一直出现在面试题里。

Q11:同步屏障机制?

那就是这个货槽要被加急处理呗,说白了就是想插队,那么当一个货槽被标记为FLAG_ASYNCHRONOUS,它就可以插队(它入队的时候也是和普通的同步消息一起按时间排列的,整个消息队列中不论同步异步消息,一并按when排列)。

当我们想给异步的货槽插队时,就可以通过货槽链控制器去给一个插队指令(即MessageQueue.postSyncBarrier,这个插队指令就是同步屏障,它也是一个Message,但target是null,意味着没有目的地Handler)。

那么,在传送带不断取消息(Message.next)时,如果发现货槽链的头部是一个插队指令货槽,就会从头开始遍历整个货槽链,去寻找标记为异步的可插队货槽,优先把它取出,而非再按队列顺序了。直到这个插队指令货槽被remove掉,所以发布了插队指令,就一定要记得移除,也正因如此,发布插队指令(postSyncBarrier)的操作被标记为hide,不让一般开发者使用。

为了保证UI及时更新,那么UI更新的消息,就是个异步消息,这里不展开了,自行了解。

Q&A的这部分就先说这么多吧。

4 结语

这篇文章以传送带模型的视角来分析了Handler机制,然而,Handler机制远比这些要复杂,但这篇文章的作用本来也不是为了能让你看完就掌握Handler机制,而是抛砖引玉,让你能够清楚地对整个Handler机制有个大体的认识,这样至少不会在看源码时云里雾里。想更进一步,必须自己读源码,然后去实践。并且,越往后学,越会发现,很多东西的设计都是贯通的,Handler的设计思想,包括它的native层的思想,都是有借鉴的。

在开发中,我们除了要对Framework 中的Handler 知识点要有所了解,还有对其他知识点了解,这边耗时两个多星期时间进行精细化整理,将这《Android Framework学习笔记》 整理好了,里面记录了:有Handler、Binder、AMS、WMS、PMS、事件分发机制、UI绘制……等等,几乎把更Framework相关的知识点全都记录在册了,学习视频整理好了,由于平台限制就不展示了

《Framework 核心知识点汇总手册》:https://qr18.cn/AQpN4J

Handler 机制实现原理部分:
1.宏观理论分析与Message源码分析
2.MessageQueue的源码分析
3.Looper的源码分析
4.handler的源码分析
5.总结

Binder 原理:
1.学习Binder前必须要了解的知识点
2.ServiceManager中的Binder机制
3.系统服务的注册过程
4.ServiceManager的启动过程
5.系统服务的获取过程
6.Java Binder的初始化
7.Java Binder中系统服务的注册过程

Zygote :

  1. Android系统的启动过程及Zygote的启动过程
  2. 应用进程的启动过程

AMS源码分析 :

深知大多数程序员,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!


img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上鸿蒙开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

需要这份系统化的资料的朋友,可以戳这里获取

[外链图片转存中…(img-4GvpC3Wp-1715636342488)]
[外链图片转存中…(img-NuK58lkf-1715636342489)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上鸿蒙开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

需要这份系统化的资料的朋友,可以戳这里获取

  • 30
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值