Handler工作流程

3 阅读27分钟

Handler工作流程

📌 面试重要度:⭐⭐⭐⭐⭐

考察频率:字节 98% | 阿里 95% | 腾讯 96%

一、核心概念

1.1 定义与作用

一句话定义: Handler 工作流程是指从消息发送到最终处理的完整过程,包括消息入队、Looper 循环取消息、消息分发、消息处理四个核心阶段,通过 Handler、Message、MessageQueue、Looper 四个类的协作完成线程间通信。

为什么重要

  • 面试必考:几乎所有 Android 面试都会问到 Handler 工作流程,是检验对 Android 消息机制理解深度的标准
  • 系统核心:Android 主线程的事件驱动模型基于 Handler 实现,生命周期回调、事件分发、View 绘制都依赖这套机制
  • 调试关键:理解工作流程才能定位 ANR、内存泄漏、消息丢失等问题
  • 源码分析基础:是深入学习 Android Framework 的入口,涉及 Java 层和 Native 层交互

1.2 与其他概念的关系

Handler 工作流程建立在四大核心类(Handler、Message、MessageQueue、Looper)的基础之上(详见 ./01-Handler基本概念.md),本文聚焦于完整的消息流转过程。具体的 API 使用方式(sendMessage、post 等)已在 ./02-Handler使用方式.md 中详细说明,本文从源码角度分析底层实现原理。


二、核心原理

2.1 完整工作流程概览

Handler 工作流程分为四个核心阶段:

┌─────────────────────────────────────────────────────────┐
│                    Handler 工作流程                      │
└─────────────────────────────────────────────────────────┘

阶段1:消息发送
┌──────────────┐
│   Handler    │ handler.sendMessage(msg)
│ sendMessage()│────┐
└──────────────┘    │
                    ↓
阶段2:消息入队
┌─────────────────────────────┐
│      MessageQueue           │
│  enqueueMessage()           │
│  按时间排序插入单链表        │
│  [msg1][msg2][msg3]       │
└─────────────────────────────┘
                    │
                    ↓
阶段3:消息循环与取出
┌─────────────────────────────┐
│        Looper.loop()        │
│  for(;;) {                  │
│    msg = queue.next()  ←─── │ 阻塞等待(epoll)
│    if (msg == null) return  │
│    msg.target.dispatch()    │
│  }                          │
└─────────────────────────────┘
                    │
                    ↓
阶段4:消息分发与处理
┌─────────────────────────────┐
│   Handler.dispatchMessage() │
│   1. msg.callback?          │
│   2. mCallback?             │
│   3. handleMessage()        │
└─────────────────────────────┘

时序图

线程A(发送者)          MessageQueue          线程B(Looper所在线程)
     │                       │                        │
     │ sendMessage(msg)      │                        │
     │──────────────────────→│                        │
     │                       │ enqueueMessage()       │
     │                       │ (插入链表)              │
     │                       │                        │
     │                       │                        │ Looper.loop()
     │                       │                        │ for(;;)
     │                       │←───────────────────────│ queue.next()
     │                       │                        │
     │                       │ (阻塞等待)              │ nativePollOnce()
     │                       │                        │
     │ 新消息到达             │                        │
     │──────────────────────→│                        │
     │                       │ nativeWake()           │
     │                       │───────────────────────→│ 唤醒
     │                       │                        │
     │                       │───────────────────────→│ 返回 Message
     │                       │                        │
     │                       │                        │ msg.target.dispatch()
     │                       │                        │ handleMessage(msg)
     │                       │                        │
     │                       │                        │ msg.recycle()
     │                       │                        │
     │                       │←───────────────────────│ queue.next()
     │                       │                        │ (继续循环)

2.2 阶段1:消息发送

2.2.1 发送流程源码分析

所有消息发送方法最终都会调用 sendMessageAtTime()

// frameworks/base/core/java/android/os/Handler.java (Android 12)
public final boolean sendMessage(@NonNull Message msg) {
    return sendMessageDelayed(msg, 0);
}

public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
    if (delayMillis < 0) {
        delayMillis = 0;
    }
    return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
    // ↑ 关键:计算绝对执行时间
}

public boolean sendMessageAtTime(@NonNull Message msg, long uptimeMillis) {
    MessageQueue queue = mQueue;
    if (queue == null) {
        RuntimeException e = new RuntimeException(
                this + " sendMessageAtTime() called with no mQueue");
        Log.w("Looper", e.getMessage(), e);
        return false;
    }
    return enqueueMessage(queue, msg, uptimeMillis);
}

private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
        long uptimeMillis) {
    msg.target = this; // ← 关键1:设置消息的目标 Handler
    msg.workSourceUid = ThreadLocalWorkSource.getUid();

    if (mAsynchronous) {
        msg.setAsynchronous(true); // ← 关键2:异步消息标记
    }
    return queue.enqueueMessage(msg, uptimeMillis); // ← 关键3:插入消息队列
}

关键步骤

  1. 时间计算delayMillis 相对时间转换为 uptimeMillis 绝对时间
  2. target 绑定msg.target = this,确保消息回到正确的 Handler
  3. 异步标记:如果 Handler 是异步的,标记 Message 为异步消息
  4. 入队操作:调用 MessageQueue.enqueueMessage() 插入队列

2.3 阶段2:消息入队

2.3.1 MessageQueue 入队机制

MessageQueue 本质是按时间排序的单链表。消息入队的核心逻辑:

关键步骤

  1. 线程安全检查:使用 synchronized (this) 保证线程安全
  2. 状态验证:检查消息是否已使用、队列是否已退出
  3. 时间排序插入:遍历链表找到合适位置,按 when 升序排列
  4. 唤醒 Looper:新消息插入队列头部时,通过 nativeWake() 唤醒阻塞的 Looper

唤醒条件

  • 队列原本为空,新消息到达
  • 新消息的执行时间早于队列头部消息
  • 存在同步屏障,且新消息是异步消息

详细源码分析:详见 ../03-MessageQueue队列管理/01-消息入队enqueueMessage.md

2.4 阶段3:消息循环与取出

2.4.1 Looper.loop() 主循环
// frameworks/base/core/java/android/os/Looper.java (Android 12)
public static void loop() {
    final Looper me = myLooper();
    if (me == null) {
        throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
    }

    final MessageQueue queue = me.mQueue;


    // 无限循环,除非调用 quit()
    for (;;) {
        Message msg = queue.next(); // ← 关键:阻塞式取消息
        if (msg == null) {
            return; // 队列退出,结束循环
        }

        try {
            msg.target.dispatchMessage(msg); // ← 关键:分发消息
        } finally {
            msg.recycleUnchecked(); // ← 关键:回收消息
        }
    }
}

