Handler、View 绘制与事件分发
核心原理讲解
Handler/Looper/MessageQueue 是怎么协作的?
你可以把 Android 主线程想象成一个永不停歇的流水线工人。Looper 是这个工人的循环节奏——它不断地从 MessageQueue(任务队列)里取出 Message,交给对应的 Handler 去处理。

关键问题是:Looper.loop() 是个死循环,为什么不会卡死主线程? 这里的核心在于 MessageQueue 底层用的是 Linux 的 epoll 机制。当队列里没消息时,主线程并不是在空转消耗 CPU,而是挂起等待(阻塞在 nativePollOnce → epoll_wait)。一旦有新消息进来(比如用户触摸屏幕、系统回调),写端写入数据唤醒 epoll,Looper 就继续取消息处理。所以”死循环”只是逻辑上的循环,实际空闲时 CPU 占用为零。
View 的 measure / layout / draw
三步走,层层递进:

- measure:从根 View 开始自顶向下递归。父 View 通过
MeasureSpec(= 测量模式 + 尺寸上限)告诉子 View “你最多能有多大”。三种模式:EXACTLY(你就是这么大)、AT_MOST(别超过这个值)、UNSPECIFIED(随便)。子 View 结合自身的LayoutParams算出自己的宽高。 - layout:在已知宽高的基础上,父 View 为每个子 View 分配具体位置(左上右下四个坐标 L, T, R, B)。
- draw:按顺序画——背景 → 自身内容 → 子 View → 装饰(如滚动条)。
一个实际的认知要点:measure 可能被调用多次。比如 LinearLayout 设了 weight,它会先测一遍子 View 原始尺寸,再按权重比例重新测一遍,所以复杂布局里 measure 调用次数会指数增长。
事件分发的三板斧
触摸事件从 Activity 一路往下传,核心就三个方法:
dispatchTouchEvent():负责”分发”,每一层都有,决定事件往哪走onInterceptTouchEvent():ViewGroup 独有,决定”要不要在这层截胡”onTouchEvent():负责”消费”,真正处理触摸事件
分发链路:Activity → Window → DecorView → ViewGroup → ... → 目标 View

核心规则:如果某个 View 在 ACTION_DOWN 时 onTouchEvent 返回了 false(不消费),那后续的 MOVE、UP 就不会再给它了——系统会直接把事件回传给它的父容器处理。
处理嵌套滑动冲突有两种经典方案:
- 外部拦截法:在父容器的
onInterceptTouchEvent里判断滑动方向,决定是否拦截 - 内部拦截法:子 View 调用
parent.requestDisallowInterceptTouchEvent(true)先抢事件,再按需放手

工程注意事项
缺口 1:IdleHandler 是什么?什么场景用?
另一个容易忽略的机制是 MessageQueue.IdleHandler。它是一个回调,在 MessageQueue 空闲时(没有待处理消息时)被触发。常见用途是在页面首帧渲染完成后再执行延迟初始化任务,比如预加载数据、上报埋点等。它也适合用于理解消息队列空闲时机。
缺口 2:同步屏障(SyncBarrier)
ViewRootImpl 在安排 View 绘制时,会往 MessageQueue 里插入一个同步屏障消息(target 为 null 的 Message)。插入后,Looper 取消息时会跳过所有普通同步消息,只处理异步消息。而 View 绘制的 scheduleTraversals 发出的就是异步消息,这保证了绘制任务的优先级高于其他普通 Handler 消息。绘制完成后屏障会被移除。
缺口 3:Compose 的测量模型差异
如果被问到 Compose,不需要深入但要能说出核心区别:Compose 的测量只进行一次(Single Pass),不允许子组件被重复测量。它用 Constraints 替代 MeasureSpec 向下传递约束,子组件返回 Placeable 向上汇报尺寸。这避免了传统 View 树在 LinearLayout + weight 等场景下多次 measure 的性能问题。