android Handler , Looper , Message , MessageQueue 详解

一、基础

Q1:请简述 Handler 机制的工作原理

核心回答:

Handler 发送消息 → MessageQueue 按时间排序存储 → Looper 无限循环取出 → 回调 Handler 处理。

细节:

  • MessageQueue 不是队列,是单向链表 ,按 when(触发时间戳)排序

  • Looper.loop()queue.next() 无消息时通过 native epoll 阻塞,不消耗 CPU

  • 最终 msg.target.dispatchMessage(msg)target 就是发送该消息的 Handler


Q2:一个线程可以有几个 Handler?几个 Looper?

数量 原因
Looper 1 个 ThreadLocal 存储,prepare() 时检查重复,重复会抛异常
Handler 多个 多个 Handler 可以共享同一个 Looper 和 MessageQueue

源码验证:

java

复制代码
// Looper.prepare()
if (sThreadLocal.get() != null) {
    throw new RuntimeException("Only one Looper may be created per thread");
}

Q3:为什么主线程的 Looper.loop() 死循环不会导致 ANR?

核心要点:

ANR 的本质是消息处理超时,不是死循环本身。

深入解析:

  1. loop() 中的 queue.next() 无消息时,会通过 native 层的 epoll_wait 阻塞线程,释放 CPU

  2. 有消息来时,native 层被唤醒,继续执行

  3. ANR 发生在 dispatchMessage() 里处理消息耗时过长(如 5s 内未响应输入)

  4. 所以死循环 + 阻塞 = 事件驱动模型,这是高性能的


Q4:MessageQueue 的数据结构是什么?

答案:单向链表 (按 when 时间戳排序)

java

复制代码
// Message.java
Message next;  // 链表指针
long when;     // 触发时间

为什么不用数组/堆?

  • 不需要随机访问,只取头部 → 链表 O(1)

  • 插入按时间排序,数量通常不多,O(n) 可接受

  • 内存更紧凑,且支持对象池复用


二、进阶深挖

Q5:Handler 的 sendMessageDelayed() 是怎么实现延迟的?

核心原理:

不是真的延迟发送,而是提前发送,按时间排队

流程:

  1. 计算 when = SystemClock.uptimeMillis() + delayMillis

  2. enqueueMessage()when 插入链表合适位置

  3. Looper 循环到该消息时,检查 when 是否到达:

    • 未到:计算剩余时间,通过 native nativePollOnce(ptr, timeout) 阻塞等待

    • 已到:取出处理


Q6:Handler 内存泄漏的原因?如何解决?

原因:

  • 非静态内部类 Handler 持有外部 Activity 的引用

  • 延迟消息(如 postDelayed)在 Activity 销毁时仍在 MessageQueue 中

  • Message 持有 Handler,HandlerActivity,导致 Activity 无法被回收

解决方案(3种):

java

复制代码
// 方案1:静态内部类 + WeakReference
private static class SafeHandler extends Handler {
    private WeakReference<Activity> ref;
    SafeHandler(Activity activity) {
        ref = new WeakReference<>(activity);
    }
    @Override
    public void handleMessage(Message msg) {
        Activity activity = ref.get();
        if (activity != null && !activity.isFinishing()) {
            // 处理消息
        }
    }
}

// 方案2:Activity.onDestroy() 时移除消息
@Override
protected void onDestroy() {
    super.onDestroy();
    handler.removeCallbacksAndMessages(null);
}

// 方案3:使用 Lifecycle 组件自动管理

Q7:Message.obtain() 和 new Message() 有什么区别?

答案:

obtain()消息对象池 中复用,new 会创建新对象。

源码:

java

复制代码
public static Message obtain() {
    synchronized (sPoolSync) {
        if (sPool != null) {
            Message m = sPool;
            sPool = m.next;
            m.next = null;
            m.flags = 0;
            sPoolSize--;
            return m;
        }
    }
    return new Message();  // 池空时才 new
}

好处: 避免频繁 GC,提升性能。Android 系统大量消息传递场景(如触摸事件、绘制)都用这个池。


Q8:post(Runnable)sendMessage(Message) 有什么区别?

post(Runnable) sendMessage(Message)
本质 把 Runnable 赋值给 msg.callback 直接发送 Message
处理优先级 高(dispatchMessage 先检查 callback)
使用场景 简单任务 需要携带复杂数据

源码:

java

复制代码
public final boolean post(Runnable r) {
    return sendMessageDelayed(getPostMessage(r), 0);
}

private static Message getPostMessage(Runnable r) {
    Message m = Message.obtain();
    m.callback = r;  // Runnable 被包装进 callback
    return m;
}