核心流程

  1. 获取 Looper:通过 myLooper() 获取当前线程的 Looper
  2. 无限循环for(;;) 持续运行,直到队列退出
  3. 阻塞取消息queue.next() 阻塞等待,直到有消息或队列退出返回 null
  4. 消息分发msg.target.dispatchMessage(msg) 将消息分发给对应的 Handler
  5. 消息回收msg.recycleUnchecked() 回收消息到对象池复用

详细源码实现:详见 ../02-Looper循环/02-Looper.loop()源码分析.md

2.4.2 MessageQueue.next() 阻塞机制
// frameworks/base/core/java/android/os/MessageQueue.java (Android 12)
Message next() {
    final long ptr = mPtr;
    if (ptr == 0) {
        return null; // MessageQueue 已销毁
    }

    int nextPollTimeoutMillis = 0; // 下次阻塞时长

    for (;;) {
        // ← 关键:Native 层阻塞等待
        nativePollOnce(ptr, nextPollTimeoutMillis);

        synchronized (this) {
            final long now = SystemClock.uptimeMillis();
            Message msg = mMessages;

            // 处理同步屏障(跳过同步消息,找异步消息)
            if (msg != null && msg.target == null) {
                do {
                    msg = msg.next;
                } while (msg != null && !msg.isAsynchronous());
            }

            if (msg != null) {
                if (now < msg.when) {
                    // 消息未到时间,计算阻塞时长
                    nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                } else {
                    // 消息到时间,从队列移除并返回
                    mMessages = msg.next;
                    msg.next = null;
                    msg.markInUse();
                    return msg; // ← 返回消息给 Looper
                }
            } else {
                // 队列为空,无限阻塞
                nextPollTimeoutMillis = -1;
            }

            // 队列退出
            if (mQuitting) {
                dispose();
                return null;
            }
        }

        // 队列空闲时执行 IdleHandler(省略详细代码)
        // ...
    }
}

核心逻辑

  1. Native 层阻塞:调用 nativePollOnce() 进入阻塞等待
  2. 同步屏障处理:队列头是同步屏障时,跳过同步消息找异步消息
  3. 时间判断
    • 消息到时间 → 返回消息
    • 消息未到时间 → 计算阻塞时长
    • 队列为空 → 无限阻塞(-1)
  4. 空闲处理:队列空闲时执行 IdleHandler 回调

阻塞时长计算

场景nextPollTimeoutMillis含义
队列为空-1无限阻塞,直到新消息到达
有延迟消息msg.when - now阻塞到消息执行时间
有即时消息0不阻塞,立即返回

详细源码分析:详见 ../03-MessageQueue队列管理/02-消息出队next.md IdleHandler 机制:详见 ../05-IdleHandler/IdleHandler原理.md

Native 层阻塞机制(epoll)

MessageQueue 的阻塞与唤醒底层使用 Linux epoll 机制实现:

nativePollOnce() → Native Looper → epoll_wait(阻塞等待)
                                        ↓
                                   管道有数据写入
                                        ↓
                                    立即唤醒返回

核心原理

  1. epoll_wait 阻塞:等待文件描述符事件,支持超时参数
  2. 管道通信:MessageQueue 创建时建立管道(pipe)用于唤醒
  3. nativeWake 唤醒:向管道写入数据,epoll_wait 立即返回

epoll 详细实现:详见 ../03-MessageQueue队列管理/04-epoll机制.md

2.5 阶段4:消息分发与处理

2.5.1 Handler.dispatchMessage() 三级分发
// frameworks/base/core/java/android/os/Handler.java (Android 12)
public void dispatchMessage(@NonNull Message msg) {
    // 优先级1:Message 自带的 callback(post 方式)
    if (msg.callback != null) {
        handleCallback(msg);
    } else {
        // 优先级2:Handler 的 mCallback
        if (mCallback != null) {
            if (mCallback.handleMessage(msg)) {
                return; // 返回 true 则拦截
            }
        }
        // 优先级3:Handler 子类重写的 handleMessage()
        handleMessage(msg);
    }
}

private static void handleCallback(Message message) {
    message.callback.run(); // 直接执行 Runnable
}

// 子类重写
public void handleMessage(@NonNull Message msg) {
}

分发优先级示意图

dispatchMessage(msg)
       
       ├─→ msg.callback != null?
                ├─ YES  handleCallback(msg)  msg.callback.run()
                └─ NO  
       
       ├─→ mCallback != null?
                ├─ YES  mCallback.handleMessage(msg)
                              ├─ return true   拦截,结束
                              └─ return false  继续 
                └─ NO  
       
       └─→ handleMessage(msg)  子类重写的处理逻辑

三种处理方式对比

优先级来源触发方式使用场景
1msg.callbackhandler.post(Runnable)简单一次性任务
2mCallbacknew Handler(Callback)拦截处理、统一日志
3handleMessage()继承 Handler 重写复杂消息处理

2.6 重要细节与边界条件

细节1:主线程 Looper 何时创建
// frameworks/base/core/java/android/app/ActivityThread.java (Android 12)
public static void main(String[] args) {
    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");

    // ...

    Looper.prepareMainLooper(); // ← 创建主线程 Looper

    // ...

    ActivityThread thread = new ActivityThread();
    thread.attach(false, startSeq);

    if (sMainThreadHandler == null) {
        sMainThreadHandler = thread.getHandler();
    }

    // ...

    Looper.loop(); // ← 开启主线程消息循环

    throw new RuntimeException("Main thread loop unexpectedly exited");
}

