前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Handler面试八问

Handler面试八问

作者头像
码农帮派
发布2021-01-28 10:08:28
1.1K0
发布2021-01-28 10:08:28
举报
文章被收录于专栏:码农帮派

线程启动之后,进入main()方法,在main()方法中进行线程的一些初始化,初始化工作完成之后,会调用Looper.loop()进行消息监听,而loop()方法是一个死循环,从而保证线程不会立即退出:

在loop()方法中,当有消息的时候,就会从queue.next()方法中读取到最新可用消息,通过dispatch进行消息分发处理,当queue中没有可用消息的时候,整个方法就会阻塞在queue.next()方法的位置,直到继续有可用消息到来。

1. 为什么主线程不会因为Looper.loop()中的死循环卡死?

主线程是通过Looper.loop()方法进入了循环状态,因为这样主线程才不会像我们创建的一般线程那样,当可执行代码结束之后,线程的生命周期就结束退出了。

当主线程的MessageQueue中没有消息的时候,会阻塞在Messagequeue.next()方法中的nativePollOnce()方法中,此时主线程会释放CPU资源进入休眠状态,直到新消息到来的时候。所以主线程大部分时间是处于休眠状态的,并不会消耗大量的CPU资源。

这里采用的是Linux的epoll机制,通过监控文件描述符eventfd,当消息队列MessageQueue中有可执行消息的时候,同时会向eventfd中写入数据,从而唤醒主线程进行消息的分发处理。

2. post和sendMessage两个发送消息方法的区别?

post方法发送的Runnable对象,最后会被封装成一个Message对象,Runnable会被赋值给Message对象的callback变量,然后通过sendMessageAtTime()方法发送出去,在分发处理消息的时候,在dispatchMessage()方法中,要是当前Message的callback变量不为空,则会优先选择执行callback的run()方法,其实就是运行了Runnable。

而sendMessage发送的就是一个封装好的Message对象,该消息的callback一般是空的,在dispatchMessage的时候,因为callabck为空,所以分发消息的优先级比较低,是通过执行Message的target变量(target其实就是Handler对象自己)的handleMessage()方法执行任务的,所以我们在使用Handler的sendMessage()方法发送消息的时候,需要同时重些Handler的handlerMessage()方法。

3. 在构建Message对象的时候,为什么建议通过Message.obtain()方法获取Message对象?

obtain()方法可以从全局消息池中获得一个空的Message对象,这样可以有效的减少Message对象创建时消耗的系统资源。同时,通过obtain()的重载方法还可以获得一些Message的拷贝,或对Message对象进行一些初始化。

4. Handler发送延迟消息的原理是什么?

我们通过postDelayed()和sendMessageDelayed()方法来发送延迟消息,其实最终是通过方法中指定的延时时长 + 当前时间戳,计算出来该消息确定的分发时间,然后通过sendMessageAtTime()方法插入到MessageQueue中,等待到这个时间点之后才会分发处理该消息,所以延迟消息是立即发送,然后延后分发处理。

消息延迟的原理是,首先Looper在发送消息到MessageQueue的时候,会按照消息设定的分发时间先后排序放在链表中,然后通过nativePollOnce()方法让线程在休眠一段时间,等到第一个消息的处理时间到达的时候,唤醒线程处理消息,从而实现延迟消息。

5. 同步屏障SyncBarrier是什么,作用是什么?

我们一般用到Handler消息是同步消息,其实Handler有三种消息:同步消息、异步消息和屏障消息SyncBarrier,屏障消息也会被插入到MessageQueue中。同步消息和异步消息的target在传入MessageQueue的时候会保证不为空,以便与在消息分发的时候知道该消息应该分发给谁,而屏障消息的target是空的,这也是Handler中判断一个消息是否为屏障消息的标准。MessageQueue.next()方法中如果当前消息是屏障消息,则会跳过后面所有的同步消息,找到屏障消息后第一个异步消息进行分发处理。

需要注意的是开发者没有办法直接向MessageQueue中插入消息屏障(当然通过反射机制是可以的)。

在View绘制的时候,View绘制的Message是优先于其他的消息,在有View绘制消息的时候,系统会同时向MessageQueue中插入一个消息屏障,从而使该消息优先处理,避免耗时的Message阻塞UI的绘制。

6. IdleHandler是什么?有什么作用 ?

当消息队列没有可用消息的时候,线程会进入休眠状态,这种场景我们可以通过IdleHandler来监听线程进入休眠状态,具体的使用方法如下:

App性能监控LeakCanary为了避免监控程序对App运行时资源的抢占,就会利用IdleHandler来监控主线程的休眠情况,在主线程处于休眠的时候,才会跟踪监控对象的内存泄漏,这样避免LeakCanary对UI绘制这样的优先级较高的Message的影响。

7. 为什么非静态类的Handler会导致内存泄漏,如何解决?

在Activity中创建Handler,Handler会持有Activity的引用,通过Handler发送的消息Message中的target就是Handler对象,因此MessageQueue中有消息未处理的时候,MessageQueue中会持有Handler的引用,而MessageQueue的生命周期贯穿了主线程的生命周期,要比Activity的生命周期长,只要MessageQueue中还有在Activity中创建的Message的时候,那么Handler就无法被回收,从而导致Activity的引用也一直被Handler持有而Activity无法被回收,造成内存泄漏。

解决的办法就是使用静态内部类 + 弱引用的方式:

然后将Activity中创建MyHandler对象设置为static。

8. 如何在子线程中弹窗Toast

在子线程中调用Looper.prepare()方法,并调用Looper.loop()方法,这样就会在子线程中创建一个Looper对象和MessageQueue消息队列,而loop()让当前子线程开始监听消息,这样我们在子线程中显示Toast的时候,UI绘制的消息才会发送到子线程的队列中,在消息分发的时候进行UI绘制。

但是需要注意的是,任务执行完毕之后,需要手动调用Looper.quitSafely()方法退出循环,否则子线程一直不会结束退出。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-01-16,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 码农帮派 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
消息队列 CMQ 版
消息队列 CMQ 版(TDMQ for CMQ,简称 TDMQ CMQ 版)是一款分布式高可用的消息队列服务,它能够提供可靠的,基于消息的异步通信机制,能够将分布式部署的不同应用(或同一应用的不同组件)中的信息传递,存储在可靠有效的 CMQ 队列中,防止消息丢失。TDMQ CMQ 版支持多进程同时读写,收发互不干扰,无需各应用或组件始终处于运行状态。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档