Q9:IdleHandler 是什么?使用场景?

定义:

当 MessageQueue 空闲(无消息或消息未到触发时间)时执行的回调。

使用:

java

复制代码
Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() {
    @Override
    public boolean queueIdle() {
        // 执行低优先级任务
        return false;  // true=保留,false=执行完移除
    }
});

经典场景:

  • 启动优化:主线程空闲时加载非关键资源

  • 延迟初始化 :如 LeakCanary 的初始化

  • UI 收尾 :如 RecyclerView 预加载


Q10:什么是同步屏障(Sync Barrier)?

原理:

插入一个 target == null 的特殊 Message,拦截其后所有同步消息 ,只允许异步消息通过。

使用场景:

  • ViewRootImplscheduleTraversals() 时插入屏障

  • 确保 Choreographer 的 VSync 异步消息优先执行,避免掉帧

注意: 同步屏障是 @hide API,应用层不能直接调用。


三、实战场景题

Q11:如何在子线程中创建 Handler?

java

复制代码
new Thread(() -> {
    // 子线程默认没有 Looper,必须手动创建
    Looper.prepare();
    
    Handler handler = new Handler(Looper.myLooper()) {
        @Override
        public void handleMessage(Message msg) {
            // 在子线程处理
        }
    };
    
    Looper.loop();  // 阻塞,开始消息循环
    
    // 退出时(子线程可以退出)
    // Looper.myLooper().quitSafely();
}).start();

更推荐使用 HandlerThread

java

复制代码
HandlerThread thread = new HandlerThread("bg");
thread.start();

Handler handler = new Handler(thread.getLooper());

Q12:Looper.quit()quitSafely() 的区别?

quit() quitSafely()
行为 立即退出,清空 MessageQueue 设置退出标记,处理完已有消息后退出
延迟消息 直接丢弃 丢弃(只保证当前已到达的消息)
使用 子线程 Looper 子线程 Looper

主线程 Looper 不允许 quit,会抛异常。


Q13:主线程的 Looper 是什么时候创建的?

答案: ActivityThread.main() 中。

java

复制代码
// ActivityThread.java
public static void main(String[] args) {
    Looper.prepareMainLooper();  // 创建主线程 Looper
    
    ActivityThread thread = new ActivityThread();
    thread.attach(false);
    
    Looper.loop();  // 开始循环,永不退出
}

Q14:为什么 Handler 构造方法里要求 Looper 不能为 null?

java

复制代码
public Handler(Looper looper) {
    this(looper, null, false);
}

原因:

  • Handler 必须关联一个 MessageQueue 才能发消息

  • MessageQueue 来自 Looper

  • 如果当前线程没有调用 Looper.prepare()Looper.myLooper() 返回 null

  • 直接抛异常,避免 NPE 或消息丢失


Q15:触摸事件是怎么通过 Handler 机制传递到 View 的?

流程:

  1. InputDispatcher(native)收到触摸事件

  2. 通过 JNI 将事件封装成 Message,发送到主线程的 MessageQueue

  3. Looper 取出消息,ViewRootImplWindowInputEventReceiver 处理

  4. 最终分发到 DecorViewViewGroupViewdispatchTouchEvent()

这解释了为什么在主线程执行耗时操作会导致触摸无响应------消息被堵在队列里。


四、面试速记口诀

问题 一句话答案
Handler 发消息去哪? MessageQueue(链表)
Looper 干嘛的? 死循环取消息,交给 Handler
为什么不会卡死? native epoll 阻塞,有消息才唤醒
延迟消息怎么实现? 提前入队,按时间排序,到点才处理
内存泄漏怎么解? 静态内部类 + WeakReference + removeCallbacks
子线程用 Handler? Looper.prepare() + Looper.loop()
相关推荐
Android-Flutter1 小时前
Android 内存泄漏详解
android·kotlin
雨声不在2 小时前
docker-android 重启后镜像起不来
android·docker·容器
阿pin2 小时前
Android随笔-Framework
android·framework
达达尼昂3 小时前
Flutter AI Harness 如何让 Agent 参与软件开发全流程
android·人工智能·后端
sRect3 小时前
用ESP32-S3开发板开发一个接收消息的BB机
android·serverless·嵌入式
AFinalStone4 小时前
Android 7系统无障碍服务(二)AccessibilityManagerService 启动与初始化
android·无障碍服务
小孔龙4 小时前
BufferQueue 多缓冲与背压
android
阿pin4 小时前
Android随笔-kotlin Flow
android·kotlin·flow
阳光予你4 小时前
App 启动流程:从 Launcher 点击到 Activity 创建
android