关键点

  1. 应用启动时,ActivityThread.main() 自动创建主线程 Looper
  2. 主线程 Looper 不允许退出(quitAllowed = false
  3. 如果主线程 Looper 退出,会抛出 RuntimeException 导致应用崩溃
细节2:同步屏障(Sync Barrier)机制
// frameworks/base/core/java/android/os/MessageQueue.java (Android 12)
public int postSyncBarrier() {
    return postSyncBarrier(SystemClock.uptimeMillis());
}

private int postSyncBarrier(long when) {
    synchronized (this) {
        final int token = mNextBarrierToken++;
        final Message msg = Message.obtain();
        msg.markInUse();
        msg.when = when;
        msg.arg1 = token;

        Message prev = null;
        Message p = mMessages;
        if (when != 0) {
            while (p != null && p.when <= when) {
                prev = p;
                p = p.next;
            }
        }
        if (prev != null) {
            msg.next = p;
            prev.next = msg;
        } else {
            msg.next = p;
            mMessages = msg;
        }
        return token;
    }
}

同步屏障特点

  • target 为 null:普通消息的 target 指向 Handler,同步屏障的 target 为 null
  • 阻塞同步消息:MessageQueue.next() 会跳过所有同步消息,只处理异步消息
  • 应用场景:View 绘制时,ViewRootImpl 会插入同步屏障,优先处理 VSYNC 信号

为什么应用层无法使用

  • postSyncBarrier()@hide 方法,应用层无法直接调用
  • 滥用会导致同步消息饿死,引发 ANR

详细原理:详见 ../06-同步屏障/同步屏障原理.md

细节3:IdleHandler 空闲消息处理
// frameworks/base/core/java/android/os/MessageQueue.java (Android 12)
public static interface IdleHandler {
    boolean queueIdle(); // 返回 false 则执行一次后移除,返回 true 则保留
}

public void addIdleHandler(@NonNull IdleHandler handler) {
    if (handler == null) {
        throw new NullPointerException("Can't add a null IdleHandler");
    }
    synchronized (this) {
        mIdleHandlers.add(handler);
    }
}

执行时机

  • 队列为空
  • 队列头部消息未到执行时间

应用场景

  • Activity 启动优化:在空闲时预加载资源
  • 性能监控:统计主线程空闲时间

详细原理:详见 ../05-IdleHandler/IdleHandler原理.md


三、实际应用

3.1 典型场景

场景1:主线程事件驱动模型

Android 主线程的所有任务都通过 Handler 消息机制执行

// frameworks/base/core/java/android/app/ActivityThread.java (Android 12)
class H extends Handler {
    public static final int LAUNCH_ACTIVITY         = 100;
    public static final int PAUSE_ACTIVITY          = 101;
    public static final int PAUSE_ACTIVITY_FINISHING= 102;
    public static final int STOP_ACTIVITY_SHOW      = 103;
    public static final int STOP_ACTIVITY_HIDE      = 104;
    public static final int SHOW_WINDOW             = 105;
    public static final int HIDE_WINDOW             = 106;
    public static final int RESUME_ACTIVITY         = 107;
    // ...

    public void handleMessage(Message msg) {
        switch (msg.what) {
            case LAUNCH_ACTIVITY: {
                final ActivityClientRecord r = (ActivityClientRecord) msg.obj;
                r.loadedApk = getLoadedApkNoCheck(...);
                handleLaunchActivity(r, null, "LAUNCH_ACTIVITY");
                break;
            }
            case RESUME_ACTIVITY: {
                handleResumeActivity((IBinder) msg.obj, true, msg.arg1 != 0, true, ...);
                break;
            }
            // ...
        }
    }
}

系统事件通过 Handler 分发

事件类型what 值触发时机处理方法
启动 ActivityLAUNCH_ACTIVITYAMS 调用handleLaunchActivity()
恢复 ActivityRESUME_ACTIVITYAMS 调用handleResumeActivity()
暂停 ActivityPAUSE_ACTIVITYAMS 调用handlePauseActivity()
停止 ActivitySTOP_ACTIVITYAMS 调用handleStopActivity()
场景2:View 绘制流程中的同步屏障
// frameworks/base/core/java/android/view/ViewRootImpl.java (Android 12)
void scheduleTraversals() {
    if (!mTraversalScheduled) {
        mTraversalScheduled = true;
        // 插入同步屏障,优先处理 VSYNC 信号
        mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
        // 请求 VSYNC 信号(异步消息)
        mChoreographer.postCallback(
                Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
    }
}

void unscheduleTraversals() {
    if (mTraversalScheduled) {
        mTraversalScheduled = false;
        // 移除同步屏障
        mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
        mChoreographer.removeCallbacks(
                Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
    }
}

同步屏障作用

  1. 插入同步屏障后,所有同步消息被阻塞
  2. VSYNC 信号到达后,异步消息(绘制任务)优先执行
  3. 绘制完成后移除同步屏障,恢复同步消息处理
场景3:IdleHandler 实现启动优化
public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // 主线程空闲时预加载资源
        Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() {
            @Override
            public boolean queueIdle() {
                // 预加载图片
                preloadImages();
                // 初始化第三方 SDK
                initThirdPartySdk();
                return false; // 返回 false,执行一次后移除
            }
        });
    }

    private void preloadImages() {
        // 预加载逻辑
    }

    private void initThirdPartySdk() {
        // 初始化逻辑
    }
}

3.2 最佳实践

✅ 推荐做法

1. 理解消息流转过程,避免误用

// ✅ 正确:在主线程更新 UI
Handler mainHandler = new Handler(Looper.getMainLooper());
new Thread(() -> {
    // 耗时操作
    String result = loadData();
    // 切换到主线程
    mainHandler.post(() -> {
        textView.setText(result);
    });
}).start();

2. 利用 IdleHandler 优化启动性能

// ✅ 推荐:非紧急任务延迟到空闲时执行
Looper.myQueue().addIdleHandler(() -> {
    initNonCriticalComponents();
    return false;
});

3. 监控主线程消息处理时长

// ✅ 推荐:通过 Looper.setMessageLogging() 监控 ANR
Looper.getMainLooper().setMessageLogging(new Printer() {
    private static final long BLOCK_THRESHOLD = 100; // 100ms
    private long startTime;

    @Override
    public void println(String x) {
        if (x.startsWith(">>>>> Dispatching")) {
            startTime = SystemClock.uptimeMillis();
        } else if (x.startsWith("<<<<< Finished")) {
            long duration = SystemClock.uptimeMillis() - startTime;
            if (duration > BLOCK_THRESHOLD) {
                Log.w("Looper", "Message processing took " + duration + "ms");
            }
        }
    }
});
❌ 常见错误

错误1:在主线程执行耗时操作

// ❌ 错误
Handler handler = new Handler(Looper.getMainLooper());
handler.post(() -> {
    String data = loadDataFromNetwork(); // 网络请求,耗时操作
    textView.setText(data); // 导致 ANR
});

// ✅ 正确
new Thread(() -> {
    String data = loadDataFromNetwork();
    handler.post(() -> {
        textView.setText(data);
    });
}).start();

