Handler面试题
-
一个线程有几个Handler?
一个线程比如主线程,我们可以在主线程中创建多个Handler;所以一个线程中是可以包含任意多个Handler的. -
线程间通信的原理是怎么样的?
通常情况下我们会在子线程中,通过调用handler.sendMessage()方法发送消息;然后在主线程中Handler的回调方法handleMessage()中去处理消息;从而实现子线程与主线程通信.
分析线程间通信的流程:子线程:通过Handler发送消息Message到MessageQueue消息队列中
handler.sendMessage()- ->sendMessageDelayed
- ->sendMessageAtTime()
- ->enqueueMessage
- ->(MessageQueue)
queue.enqueueMessage- 将发送的消息添加到消息队列MessageQueue中,这里会去遍历之前的消息Message,并把心来的消息插进去.消息的实现方式是单链表,每个消息对象中会包含一个next变量存储下一个消息,消息之间是按照执行时间排序的,最早执行的消息排在最前面.
主线程:通过Looper从MessageQueue中取出消息并处理
ActivityThread.main()- 这个main()方法中会调用
Looper.prepareMainLooper()方法,创建sMainLooper对象;然后调用调用Looper.loop()方法开启死循环,从消息队列中取消息分发给Hanlder处理.
- 这个main()方法中会调用
Looper.loop()- 通过(Looper)me.mQueue获取到消息队列
- ->(MessageQueue)
queue.next()- 通过一个死循环不停地从消息队列中取消息
- ->msg.target.dispatchMessage(msg)
- 注意:msg.target是Message中的target变量,是Handler对象的变量,所以这里其实是调用的Handler中的dispatchMessage()方法
- ->(Handler)
dispatchMessage()- 调用Hanler中的分发消息方法dispatchMessage()
- ->
handleMessage(msg)- 最终回调我们Activity/Fragment中创建的Handler中的handleMessage方法,我们在这里处理消息.
总结: (
线程间通信的原理就是内存共享)
由于线程间内存共享的机制,使得共享的Message消息对象可以通过子线程添加到MessageQueue消息队列中,接着主线程可以取出共享的MessageQueue队列中的Message对象进行处理.
举例:
Activity中创建的成员变量,我们在主线程和子线程中都可以去访问和修改这个成员变量,这就是线程间内存共享的案例. -
Handler内存泄漏的原因?为什么其他内部类没有这个问题?
- 解决方案:
- 首先我们可以通过将
Handler声明称静态的static,因为静态内部类不会持有外部类的引用,从而页面可以正常销毁. - 在页面销毁的时候,调用
removeCallbacksAndMessages(null)方法,这个方法会回收Hanlder持有的消息队列MessageQueue中的所有消息Message;该方法会遍历消息单链表,依次调用每一个消息的Message.recycleUnchecked()方法进行消息回收.从而避免延迟消息也页面销毁时未执行导致内存泄漏.
- 首先我们可以通过将
- 问题分析:
MainActivity- 创建内部类Handler,并重写handleMessage()方法
Handler mHandler = new Handler() { @Override public void handleMessage(@NonNull Message msg) { super.handleMessage(msg); } };
- 创建内部类Handler,并重写handleMessage()方法
<-Handler- 非静态内部类Handler会持有外部类MainActivity的引用
<-Message- 发送的消息Message会持有Handler类.在发送消的过程中,Handler中的enqueueMessage()方法里会有如下一段代码:
msg.target = this;
Message就会持有this,这个this就是Handler类.
- 发送的消息Message会持有Handler类.在发送消的过程中,Handler中的enqueueMessage()方法里会有如下一段代码:
<-MessageQueue- 消息会添加到消息队列MessageQueue中,将消息添加到消息队列中的mMessages单链表中
<-LooperLooper会调用prepareMainLooper()方法中的prepare()方法,将创建的Looper对象设置到ThreadLocal中去,代码如下:
sThreadLocal.set(new Looper(quitAllowed));
同时会执行如下代码,将ThreadLocal中存储的Looper对象赋值给一个静态变量sMainLooper,该变量的声明如下:
private static Looper sMainLooper;
这里sMainLooper是一个静态变量,他是GC Root对象,会持有所在类Looper,从而导致了内存泄漏.
在使用Looper的loop()方法时,会调用MessageQueue的next()方法,从消息队列中取出消息进行进行处理,从而因为(Looper)sMainLooper是一个静态变量,是GC Root对象,无法被JVM回收,存在导致内存泄漏的风险.- 当
Looper调用loop()方法,通过一个死循环从消息队列中取出消息分发给Hanlder去处理时,当消息被处理掉则不会导致内存泄漏.
总结:
由于页面在销毁的过程中,Looper类中的sMainLooper是静态变量,而静态变量是GC Root对象,无法被JVM回收,所以导致了Looper无法被回收,从而导致向上引用的MessageQueue、Message、Handler和MainActivity都无法被回收,从而导致了内存泄漏.
当我们将MainActivity中的Handler声明成static,内部类Handler就不会持有外部类MainActivity的应用,从而MainActivity销毁时可以被JVM正常回收;
当我们在MainActivity销毁的时候调用removeCallbacksAndMessages(null)方法,可以回收所有的消息Message对象,从而Message就不会持有Handler的引用,从而Message、Handler和Activity都可以正常被回收. - 解决方案:
-
为什么主线程可以new Handler?如果要在子线程中new Handler要做什么准备?
- 主线程之所以能创建Handler:
是因为Activity在启动的时候,会执行ActivityThread中的main()方法,这个方法中会执行这两行代码Looper.prepareMainLooper()和Loop.loop(),他们的作用分别是创建Looper对象,这个
- 主线程之所以能创建Handler:

本文详细介绍了Android中Handler、Looper和MessageQueue在线程间通信的角色和工作流程,分析了线程间通信的原理以及可能导致内存泄漏的情况。同时,讨论了Handler内存泄漏的原因和解决方案,包括使用静态Handler和在Activity销毁时清理消息。此外,文章还涵盖了在子线程中创建Handler的方法、HandlerThread的使用,以及IdleHandler的概念。最后,提到了消息的同步和异步特性,以及屏障消息的特殊性。

2634

被折叠的 条评论
为什么被折叠?



