Handler的实现原理

本文深入解析了Android中Handler机制的工作原理,包括消息的发送、处理流程,以及如何在子线程中正确创建Handler。探讨了Handler、Message、MessageQueue和Looper之间的关系,并提供了线程安全的实现方式。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、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()sThreaLocalkeyLoopervalue保存到主线程中,而我们新建主线程Handler的时候,Handler的构造函数会调用Looper.myLoopersThreadLocal中get出一个Looper,因为为同一个线程,而sThreadLocal又是同一个变量,自然能正确的获取到在ActivityThread.java中初始化好的全局唯一的Looper对象了,而在希望创建一个跑在其他线程的Handler的时候,我们并没有事先将这个线程的Looper给保存到当前线程中中,所以需要调用Looper.prepare来初始化。

为什么主线程不用调用 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子线程到主线程通信原理

HandlersendMessage() 方法之所以能将子线程的消息发送到主线程处理,核心原理是通过 线程间共享的 MessageQueue 和 事件循环(Looper) 实现的。

原理:如下图所示,子线程消息通过enqueueMessage()方法入队(MessageQueue),MessageQueue存放到了共享内存空间,主线程通过Looper.loop()方法进行轮询取消息。
在这里插入图片描述

五、重点:handler内存泄漏原因是什么?

handler调用底层framework层链路:

static Looper(类的静态变量)-> sMainLooper-> MessageQueue->msg-> Handler->Activity

主线程的 MessageQueueHandlerActivity(无法回收)

问题根源:

  • 非静态内部类隐式持有外部类的引用: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 或逻辑
            }
        }
    }
}

关键点:

  1. 切断隐式引用static 修饰的 Handler 是静态内部类,不再隐式持有外部类(Activity)的实例。
  2. 使用弱引用WeakReference):即使 Handler 需要访问 Activity,也通过弱引用持有,避免形成强引用链。
  3. 消息队列的关联:即使主线程的 MessageQueue 持有 Handler,由于 Handler 不再强引用 ActivityActivity 销毁后可以被回收。

修复后引用链:

主线程的 MessageQueue → StaticHandler →(弱引用)→ Activity(可回收)

注意事项:

  • 必须配合弱引用:即使 Handlerstatic,如果直接强引用 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)、线程栈中的局部变量等。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值