错误2:误解 Looper.loop() 是死循环会卡死主线程

// ❌ 错误理解
// "Looper.loop() 是死循环,会卡死主线程"

// ✅ 正确理解
// Looper.loop() 虽然是死循环,但会通过 epoll 机制阻塞等待,
// 不会占用 CPU 资源,不会卡死主线程。
// 主线程必须持续运行,所有事件都通过消息机制处理。

错误3:忘记移除消息导致内存泄漏

// ❌ 错误
handler.postDelayed(() -> {
    // 延迟60秒执行
}, 60000);
// Activity 销毁时未移除,导致内存泄漏

// ✅ 正确
@Override
protected void onDestroy() {
    super.onDestroy();
    handler.removeCallbacksAndMessages(null);
}

3.3 性能优化建议

1. 避免频繁发送消息

// ❌ 不推荐
for (int i = 0; i < 1000; i++) {
    handler.sendEmptyMessage(i);
}

// ✅ 推荐:合并消息
handler.removeMessages(MSG_UPDATE);
handler.sendEmptyMessage(MSG_UPDATE);

2. 使用 IdleHandler 延迟非紧急任务

// ✅ 推荐
Looper.myQueue().addIdleHandler(() -> {
    // 空闲时执行
    updateCache();
    return false;
});

3. 监控消息处理时长,定位 ANR

// ✅ 推荐:使用 BlockCanary 等工具监控主线程卡顿

四、面试真题解析

4.1 基础必答题(P5必须掌握)

【高频题1】描述 Handler 的完整工作流程

标准答案(30秒): Handler 工作流程分为四个阶段:第一阶段是消息发送,Handler 调用 sendMessage 最终会调用 enqueueMessage 将消息插入 MessageQueue;第二阶段是消息入队,MessageQueue 按时间排序将消息插入单链表,如果 Looper 阻塞且新消息插入队列头部会唤醒 Looper;第三阶段是消息循环,Looper.loop 无限循环调用 MessageQueue.next 取消息,如果队列为空会通过 epoll 机制阻塞等待;第四阶段是消息分发,Looper 将消息分发给 msg.target 对应的 Handler,Handler 调用 dispatchMessage 经过三级分发最终执行 handleMessage 处理消息。

深入展开(追问后)

四个阶段详细流程

  1. 消息发送阶段
handler.sendMessage(msg)
  → sendMessageDelayed(msg, 0)
  → sendMessageAtTime(msg, SystemClock.uptimeMillis() + delay)
  → enqueueMessage(queue, msg, uptimeMillis)
      ├─ msg.target = this  // 设置目标 Handler
      └─ queue.enqueueMessage(msg, uptimeMillis)
  1. 消息入队阶段
MessageQueue.enqueueMessage(msg, when)
  ├─ msg.when = when
  ├─ 遍历链表,按时间排序插入
  └─ if (needWake) nativeWake(mPtr)  // 唤醒 Looper
  1. 消息循环阶段
Looper.loop()
  for (;;) {
    msg = queue.next()  // 阻塞式取消息
      ├─ if (队列为空) nativePollOnce(-1)  // 无限阻塞
      ├─ if (延迟消息) nativePollOnce(delay)  // 定时阻塞
      └─ return msg
    msg.target.dispatchMessage(msg)
    msg.recycleUnchecked()
  }
  1. 消息分发阶段
Handler.dispatchMessage(msg)
  if (msg.callback != null)
    handleCallback(msg)  // 优先级1
  else if (mCallback != null && mCallback.handleMessage(msg))
    return  // 优先级2,返回 true 则拦截
  else
    handleMessage(msg)  // 优先级3

面试官追问

追问1:MessageQueue 是队列吗?为什么叫 Queue?

答:MessageQueue 不是真正的队列,而是按时间排序的单链表。之所以叫 Queue,是因为它提供了类似队列的 FIFO 语义:先发送的消息(when 相同时)先执行。但由于支持延迟消息,必须按时间排序,所以底层使用单链表实现,插入时间复杂度 O(n),取消息时间复杂度 O(1)。

追问2:Looper.loop() 是死循环,为什么不会 ANR?

答:Looper.loop() 虽然是死循环,但不会导致 ANR,原因有三点:

  1. epoll 阻塞机制:队列为空时,MessageQueue.next() 调用 nativePollOnce() 进入阻塞状态,释放 CPU 资源,不会空转
  2. 事件驱动模型:Android 主线程必须持续运行,所有 UI 事件、生命周期回调都通过消息机制触发
  3. 单个消息执行时间短:ANR 是单个消息处理超过 5 秒导致的,不是 Looper 循环导致的

实际 ANR 场景:

// ❌ 会 ANR
handler.post(() -> {
    Thread.sleep(6000); // 单个消息处理超过 5 秒
});

// ✅ 不会 ANR
for (int i = 0; i < 1000; i++) {
    handler.post(() -> {
        // 每个消息处理 10ms,总共 10 秒也不会 ANR
    });
}

【高频题2】MessageQueue 如何实现阻塞和唤醒?

标准答案(30秒): MessageQueue 通过 Linux epoll 机制实现阻塞和唤醒。当队列为空或队列头部消息未到执行时间时,MessageQueue.next 会调用 nativePollOnce 进入阻塞状态,底层使用 epoll_wait 监听管道的读端。当有新消息插入队列头部时,enqueueMessage 会调用 nativeWake,向管道的写端写入数据,epoll_wait 监听到读端有数据立即返回,Looper 被唤醒继续取消息。这种机制避免了 CPU 空转,提高了性能。

深入展开(追问后)

epoll 机制原理

  1. 创建管道
// frameworks/base/core/jni/android_os_MessageQueue.cpp
NativeMessageQueue::NativeMessageQueue() {
    mLooper = Looper::getForThread();
    if (mLooper == NULL) {
        mLooper = new Looper(false);
        Looper::setForThread(mLooper);
    }
}

// system/core/libutils/Looper.cpp
Looper::Looper(bool allowNonCallbacks) {
    // 创建管道
    int wakeFds[2];
    int result = pipe(wakeFds);
    mWakeReadPipeFd = wakeFds[0];
    mWakeWritePipeFd = wakeFds[1];

    // 创建 epoll 实例
    mEpollFd = epoll_create(EPOLL_SIZE_HINT);

    // 监听管道读端
    struct epoll_event eventItem;
    eventItem.events = EPOLLIN;
    eventItem.data.fd = mWakeReadPipeFd;
    epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mWakeReadPipeFd, &eventItem);
}
  1. 阻塞等待
