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就是发送该消息的 HandlerQ2一个线程可以有几个 Handler几个 Looper数量原因Looper1 个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 的本质是消息处理超时不是死循环本身。深入解析loop()中的queue.next()无消息时会通过native 层的epoll_wait阻塞线程释放 CPU有消息来时native 层被唤醒继续执行ANR 发生在dispatchMessage()里处理消息耗时过长如 5s 内未响应输入所以死循环 阻塞 事件驱动模型这是高性能的Q4MessageQueue 的数据结构是什么答案单向链表按when时间戳排序java// Message.java Message next; // 链表指针 long when; // 触发时间为什么不用数组/堆不需要随机访问只取头部 → 链表 O(1)插入按时间排序数量通常不多O(n) 可接受内存更紧凑且支持对象池复用二、进阶深挖Q5Handler 的sendMessageDelayed()是怎么实现延迟的核心原理不是真的延迟发送而是提前发送按时间排队。流程计算when SystemClock.uptimeMillis() delayMillisenqueueMessage()按when插入链表合适位置Looper循环到该消息时检查when是否到达未到计算剩余时间通过nativenativePollOnce(ptr, timeout)阻塞等待已到取出处理Q6Handler 内存泄漏的原因如何解决原因非静态内部类Handler持有外部Activity的引用延迟消息如postDelayed在 Activity 销毁时仍在 MessageQueue 中Message 持有 HandlerHandler→Activity导致 Activity 无法被回收解决方案3种java// 方案1静态内部类 WeakReference private static class SafeHandler extends Handler { private WeakReferenceActivity ref; SafeHandler(Activity activity) { ref new WeakReference(activity); } Override public void handleMessage(Message msg) { Activity activity ref.get(); if (activity ! null !activity.isFinishing()) { // 处理消息 } } } // 方案2Activity.onDestroy() 时移除消息 Override protected void onDestroy() { super.onDestroy(); handler.removeCallbacksAndMessages(null); } // 方案3使用 Lifecycle 组件自动管理Q7Message.obtain() 和 new Message() 有什么区别答案obtain()从消息对象池中复用new会创建新对象。源码javapublic 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 系统大量消息传递场景如触摸事件、绘制都用这个池。Q8post(Runnable)和sendMessage(Message)有什么区别post(Runnable)sendMessage(Message)本质把 Runnable 赋值给msg.callback直接发送 Message处理优先级高dispatchMessage先检查 callback低使用场景简单任务需要携带复杂数据源码javapublic 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; }Q9IdleHandler 是什么使用场景定义当 MessageQueue 空闲无消息或消息未到触发时间时执行的回调。使用javaLooper.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如何在子线程中创建 Handlerjavanew Thread(() - { // 子线程默认没有 Looper必须手动创建 Looper.prepare(); Handler handler new Handler(Looper.myLooper()) { Override public void handleMessage(Message msg) { // 在子线程处理 } }; Looper.loop(); // 阻塞开始消息循环 // 退出时子线程可以退出 // Looper.myLooper().quitSafely(); }).start();更推荐使用HandlerThreadjavaHandlerThread thread new HandlerThread(bg); thread.start(); Handler handler new Handler(thread.getLooper());Q12Looper.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 不能为 nulljavapublic Handler(Looper looper) { this(looper, null, false); }原因Handler 必须关联一个 MessageQueue 才能发消息MessageQueue 来自 Looper如果当前线程没有调用Looper.prepare()Looper.myLooper()返回 null直接抛异常避免 NPE 或消息丢失Q15触摸事件是怎么通过 Handler 机制传递到 View 的流程InputDispatchernative收到触摸事件通过JNI将事件封装成Message发送到主线程的MessageQueueLooper取出消息ViewRootImpl的WindowInputEventReceiver处理最终分发到DecorView→ViewGroup→View的dispatchTouchEvent()这解释了为什么在主线程执行耗时操作会导致触摸无响应——消息被堵在队列里。四、面试速记口诀问题一句话答案Handler 发消息去哪MessageQueue链表Looper 干嘛的死循环取消息交给 Handler为什么不会卡死native epoll 阻塞有消息才唤醒延迟消息怎么实现提前入队按时间排序到点才处理内存泄漏怎么解静态内部类 WeakReference removeCallbacks子线程用 Handler先Looper.prepare()Looper.loop()