5月28日 02:28

Android中Handler机制的工作原理是什么?

Handler是Android线程间通信的核心机制,主线程正是依靠Handler的消息循环来驱动整个应用的事件分发与UI刷新。理解Handler,关键在于搞清楚Handler、Looper、MessageQueue、Message四者如何协作,以及底层阻塞唤醒的原理。

核心组件与关系

Handler:消息的发送者与处理者。创建时绑定当前线程的Looper,通过sendMessage()post()将消息投递到MessageQueue。

Looper:消息循环引擎。每个线程最多一个Looper,通过Looper.loop()开启死循环,不断从MessageQueue取消息分发。主线程的Looper在ActivityThread.main()中由系统自动创建,子线程需手动调用Looper.prepare()Looper.loop()

MessageQueue:消息队列,按when字段(延迟时间)升序排列的单链表,并非严格FIFO。enqueueMessage()按时间插入,next()取队头消息。

Message:消息载体。what标识类型,arg1/arg2传整型,obj传对象,callback可携带Runnable。通过Message.obtain()从消息池复用,避免频繁GC。

关系总结:一个Looper对应一个MessageQueue,一个Looper可被多个Handler共享,每个Handler只能绑定一个Looper。

消息发送与分发流程

shell
发送:Handler.sendMessage/post() → Handler.enqueueMessage() 设置msg.target = this → MessageQueue.enqueueMessage() 按when插入链表 分发:Looper.loop() 死循环 → MessageQueue.next() 取消息(无消息时nativePollOnce阻塞) → msg.target.dispatchMessage(msg) 回到发送该消息的Handler → Handler.dispatchMessage() 优先级: 1. msg.callback != null → handleCallback(msg) 2. mCallback != null → mCallback.handleMessage(msg) 3. handleMessage(msg) 子类重写的方法

dispatchMessage的优先级是面试高频点:post发送的Runnable优先级最高,其次是Callback接口,最后才是handleMessage。理解这个优先级有助于排查"handleMessage不执行"的问题——很可能是post的Runnable拦截了消息。

MessageQueue的阻塞与唤醒

MessageQueue的next()方法中,当没有消息或队头消息的执行时间未到时,调用nativePollOnce()使线程进入休眠。底层实现基于Linux的epoll机制:Looper初始化时通过eventfd创建一个文件描述符,nativePollOnce()调用epoll_wait()阻塞在该fd上。当enqueueMessage()入队新消息或延迟消息到期时,nativeWake()向eventfd写入数据唤醒epoll。

这就是主线程Looper.loop()虽然是死循环却不会ANR的原因——无消息时线程休眠,有消息时才唤醒处理。ANR发生在单条消息处理超时(输入事件5秒、BroadcastReceiver 10秒、Service 20秒),而非loop()本身。

同步屏障(Sync Barrier)

MessageQueue支持同步屏障消息(target为null的Message)。当插入同步屏障后,next()会跳过所有同步消息,优先取出异步消息。系统用这个机制保证Choreographer的VSYNC信号优先处理,从而保障UI流畅绘制。具体流程:Choreographer在请求VSYNC前post一个同步屏障,VSYNC回调到来时作为异步消息插入,处理完后移除屏障。

java
// 发送同步屏障(系统API,应用层需反射调用) int token = MessageQueue.postSyncBarrier(); // 移除同步屏障 MessageQueue.removeSyncBarrier(token); // Handler构造时指定async=true可发送异步消息 Handler asyncHandler = new Handler(Looper.getMainLooper(), null, true);

面试中若能讲出同步屏障配合Choreographer保障UI绘制的完整链路,属于加分项。

IdleHandler

当MessageQueue中没有消息可处理时,会遍历执行注册的IdleHandler。适合做低优先级的初始化、预加载或资源回收。注意IdleHandler在next()中执行,若返回true则每次空闲都会重复调用。

java
Looper.myQueue().addIdleHandler(() -> { // 队列空闲时执行 return false; // false=只执行一次,true=每次空闲都执行 });

内存泄漏与修复

原因:非静态内部类Handler持有Activity引用,延迟消息在MessageQueue中存活,导致Activity无法回收。

java
// 修复方案:静态内部类 + WeakReference private static class SafeHandler extends Handler { private final 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()) return; // 处理消息 } } // Activity销毁时清除消息 @Override protected void onDestroy() { handler.removeCallbacksAndMessages(null); super.onDestroy(); }

面试追问

Q:Handler.post()和sendMessage()的区别? post()底层也是sendMessage,只是将Runnable包装成Message的callback字段。dispatchMessage时callback优先级高于handleMessage。所以如果同时post了一个Runnable又send了一个Message,Runnable会先执行。

Q:为什么主线程不会因为Looper.loop()的死循环而ANR? loop()在无消息时通过epoll休眠,不占CPU。ANR是单条消息处理超时,与循环本身无关。可以把loop()理解为"没活干就歇着,有活干才起来",ANR是"活干了太久"。

Q:一个线程可以有几个Handler?几个Looper? 多个Handler,但只有一个Looper。Handler构造时从ThreadLocal获取当前线程的Looper,重复调用Looper.prepare()会抛"Only one Looper may be created per thread"异常。

Q:Message.obtain()为什么比new Message()好? obtain()从消息池(sPool链表,最大50个)复用Message对象,避免重复创建和GC。recycleUnchecked()将用完的Message清空数据后回收到池中。高频场景下差异明显。

Q:Handler与Kotlin协程的关系? 协程的Dispatchers.Main底层通过Handler将续体(Continuation)分发到主线程。协程是更高层的并发抽象,但主线程调度仍依赖Handler机制。可以说Handler是Android线程通信的基石,协程是在此之上构建的便利API。

标签:Android