int Looper::pollInner(int timeoutMillis) {
    // 使用 epoll_wait 阻塞等待
    int eventCount = epoll_wait(mEpollFd, eventItems, EPOLL_MAX_EVENTS, timeoutMillis);

    if (eventCount == 0) {
        return POLL_TIMEOUT; // 超时
    }

    for (int i = 0; i < eventCount; i++) {
        int fd = eventItems[i].data.fd;
        uint32_t epollEvents = eventItems[i].events;

        if (fd == mWakeReadPipeFd) {
            if (epollEvents & EPOLLIN) {
                awoken(); // 被唤醒,读取管道数据
            }
        }
    }
    return POLL_WAKE;
}

void Looper::awoken() {
    uint64_t counter;
    TEMP_FAILURE_RETRY(read(mWakeReadPipeFd, &counter, sizeof(uint64_t)));
}
  1. 唤醒机制
void Looper::wake() {
    uint64_t inc = 1;
    // 向管道写端写入数据
    ssize_t nWrite = TEMP_FAILURE_RETRY(write(mWakeWritePipeFd, &inc, sizeof(uint64_t)));
}

阻塞时长计算表

队列状态nextPollTimeoutMillisepoll_wait 行为
队列为空-1无限阻塞,直到 nativeWake()
有延迟消息(未到时间)msg.when - now定时阻塞,到时间自动返回
有即时消息0立即返回,不阻塞

面试官追问

追问1:为什么不用 Object.wait() 和 notify()?

答:主要有三个原因:

  1. 跨进程唤醒:epoll 可以监听文件描述符,支持跨进程唤醒(例如 Binder 驱动写入数据唤醒应用进程)
  2. 精确定时:epoll_wait 支持超时参数,可以精确控制阻塞时长
  3. 统一机制:Native Looper 和 Java Looper 都使用 epoll,保持一致性

追问2:nativeWake() 会不会重复唤醒?

答:不会。nativeWake() 向管道写入数据,awoken() 会读取管道数据清空,即使多次调用 nativeWake(),epoll_wait 也只会返回一次。类似于信号量的设计,多次 wake 等同于一次 wake。


【高频题3】Handler 的消息分发机制是什么?

标准答案(30秒): Handler 消息分发采用三级分发机制。Looper 取出消息后调用 msg.target.dispatchMessage,第一优先级检查 msg.callback 是否为空,如果不为空直接执行 Runnable.run,这是 post 方式的消息;第二优先级检查 Handler 的 mCallback 是否为空,如果不为空调用 mCallback.handleMessage,返回 true 则拦截不再继续分发;第三优先级调用 Handler 子类重写的 handleMessage 方法。这种设计提供了灵活的消息处理机制,既支持简单的 Runnable 任务,又支持复杂的消息处理逻辑。

深入展开(追问后)

三级分发源码

// frameworks/base/core/java/android/os/Handler.java (Android 12)
public void dispatchMessage(@NonNull Message msg) {
    // 优先级1:Message.callback (Runnable)
    if (msg.callback != null) {
        handleCallback(msg);
    } else {
        // 优先级2:Handler.mCallback
        if (mCallback != null) {
            if (mCallback.handleMessage(msg)) {
                return; // 返回 true 拦截
            }
        }
        // 优先级3:handleMessage()
        handleMessage(msg);
    }
}

private static void handleCallback(Message message) {
    message.callback.run();
}

三种使用方式示例

// 优先级1:post 方式
handler.post(() -> {
    Log.d("Handler", "优先级1:msg.callback");
    textView.setText("更新 UI");
});

// 优先级2:Callback 拦截
Handler handler = new Handler(new Handler.Callback() {
    @Override
    public boolean handleMessage(Message msg) {
        Log.d("Handler", "优先级2:mCallback");
        // 统一日志处理
        if (msg.what == MSG_ERROR) {
            handleError(msg);
            return true; // 拦截,不再调用 handleMessage
        }
        return false; // 继续分发
    }
});

// 优先级3:子类重写
Handler handler = new Handler() {
    @Override
    public void handleMessage(Message msg) {
        Log.d("Handler", "优先级3:handleMessage");
        // 复杂消息处理
        switch (msg.what) {
            case MSG_UPDATE:
                // ...
                break;
        }
    }
};

面试官追问

追问1:为什么 mCallback 可以拦截 handleMessage?

答:这是责任链模式的变体。通过返回值控制是否继续传递消息,提供了灵活的扩展机制。典型应用场景:

  1. 统一拦截处理:全局日志记录、性能监控
  2. 条件处理:根据消息类型决定是否继续处理
  3. 测试 Mock:测试时替换处理逻辑,无需修改 Handler 子类

追问2:post 的 Runnable 在哪个线程执行?

答:Runnable 在 Handler 绑定的 Looper 所在线程执行,与调用 post 的线程无关。

Handler mainHandler = new Handler(Looper.getMainLooper());

// 子线程调用 post
new Thread(() -> {
    mainHandler.post(() -> {
        // 这里在主线程执行
        Log.d("Thread", Thread.currentThread().getName()); // main
    });
}).start();

原理:post 底层是 sendMessage,消息插入主线程 MessageQueue,主线程 Looper 取出后在主线程执行。


【高频题4】主线程的 Looper 何时创建?如何保证只有一个?**

标准答案(30秒): 主线程的 Looper 在应用启动时由 ActivityThread.main 方法自动创建。main 方法调用 Looper.prepareMainLooper 创建主线程 Looper,然后调用 Looper.loop 开启消息循环。Looper 通过 ThreadLocal 保证每个线程只有一个 Looper,prepareMainLooper 内部会检查 sThreadLocal.get 是否为空,如果已存在则抛出异常。主线程 Looper 创建时设置 quitAllowed 为 false,不允许退出,如果主线程 Looper 退出会抛出 RuntimeException 导致应用崩溃。

深入展开(追问后)

主线程 Looper 创建源码

// frameworks/base/core/java/android/app/ActivityThread.java (Android 12)
public static void main(String[] args) {
    // ...

    Looper.prepareMainLooper(); // ← 创建主线程 Looper

    // ...
    ActivityThread thread = new ActivityThread();
    thread.attach(false, startSeq);

    if (sMainThreadHandler == null) {
        sMainThreadHandler = thread.getHandler();
    }

    // ...
    Looper.loop(); // ← 开启主线程消息循环

    throw new RuntimeException("Main thread loop unexpectedly exited");
}

