一、handler消息处理过程
流程图
处理过程:
1)从handler中获取一个消息对象,把数据封装到消息对象中,通过handler的send…方法把消息push到MessageQueue队列中。
2)Looper对象会轮询MessageQueue队列,把消息对象取出。
3)通过dispatchMessage分发给Handler,再回到调用Handler实现的handleMessage方法处理消息。
二、如何在子线程创建handler
说明:为何不能直接在子线程中创建handler
解释:在应用App启动的时候,会在执行程序的入口创建一个Looper对象:Looper.prepareMainLooper(),然后Looper.loop();完成Looper对象的创建。实际上Looper.prepareMainLooper()方法还是调用了Looper的prepare()方法完成Looper对象的创建。因此在主线程中通过关键字new创建的Handler对象之前,Looper对象已经存在并始终存在。
1、第一种
在handler前加 Looper.prepare();后加Looper.loop();
new Thread(new Runnable() {
@Override
public void run() {
Looper.prepare();//Looper初始化
//Handler初始化 需要注意, Handler初始化传入Looper对象是子线程中缓存的Looper对象
mHandler = new Handler(Looper.myLooper());
Looper.loop();//死循环
//注意: Looper.loop()之后的位置代码在Looper退出之前不会执行,(并非永远不执行)
}
}).start();
为什么主线程不用调用 Looper.prepare()
深层原因:
在app启动时,系统会调用Looper.prepareMainLooper() 以 sThreaLocal为key,Looper为value保存到主线程中,而我们新建主线程Handler的时候,Handler的构造函数会调用Looper.myLooper从sThreadLocal中get出一个Looper,因为为同一个线程,而sThreadLocal又是同一个变量,自然能正确的获取到在ActivityThread.java中初始化好的全局唯一的Looper对象了,而在希望创建一个跑在其他线程的Handler的时候,我们并没有事先将这个线程的Looper给保存到当前线程中中,所以需要调用Looper.prepare来初始化。
三、handler没有消息处理时会阻塞吗,会导致ANR吗
说明:
Handler 的底层机制基于 Looper 的消息循环(Message Loop),其核心是 阻塞式等待消息,但通过 Linux 的epoll 机制实现了高效的线程休眠和唤醒。这种设计既不会浪费 CPU 资源,又能避免 ANR。
Handler 消息处理机制的本质
1. 消息循环(Message Loop)的阻塞原理
Looper.loop() 源码:
public static void loop() {
final Looper me = myLooper();
final MessageQueue queue = me.mQueue;
for (;;) { // 无限循环
Message msg = queue.next(); // 阻塞式获取消息
if (msg == null) return; // 只有 Looper 被退出时返回 null
msg.target.dispatchMessage(msg); // 分发消息给 Handler
msg.recycleUnchecked();
}
}
MessageQueue.next() 源码:
Message next() {
for (;;) {
nativePollOnce(ptr, nextPollTimeoutMillis); // 通过 epoll 让线程休眠
// ... 从队列中取消息
}
}
2. 关键结论
1)阻塞式等待:当没有消息时,nativePollOnce() 会让线程进入休眠状态(底层使用 epoll 机制),释放 CPU 资源。
2)非忙等待:不同于 while (true) 空转,休眠期间线程不消耗 CPU。
3)唤醒机制:当有消息入队时,通过 nativeWake() 唤醒线程(类似线程的 notify())。
为什么不会导致 ANR?
1. ANR 的触发条件
ANR(Application Not Responding)发生的根本原因是 主线程(UI 线程)在规定时间内未完成输入事件、BroadcastReceiver 或 Service 的生命周期回调。例如:
- 输入事件:5 秒内未处理完成。
- BroadcastReceiver:10 秒内未处理完成。
2. Handler 的“阻塞”与 ANR 的关系
-
主线程的“阻塞”是正常的:
主线程的 Looper 在没有消息时处于休眠状态,但 一旦有新的消息(如点击事件),会立即被唤醒并处理。这种“阻塞”不会影响主线程的响应能力。 -
ANR 的真正原因:
当某个消息的处理时间过长(例如在主线程执行耗时操作),导致后续消息(如输入事件)无法及时处理,才会触发 ANR。
四、handler子线程到主线程通信原理
Handler 的 sendMessage() 方法之所以能将子线程的消息发送到主线程处理,核心原理是通过 线程间共享的 MessageQueue 和 事件循环(Looper) 实现的。
原理:如下图所示,子线程消息通过enqueueMessage()方法入队(MessageQueue),MessageQueue存放到了共享内存空间,主线程通过Looper.loop()方法进行轮询取消息。
五、重点:handler内存泄漏原因是什么?
handler调用底层framework层链路:
static Looper(类的静态变量)-> sMainLooper-> MessageQueue->msg-> Handler->Activity
或
主线程的 MessageQueue → Handler → Activity(无法回收)
问题根源:
- 非静态内部类隐式持有外部类的引用:Java 中,非静态内部类(如 Handler)会隐式持有外部类(MyActivity)的实例。
- 消息队列的延迟:当通过 Handler 发送延迟消息(如 postDelayed())时,Handler 会关联到主线程的 MessageQueue。如果 Activity 在销毁前未及时移除消息,MessageQueue 会持有 Handler 的引用 → Handler 持有 Activity 的引用 → Activity 无法被垃圾回收(即使它已被销毁)。
为什么static Handler能避免内存泄漏
public class MyActivity extends Activity {
private static class StaticHandler extends Handler {
// 使用 WeakReference 避免强引用 Activity
private WeakReference<MyActivity> mActivityRef;
public StaticHandler(MyActivity activity) {
mActivityRef = new WeakReference<>(activity);
}
@Override
public void handleMessage(Message msg) {
MyActivity activity = mActivityRef.get();
if (activity != null) {
// 安全操作 Activity 的 UI 或逻辑
}
}
}
}
关键点:
- 切断隐式引用:static 修饰的 Handler 是静态内部类,不再隐式持有外部类(Activity)的实例。
- 使用弱引用(WeakReference):即使 Handler 需要访问 Activity,也通过弱引用持有,避免形成强引用链。
- 消息队列的关联:即使主线程的 MessageQueue 持有 Handler,由于 Handler 不再强引用 Activity,Activity 销毁后可以被回收。
修复后引用链:
主线程的 MessageQueue → StaticHandler →(弱引用)→ Activity(可回收)
注意事项:
- 必须配合弱引用:即使 Handler 是 static,如果直接强引用 Activity(如 public StaticHandler(MyActivity activity) 中直接保存 activity),依然会导致泄漏。
- 及时移除消息:在 Activity 销毁时(如 onDestroy()),调用 handler.removeCallbacksAndMessages(null) 移除所有未处理的消息,避免 Handler 被消息队列长期持有。
四类对象可以作为GC Root :
- 虚拟机栈中的局部变量
当前正在执行的方法中的局部变量引用的对象
void myMethod() {
Object obj = new Object(); // obj 是局部变量,作为 GC Root
}
- 方法区中的静态变量
类的静态变量(static修饰)引用的对象
class MyClass {
static Object staticObj = new Object(); // staticObj 是 GC Root
}
-
本地方法栈中的JNI引用
——通过 Java 本地接口(JNI)调用的本地代码(如 C/C++)引用的对象。 -
活跃线程相关对象
—— 正在运行的线程对象(Thread)、线程栈中的局部变量等。