AGI 的时代真的是太棒啦~
前言
在 Android 开发中,Handler、Looper、MessageQueue 构成了整个应用消息机制的核心。而主线程执行耗时任务是 Android 想要极力避免的。
接下来,我们会基于消息机制,通过接管主线程 Loop 循环来无侵入地监控 UI 卡顿
首先简单介绍消息机制的设计,如果需要详细的源码分析,可以看之前的文章 传送门
原理
Android 的消息机制本质上是一个生产者-消费者模型
Handler: 消息的生产者(发送)与最终消费者(处理)MessageQueue: 消息队列(缓冲区),内部使用单链表结构维护消息,按执行时间排序。Looper: 动力泵,不断从MessageQueue中抽取消息。Message: 载体,持有数据和处理它的Handler(target)。
核心类图
为了理清它们之间的持有关系,我们来看一下类图结构:

说明:
Looper也就是整个消息机制的"管家",它拥有唯一的MessageQueue。Handler必须绑定到一个Looper上才能工作。Message内部持有target(即Handler),这样Looper取出消息后,知道该把它交给谁去处理。- 一个线程只会对应一个
Looper对象
消息分发时序图
当我们在子线程调用 handler.sendMessage() 时,整个系统是如何运转的?

关键点:
- 入队 (
enqueueMessage):线程安全的将消息插入单链表。 - 出队 (
next):这是一个阻塞操作。如果队列为空或时间未到,利用 Linux 的epoll机制挂起线程,释放 CPU 资源。 - 分发 (
dispatchMessage):Looper拿到消息后,直接调用msg.target(即Handler) 的方法在当前线程执行。
基础内容结束,接下来开始我们的实战
实战
在性能优化中,监控主线程卡顿(ANR)是重中之重。 通常我们使用 Looper.getMainLooper().setMessageLogging(Printer) 来监控,但这种方式会产生大量的字符串拼接,对性能有微弱影响。
这里介绍一种更底层、更激进的思路:Looper 劫持
核心思路
Android 主线程本质上就是在一个死循环中运行:
java
// 原生 ActivityThread 逻辑简化
public static void main(String[] args) {
Looper.prepareMainLooper();
// ...
Looper.loop(); // <--- 死循环,这里如果不退出,APP 就在运行中
}
如果我们向主线程发送一个消息,这个消息的 Runnable 内部也是一个死循环,会发生什么?
答案是:主线程会被阻塞在这个消息里。
但是,如果我们在我们的死循环里,手动通过反射去调用 MessageQueue.next() 和 Handler.dispatchMessage(),我们就相当于在主线程原有的 Loop 里面嵌套了一个我们自己的 Loop。
这样,我们既接管了消息分发权(可以轻松计算分发耗时),又没有让主线程瘫痪。
实现代码
kotlin
import android.annotation.SuppressLint
import android.os.Handler
import android.os.Looper
import android.os.Message
import android.os.MessageQueue
import android.util.Log
import java.lang.reflect.Field
import java.lang.reflect.Method
object LooperMonitor {
private const val TAG = "LooperMonitor"
// 阈值:超过 100ms 视为卡顿
private const val BLOCK_THRESHOLD = 100L
fun startMonitor() {
val mainHandler = Handler(Looper.getMainLooper())
// 向主线程发送一个"毒丸"消息,接管 Loop
mainHandler.post {
hijackLooper()
}
}
@SuppressLint("DiscouragedPrivateApi")
private fun hijackLooper() {
Log.i(TAG, "Looper Hijack Started!")
try {
// 1. 反射获取 MessageQueue
val mainLooper = Looper.getMainLooper()
val queueField: Field = Looper::class.java.getDeclaredField("mQueue")
queueField.isAccessible = true
val messageQueue = queueField.get(mainLooper) as MessageQueue
// 2. 反射获取 MessageQueue.next() 方法
val nextMethod: Method = MessageQueue::class.java.getDeclaredMethod("next")
nextMethod.isAccessible = true
// 3. 反射获取 Message.target 字段 (Handler)
val targetField: Field = Message::class.java.getDeclaredField("target")
targetField.isAccessible = true
// 4. 反射获取 Message.recycleUnchecked() 方法 (用于回收)
// 注意:不同版本 API recycle 方法名可能不同,这里简化处理,或者直接不手动 recycle 等待系统处理
val recycleMethod = Message::class.java.getDeclaredMethod("recycleUnchecked")
recycleMethod.isAccessible = true
// 5. 开启我们自己的死循环,代替 Looper.loop()
while (true) {
// 手动调用 next(),这里可能会阻塞,就像原生的 loop() 一样
val msg = nextMethod.invoke(messageQueue) as? Message ?: return
val handler = targetField.get(msg) as? Handler
if (handler == null) {
// 没有 target 的消息通常意味着退出 loop,但在主线程一般不会遇到
return
}
// --- 监控开始 ---
val startTime = System.currentTimeMillis()
// 执行分发
handler.dispatchMessage(msg)
// --- 监控结束 ---
val endTime = System.currentTimeMillis()
val cost = endTime - startTime
if (cost > BLOCK_THRESHOLD) {
Log.w(TAG, "UI 卡顿检测: 耗时 ${cost}ms | Target: ${handler.javaClass.name}")
// 此时可以抓取堆栈信息:Thread.currentThread().stackTrace
}
// 回收消息 (模拟 Looper.loop 的行为)
recycleMethod.invoke(msg)
}
} catch (e: Exception) {
Log.e(TAG, "Hijack failed", e)
// 如果出错了,最好抛出异常或者恢复现场,否则 App 可能 ANR
}
}
}
正常情况下,我们可以从日志中看到
log
2025-11-24 21:43:20.402 7774-7774 LooperMonitor com.hualee.myapplication I Looper Hijack Started!
2025-11-24 21:43:20.511 7774-7774 LooperMonitor com.hualee.myapplication W UI 卡顿检测: 耗时 108ms | Target: android.app.ActivityThread$H
2025-11-24 21:43:20.733 7774-7774 LooperMonitor com.hualee.myapplication W UI 卡顿检测: 耗时 222ms | Target: android.view.Choreographer$FrameHandler