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,Handler → Activity,导致 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,拦截其后所有同步消息 ,只允许异步消息通过。

使用场景:

  • ViewRootImpl 在 scheduleTraversals() 时插入屏障

  • 确保 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 取出消息,ViewRootImpl 的 WindowInputEventReceiver 处理

  4. 最终分发到 DecorView → ViewGroup → View 的 dispatchTouchEvent()

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


四、面试速记口诀

问题 一句话答案
Handler 发消息去哪? MessageQueue(链表)
Looper 干嘛的? 死循环取消息,交给 Handler
为什么不会卡死? native epoll 阻塞,有消息才唤醒
延迟消息怎么实现? 提前入队,按时间排序,到点才处理
内存泄漏怎么解? 静态内部类 + WeakReference + removeCallbacks
子线程用 Handler? 先 Looper.prepare() + Looper.loop()
相关推荐
千里马学框架4 天前
一起学 Android 14:ShellTransition 屏幕旋转过程深度剖析
android·智能手机·性能优化·framework·性能·屏幕旋转·rotation
美狐美颜SDK开放平台4 天前
开发直播APP时如何接入视频美颜SDK?开发流程与注意事项
android·人工智能·计算机视觉·音视频·直播美颜sdk
AFinalStone4 天前
Android7 SystemUI源码解析(七)Keyguard锁屏模块深度解析
android·systemui
致远ccc4 天前
Google Play 上架前如何测试 App?多国家 Android 环境测试
android·app测试·googleplay·多国家应用测试
ttyyttemo4 天前
Kotlin 协程中的 Job 结构化并发与取消
android
sun0077004 天前
tbox 4g/5g切换,导致wan ip 改变,导致车机旧网络不可用。需要重启车机才行
android
ai2work4 天前
ch23 综合复刻:从零做一个最小可用版本(capstone)
kotlin
其实防守也摸鱼4 天前
内网穿透与反向代理:原理、工具与实战指南
android·大数据·运维·安全·网络安全·自动化·渗透
ai2work4 天前
ch21 签名、校验与发版
kotlin
AFinalStone4 天前
Android7 SystemUI 源码解析(四)NavigationBar 导航栏与 SystemBars
android·systemui