prepareMainLooper 源码

// frameworks/base/core/java/android/os/Looper.java (Android 12)
public static void prepareMainLooper() {
    prepare(false); // quitAllowed = false
    synchronized (Looper.class) {
        if (sMainLooper != null) {
            throw new IllegalStateException("The main Looper has already been prepared.");
        }
        sMainLooper = myLooper();
    }
}

private static void prepare(boolean quitAllowed) {
    if (sThreadLocal.get() != null) {
        throw new RuntimeException("Only one Looper may be created per thread");
    }
    sThreadLocal.set(new Looper(quitAllowed));
}

ThreadLocal 原理

// frameworks/base/core/java/android/os/Looper.java (Android 12)
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

public static @Nullable Looper myLooper() {
    return sThreadLocal.get(); // 获取当前线程的 Looper
}

ThreadLocal 内部机制:

  • 每个 Thread 对象都有一个 ThreadLocalMap
  • ThreadLocal 作为 key,Looper 作为 value
  • 不同线程的 Map 互不影响,实现线程隔离

面试官追问

追问1:如果调用 Looper.getMainLooper().quit() 会怎样?

答:会抛出异常。主线程 Looper 创建时设置了 quitAllowed = false

// frameworks/base/core/java/android/os/Looper.java (Android 12)
public void quit() {
    mQueue.quit(false);
}

// frameworks/base/core/java/android/os/MessageQueue.java (Android 12)
void quit(boolean safe) {
    if (!mQuitAllowed) {
        throw new IllegalStateException("Main thread not allowed to quit.");
    }
    // ...
}

追问2:子线程如何创建 Looper?

答:子线程需要手动调用 Looper.prepare()Looper.loop()

new Thread(() -> {
    Looper.prepare();           // 1. 创建 Looper
    Handler handler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            // 处理消息
        }
    };
    Looper.loop();              // 2. 开启消息循环
}).start();

或者使用 HandlerThread:

HandlerThread thread = new HandlerThread("WorkThread");
thread.start();
Handler handler = new Handler(thread.getLooper());

【高频题5】Message 的复用机制是什么?**

标准答案(30秒): Message 使用享元模式实现对象复用,内部维护了一个最大容量为 50 的对象池。调用 Message.obtain 时会从对象池的链表头部取出一个 Message 复用,如果池为空则创建新对象。消息处理完毕后 Looper 会自动调用 recycleUnchecked 方法清空消息的所有字段数据,然后放回对象池链表头部。这种机制避免了频繁创建销毁 Message 对象,减少 GC 压力,提升性能。开发中应该优先使用 Message.obtain 而非 new Message。

深入展开(追问后)

对象池源码

// frameworks/base/core/java/android/os/Message.java (Android 12)
private static Message sPool;      // 对象池链表头节点
private static int sPoolSize = 0;  // 当前池大小
private static final int MAX_POOL_SIZE = 50; // 最大容量

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();  // 池为空则创建新对象
}

void recycleUnchecked() {
    // 清空所有字段
    flags = FLAG_IN_USE;
    what = 0;
    arg1 = 0;
    arg2 = 0;
    obj = null;
    replyTo = null;
    sendingUid = UID_NONE;
    workSourceUid = UID_NONE;
    when = 0;
    target = null;
    callback = null;
    data = null;

    synchronized (sPoolSync) {
        if (sPoolSize < MAX_POOL_SIZE) {
            next = sPool;    // 放回池中
            sPool = this;
            sPoolSize++;
        }
    }
}

对象池结构

sPool → [Message1][Message2][Message3] → ... → [Message50] → null
        (最新回收)                                     (最早回收)

obtain() 从头部取出
recycle() 插入到头部

Looper 自动回收

// frameworks/base/core/java/android/os/Looper.java (Android 12)
public static void loop() {
    for (;;) {
        Message msg = queue.next();
        // ...
        try {
            msg.target.dispatchMessage(msg);
        } finally {
            msg.recycleUnchecked(); // ← 自动回收
        }
    }
}

面试官追问

追问1:为什么对象池最大容量是 50?

答:这是性能和内存的平衡:

  1. 避免内存占用过大:Message 可能携带大量数据(Bundle、Bitmap 等),无限制缓存会占用过多内存
  2. 满足高频场景:50 个对象足够应对大部分高频消息场景
  3. 超过容量自动回收:超过 50 个的 Message 不会放回池中,由 GC 回收

追问2:为什么 recycle 要清空所有字段?

答:防止内存泄漏。Message 可能持有 Activity、View、Bitmap 等大对象的引用,如果不清空直接放回对象池,即使外部不再使用该 Message,这些对象也无法被 GC 回收,导致内存泄漏。

示例:

// 错误:未清空会导致泄漏
Message msg = Message.obtain();
msg.obj = bitmap; // 持有 Bitmap 引用
handler.sendMessage(msg);
// msg 处理完后如果不清空 obj,bitmap 无法回收

// 正确:recycleUnchecked 会清空 obj
msg.recycleUnchecked();
// obj = null,bitmap 可以被 GC 回收

4.2 进阶加分题(P6/P6+)

【进阶题1】同步屏障(Sync Barrier)的工作原理是什么?**

参考答案

同步屏障是一种特殊的 Message,它的 target 字段为 null(普通消息的 target 指向 Handler)。当 MessageQueue 中存在同步屏障时,MessageQueue.next() 会跳过所有同步消息,只处理异步消息,从而实现异步消息的优先执行。

插入同步屏障

// frameworks/base/core/java/android/os/MessageQueue.java (Android 12)
public int postSyncBarrier() {
    return postSyncBarrier(SystemClock.uptimeMillis());
}

private int postSyncBarrier(long when) {
    synchronized (this) {
        final int token = mNextBarrierToken++;
        final Message msg = Message.obtain();
        msg.markInUse();
        msg.when = when;
        msg.arg1 = token;
        // ← 关键:不设置 target,target 为 null

        // 按时间顺序插入链表
        Message prev = null;
        Message p = mMessages;
        if (when != 0) {
            while (p != null && p.when <= when) {
                prev = p;
                p = p.next;
            }
        }
        if (prev != null) {
            msg.next = p;
            prev.next = msg;
        } else {
            msg.next = p;
            mMessages = msg;
        }
        return token;
    }
}

跳过同步消息

// frameworks/base/core/java/android/os/MessageQueue.java (Android 12)
Message next() {
    for (;;) {
        nativePollOnce(ptr, nextPollTimeoutMillis);

        synchronized (this) {
            Message prevMsg = null;
            Message msg = mMessages;

            // ← 关键:处理同步屏障
            if (msg != null && msg.target == null) {
                // 队列头是同步屏障,跳过所有同步消息
                do {
                    prevMsg = msg;
                    msg = msg.next;
                } while (msg != null && !msg.isAsynchronous());
            }

            if (msg != null) {
                if (now < msg.when) {
                    nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                } else {
                    // 返回异步消息
                    return msg;
                }
            }
        }
    }
}

应用场景:View 绘制

// frameworks/base/core/java/android/view/ViewRootImpl.java (Android 12)
void scheduleTraversals() {
    if (!mTraversalScheduled) {
        mTraversalScheduled = true;
        // 插入同步屏障
        mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
        // 请求 VSYNC 信号(异步消息)
        mChoreographer.postCallback(
                Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
    }
}

void unscheduleTraversals() {
    if (mTraversalScheduled) {
        mTraversalScheduled = false;
        // 移除同步屏障
        mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
    }
}

追问:为什么应用层无法使用同步屏障?

答:postSyncBarrier()removeSyncBarrier() 都是 @hide 方法,应用层无法直接调用。原因:

  1. 风险高:同步屏障会阻塞所有同步消息,滥用会导致主线程任务饿死,引发 ANR
  2. 系统级优化:只应该在可控场景下使用,例如 View 绘制、输入事件处理
  3. 需要成对使用:必须及时移除同步屏障,否则会永久阻塞同步消息

【进阶题2】IdleHandler 的工作原理和应用场景?**

参考答案

IdleHandler 是 MessageQueue 提供的一种回调接口,当消息队列空闲时(队列为空或队列头部消息未到执行时间)会执行 IdleHandler.queueIdle() 方法。

IdleHandler 接口

// frameworks/base/core/java/android/os/MessageQueue.java (Android 12)
public static interface IdleHandler {
    boolean queueIdle(); // 返回 false 执行一次后移除,返回 true 保留
}

public void addIdleHandler(@NonNull IdleHandler handler) {
    if (handler == null) {
        throw new NullPointerException("Can't add a null IdleHandler");
    }
    synchronized (this) {
        mIdleHandlers.add(handler);
    }
}

执行时机

// frameworks/base/core/java/android/os/MessageQueue.java (Android 12)
Message next() {
    for (;;) {
        nativePollOnce(ptr, nextPollTimeoutMillis);

        synchronized (this) {
            // ...
            if (msg != null) {
                if (now < msg.when) {
                    nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                } else {
                    return msg;
                }
            } else {
                nextPollTimeoutMillis = -1;
            }

            // ← IdleHandler 执行时机:队列空闲
            if (pendingIdleHandlerCount < 0
                    && (mMessages == null || now < mMessages.when)) {
                pendingIdleHandlerCount = mIdleHandlers.size();
            }
            if (pendingIdleHandlerCount <= 0) {
                mBlocked = true;
                continue;
            }

            if (mPendingIdleHandlers == null) {
                mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
            }
            mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
        }

        // 执行 IdleHandler
        for (int i = 0; i < pendingIdleHandlerCount; i++) {
            final IdleHandler idler = mPendingIdleHandlers[i];
            mPendingIdleHandlers[i] = null;

            boolean keep = false;
            try {
                keep = idler.queueIdle();
            } catch (Throwable t) {
                Log.wtf(TAG, "IdleHandler threw exception", t);
            }

            if (!keep) {
                synchronized (this) {
                    mIdleHandlers.remove(idler);
                }
            }
        }
    }
}

应用场景

  1. Activity 启动优化
Looper.myQueue().addIdleHandler(() -> {
    // 预加载资源
    preloadImages();
    // 初始化第三方 SDK
    initSDK();
    return false; // 执行一次后移除
});
  1. 性能监控
Looper.myQueue().addIdleHandler(() -> {
    long idleTime = SystemClock.uptimeMillis() - lastBusyTime;
    Log.d("Performance", "Main thread idle for " + idleTime + "ms");
    return true; // 保留,持续监控
});

追问:IdleHandler 和 postDelayed(0) 有什么区别?

答:

对比项IdleHandlerpostDelayed(0)
执行时机队列空闲时立即插入队列,按顺序执行
优先级低于所有消息与其他即时消息相同
适用场景非紧急任务、启动优化切换到主线程执行
可能不执行队列一直繁忙可能不执行一定会执行

【进阶题3】如何监控主线程消息处理时长定位 ANR?**

参考答案

可以通过 Looper.setMessageLogging() 监控主线程消息处理时长,定位 ANR 原因。

原理

// frameworks/base/core/java/android/os/Looper.java (Android 12)
public static void loop() {
    final Looper me = myLooper();
    final MessageQueue queue = me.mQueue;

    for (;;) {
        Message msg = queue.next();
        if (msg == null) {
            return;
        }

        final Printer logging = me.mLogging; // ← 日志打印器
        if (logging != null) {
            logging.println(">>>>> Dispatching to " + msg.target + " " +
                    msg.callback + ": " + msg.what);
        }

        try {
            msg.target.dispatchMessage(msg);
        } finally {
            if (logging != null) {
                logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
            }
        }

        msg.recycleUnchecked();
    }
}

监控实现

public class LooperMonitor {
    private static final long BLOCK_THRESHOLD = 100; // 100ms 阈值

    public static void start() {
        Looper.getMainLooper().setMessageLogging(new Printer() {
            private long startTime;
            private boolean isDispatching;

            @Override
            public void println(String x) {
                if (x.startsWith(">>>>> Dispatching")) {
                    startTime = SystemClock.uptimeMillis();
                    isDispatching = true;
                } else if (x.startsWith("<<<<< Finished")) {
                    if (isDispatching) {
                        long duration = SystemClock.uptimeMillis() - startTime;
                        if (duration > BLOCK_THRESHOLD) {
                            // 打印堆栈
                            StackTraceElement[] stackTrace = Looper.getMainLooper().getThread().getStackTrace();
                            StringBuilder sb = new StringBuilder();
                            for (StackTraceElement element : stackTrace) {
                                sb.append(element.toString()).append("\n");
                            }
                            Log.w("LooperMonitor", "Message processing took " + duration + "ms\n" + sb.toString());
                        }
                        isDispatching = false;
                    }
                }
            }
        });
    }
}

开源方案:BlockCanary

public class BlockCanary {
    public void start() {
        Looper.getMainLooper().setMessageLogging(mainLooperPrinter);
    }

    class LooperPrinter implements Printer {
        @Override
        public void println(String x) {
            if (x.startsWith(">>>>> Dispatching")) {
                startDump();
            } else if (x.startsWith("<<<<< Finished")) {
                stopDump();
            }
        }
    }
}

追问:这种监控方案的缺点是什么?

答:

  1. 性能开销:每个消息都会调用 println,频繁打印日志影响性能
  2. 无法监控 Native 层:只能监控 Java 层消息,Native 层耗时无法感知
  3. 堆栈不准确:打印堆栈时消息可能已经处理完毕,堆栈信息不准确

更好的方案:

  • Choreographer.FrameCallback:监控帧率,超过 16.6ms 即为卡顿
  • 插桩:编译时插桩,精确记录方法耗时
  • TraceView/Systrace:系统工具,更全面的性能分析

4.3 实战场景题

【场景题】主线程 Handler 大量消息堆积导致 UI 卡顿,如何优化?**

场景描述: 一个列表页面滑动时会发送大量刷新消息到主线程 Handler,导致消息队列堆积,UI 卡顿。如何优化?

问题分析

  1. 根本原因:频繁发送消息导致 MessageQueue 堆积,单个消息处理时间短但总量大
  2. 卡顿表现:列表滑动不流畅,帧率下降
  3. 优化目标:减少消息数量,提高处理效率

解决方案

方案1:合并消息(推荐)

private Handler handler = new Handler(Looper.getMainLooper());
private static final int MSG_REFRESH = 1;

public void requestRefresh() {
    // 移除之前的刷新消息,只保留最新的
    handler.removeMessages(MSG_REFRESH);
    handler.sendEmptyMessageDelayed(MSG_REFRESH, 16); // 延迟 16ms,合并一帧内的请求
}

方案2:使用 IdleHandler 延迟非紧急任务

Looper.myQueue().addIdleHandler(() -> {
    // 空闲时执行非紧急刷新
    updateNonCriticalUI();
    return false;
});

方案3:分批处理

private List<Item> pendingItems = new ArrayList<>();

public void addItem(Item item) {
    pendingItems.add(item);
    if (pendingItems.size() >= 10) {
        processBatch();
    }
}

private void processBatch() {
    handler.post(() -> {
        adapter.addAll(pendingItems);
        pendingItems.clear();
    });
}

方案4:异步处理 + 结果回调

ExecutorService executor = Executors.newSingleThreadExecutor();

public void loadData() {
    executor.execute(() -> {
        // 子线程处理数据
        List<Item> items = processData();
        // 主线程更新 UI
        handler.post(() -> {
            adapter.setItems(items);
        });
    });
}

追问

1. 方案缺点?

  • 方案1(合并消息)

    • 优点:简单有效,减少消息数量
    • 缺点:可能丢失部分刷新请求,适合最终状态一致的场景
  • 方案2(IdleHandler)

    • 优点:不占用主线程繁忙时间
    • 缺点:主线程一直繁忙可能不执行
  • 方案3(分批处理)

    • 优点:减少频率,提高效率
    • 缺点:延迟更新,用户体验稍差
  • 方案4(异步处理)

    • 优点:耗时操作在子线程
    • 缺点:增加线程切换开销

2. 如何选择方案?

场景推荐方案
频繁刷新同一数据方案1(合并消息)
非紧急任务方案2(IdleHandler)
大量数据更新方案3(分批处理)
耗时操作方案4(异步处理)

3. 如何监控优化效果?

// 使用 Choreographer 监控帧率
Choreographer.getInstance().postFrameCallback(new Choreographer.FrameCallback() {
    private long lastFrameTime;

    @Override
    public void doFrame(long frameTimeNanos) {
        long currentTime = frameTimeNanos / 1000000;
        if (lastFrameTime != 0) {
            long frameDuration = currentTime - lastFrameTime;
            if (frameDuration > 16) { // 超过 16ms 即为掉帧
                Log.w("FPS", "Frame drop: " + frameDuration + "ms");
            }
        }
        lastFrameTime = currentTime;
        Choreographer.getInstance().postFrameCallback(this);
    }
});

五、对比与总结

5.1 关键阶段对比

阶段核心类关键方法主要逻辑
消息发送HandlersendMessageAtTime()计算 when,设置 target
消息入队MessageQueueenqueueMessage()按时间排序插入链表,唤醒 Looper
消息循环Looperloop()无限循环,阻塞取消息
消息取出MessageQueuenext()epoll 阻塞等待,返回消息
消息分发HandlerdispatchMessage()三级分发机制
消息处理HandlerhandleMessage()子类重写处理逻辑
消息回收MessagerecycleUnchecked()清空字段,放回对象池

5.2 核心要点速记

一句话记忆: Handler 通过四大核心类协作完成消息从发送、入队、循环取出、分发处理到回收的完整流程,底层通过 epoll 机制实现高效阻塞唤醒,支持线程间通信和延迟任务。

3个关键点

  1. 消息入队:MessageQueue 按时间排序的单链表,插入时唤醒阻塞的 Looper
  2. 消息循环:Looper.loop() 无限循环通过 epoll 阻塞等待,避免 CPU 空转
  3. 消息分发:Handler 三级分发机制(msg.callback → mCallback → handleMessage)

面试官最爱问

  1. 完整流程:发送 → 入队 → 循环 → 分发 → 处理 → 回收
  2. 阻塞唤醒:epoll_wait 阻塞,nativeWake 写管道唤醒
  3. 线程切换:Handler 绑定 Looper,消息在 Looper 线程执行

六、关联知识点

前置知识

  • Handler 四大核心类(Handler、Message、MessageQueue、Looper)(详见:./01-Handler基本概念.md
  • Handler 使用方式(sendMessage、post 等 API)(详见:./02-Handler使用方式.md

后续扩展

  • MessageQueue 同步屏障机制详解
  • IdleHandler 原理与应用
  • Choreographer VSYNC 机制
  • HandlerThread 源码实现
  • AsyncTask 底层原理

相关文件

  • ./01-Handler基本概念.md - Handler 定义、四大核心类关系、基本工作机制
  • ./02-Handler使用方式.md - sendMessage、post、postDelayed 等 API 详解