Looper.loop() 循环机制

4 阅读28分钟

Looper.loop() 循环机制

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

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

一、核心概念

1.1 定义与作用

一句话定义: Looper.loop() 是 Android 消息机制的核心循环方法,通过无限循环不断从 MessageQueue 中取出消息并分发给对应的 Handler 处理,支持通过 epoll 机制实现高效的阻塞和唤醒,确保主线程在无消息时释放 CPU 资源。

为什么重要

  • Android 架构基石:主线程的所有生命周期回调、UI 更新、事件分发都依赖 loop() 循环
  • 面试必考核心:loop() 为什么不会 ANR、epoll 阻塞机制、消息分发流程是面试最高频考点
  • 性能关键:理解 loop() 循环机制才能优化消息处理性能、定位 ANR 问题
  • 深入源码必经之路:loop() 源码简洁但设计精妙,是理解 Android Framework 的关键

1.2 与其他概念的关系

Looper.loop() 是 Looper 原理的核心运行阶段,与其他概念的关系:

  • 与 Looper 创建:prepare() 创建 Looper 和 MessageQueue,loop() 启动循环(详见 ./01-Looper创建与启动.md
  • 与 MessageQueue:loop() 通过 queue.next() 不断从消息队列取消息
  • 与 Handler:loop() 将消息分发给 msg.target(Handler)处理
  • 与 epoll 机制:loop() 通过 nativePollOnce() 实现阻塞和唤醒
  • 与 ThreadLocal:loop() 通过 ThreadLocal 获取当前线程的 Looper(详见 ./03-ThreadLocal机制.md

边界说明: 本文专注于 loop() 方法的循环机制,包括消息获取、阻塞唤醒、消息分发、回收机制,不涉及 Looper 创建流程和 ThreadLocal 实现原理。


二、核心原理

2.1 Looper.loop() 整体流程

2.1.1 核心流程概览
Looper.loop() 无限循环
    ↓
for (;;) {
    ↓
    1. MessageQueue.next() 取消息(可能阻塞)
        ├─ 队列为空 → nativePollOnce(-1) 无限阻塞
        ├─ 有延迟消息 → nativePollOnce(delay) 定时阻塞
        └─ 有即时消息 → 立即返回消息
    ↓
    2. 检查消息是否为 null
        ├─ null → 队列退出,return 结束循环
        └─ 非 null → 继续处理
    ↓
    3. 消息分发前的日志/监控
        └─ Printer.println(">>>>> Dispatching...")
    ↓
    4. msg.target.dispatchMessage(msg)
        └─ Handler 处理消息
    ↓
    5. 消息分发后的日志/监控
        └─ Printer.println("<<<<< Finished...")
    ↓
    6. msg.recycleUnchecked() 回收消息
    ↓
    继续下一次循环
}

关键设计

  1. 无限循环:for(;;) 持续运行,只有 quit() 时才退出
  2. 阻塞等待:无消息时通过 epoll 阻塞,不占用 CPU
  3. 消息分发:通过 msg.target 将消息回调给发送它的 Handler
  4. 日志监控:提供 setMessageLogging() 接口监控消息处理

2.2 源码分析

2.2.1 Looper.loop() 主循环
// 【Android 12】frameworks/base/core/java/android/os/Looper.java

public static void loop() {
    // ★获取当前线程的 Looper
    final Looper me = myLooper();
    if (me == null) {
        throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
    }

    // 标记进入循环状态
    if (me.mInLoop) {
        Slog.w(TAG, "Loop again would have the queued messages be executed"
                + " before this one completed.");
    }
    me.mInLoop = true;

    // 获取消息队列
    final MessageQueue queue = me.mQueue;

    // 确保此线程的身份是本地进程
    Binder.clearCallingIdentity();
    final long ident = Binder.clearCallingIdentity();

    // 允许通过系统属性覆盖慢消息阈值
    int thresholdOverride = SystemProperties.getInt("log.looper."
            + Process.myUid() + "."
            + Thread.currentThread().getName()
            + ".slow", 0);

    // 是否开启慢消息检测
    boolean slowDeliveryDetected = false;

    // ★核心:无限循环
    for (;;) {
        // ★调用 loopOnce() 处理一次消息
        if (!loopOnce(me, ident, thresholdOverride)) {
            return;  // loopOnce 返回 false 表示队列退出
        }
    }
}

关键点

  1. 前置检查:确保当前线程已调用 prepare() 创建 Looper
  2. Binder 身份:清除远程调用者身份,确保是本地进程
  3. 无限循环:for(;;) 只有在 quit() 时通过 loopOnce() 返回 false 才退出
  4. 性能监控:支持通过系统属性配置慢消息阈值
2.2.2 loopOnce() 处理单次循环
// 【Android 12】frameworks/base/core/java/android/os/Looper.java

private static boolean loopOnce(final Looper me,
        final long ident, final int thresholdOverride) {
    // ★步骤1:从消息队列取消息(可能阻塞)
    Message msg = me.mQueue.next(); // might block
    if (msg == null) {
        // ★队列退出,返回 false 结束循环
        return false;
    }

    // ★步骤2:消息分发前的日志打印
    final Printer logging = me.mLogging;
    if (logging != null) {
        logging.println(">>>>> Dispatching to " + msg.target + " "
                + msg.callback + ": " + msg.what);
    }
    // ★步骤2.1:消息分发前的观察者回调
    final Observer observer = sObserver;
    final long traceTag = me.mTraceTag;
    long slowDispatchThresholdMs = me.mSlowDispatchThresholdMs;
    long slowDeliveryThresholdMs = me.mSlowDeliveryThresholdMs;

    // 如果有阈值覆盖,使用覆盖值
    if (thresholdOverride > 0) {
        slowDispatchThresholdMs = thresholdOverride;
        slowDeliveryThresholdMs = thresholdOverride;
    }

    // 慢消息检测
    final boolean logSlowDelivery = (slowDeliveryThresholdMs > 0) && (msg.when > 0);
    final boolean logSlowDispatch = (slowDispatchThresholdMs > 0);

    final boolean needStartTime = logSlowDelivery || logSlowDispatch;
    final boolean needEndTime = logSlowDispatch;

    if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
        Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
    }

    final long dispatchStart = needStartTime ? SystemClock.uptimeMillis() : 0;
    final long dispatchEnd;
    Object token = null;
    if (observer != null) {
        token = observer.messageDispatchStarting();
    }

    long origWorkSource = ThreadLocalWorkSource.setUid(msg.workSourceUid);
    try {
        // ★步骤3:分发消息给 Handler
        msg.target.dispatchMessage(msg);
        if (observer != null) {
            observer.messageDispatched(token, msg);
        }
        dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
    } catch (Exception exception) {
        if (observer != null) {
            observer.dispatchingThrewException(token, msg, exception);
        }
        throw exception;
    } finally {
        ThreadLocalWorkSource.restore(origWorkSource);
        if (traceTag != 0) {
            Trace.traceEnd(traceTag);
        }
    }

    // ★步骤4:消息分发后的日志打印
    if (logging != null) {
        logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
    }

    // ★步骤5:慢消息检测与警告
    if (logSlowDelivery) {
        if (me.mSlowDeliveryDetected) {
            if ((dispatchStart - msg.when) <= 10) {
                Slog.w(TAG, "Drained");
                me.mSlowDeliveryDetected = false;
            }
        } else {
            if (showSlowLog(slowDeliveryThresholdMs, msg.when, dispatchStart, "delivery",
                    msg)) {
                me.mSlowDeliveryDetected = true;
            }
        }
    }
    if (logSlowDispatch) {
        showSlowLog(slowDispatchThresholdMs, dispatchStart, dispatchEnd, "dispatch", msg);
    }

    // 确保线程身份没有被破坏
    final long newIdent = Binder.clearCallingIdentity();
    if (ident != newIdent) {
        Log.wtf(TAG, "Thread identity changed from 0x"
                + Long.toHexString(ident) + " to 0x"
                + Long.toHexString(newIdent) + " while dispatching to "
                + msg.target.getClass().getName() + " "
                + msg.callback + " what=" + msg.what);
    }

    // ★步骤6:回收消息到对象池
    msg.recycleUnchecked();

    return true;  // 继续循环
}

关键步骤

  1. queue.next():阻塞式获取消息
  2. 日志打印>>>>> Dispatching 标记开始处理
  3. 消息分发msg.target.dispatchMessage(msg)
  4. 日志打印<<<<< Finished 标记处理完成
  5. 性能监控:检测慢消息并警告
  6. 消息回收recycleUnchecked() 放回对象池

2.3 MessageQueue.next() 阻塞机制

2.3.1 next() 核心源码
// 【Android 12】frameworks/base/core/java/android/os/MessageQueue.java

Message next() {
    // Native 层 MessageQueue 的指针
    final long ptr = mPtr;
    if (ptr == 0) {
        // 队列已销毁,返回 null
        return null;
    }

    int pendingIdleHandlerCount = -1; // -1 表示第一次迭代
    int nextPollTimeoutMillis = 0;    // 下次阻塞时长

    // ★无限循环,直到取到消息或队列退出
    for (;;) {
        if (nextPollTimeoutMillis != 0) {
            Binder.flushPendingCommands();
        }

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

        synchronized (this) {
            // 获取当前时间
            final long now = SystemClock.uptimeMillis();
            Message prevMsg = null;
            Message msg = mMessages;  // 队列头节点

            // ★同步屏障处理:跳过同步消息,找异步消息
            if (msg != null && msg.target == null) {
                // 队列头是同步屏障(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 {
                    // ★消息到时间了,取出返回
                    mBlocked = false;
                    if (prevMsg != null) {
                        prevMsg.next = msg.next;
                    } else {
                        mMessages = msg.next;
                    }
                    msg.next = null;
                    if (DEBUG) Log.v(TAG, "Returning message: " + msg);
                    msg.markInUse();
                    return msg;  // ★返回消息
                }
            } else {
                // ★队列为空,无限阻塞
                nextPollTimeoutMillis = -1;
            }

            // ★检查队列是否退出
            if (mQuitting) {
                dispose();
                return null;  // 返回 null,loop() 会结束循环
            }

            // ★IdleHandler 处理(队列空闲时执行)
            if (pendingIdleHandlerCount < 0
                    && (mMessages == null || now < mMessages.when)) {
                pendingIdleHandlerCount = mIdleHandlers.size();
            }
            if (pendingIdleHandlerCount <= 0) {
                // 没有 IdleHandler,标记阻塞状态
                mBlocked = true;
                continue;  // 继续循环
            }

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

        // ★执行 IdleHandler(在 synchronized 外执行,避免长时间持锁)
        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);
                }
            }
        }

        // IdleHandler 执行后,重置计数和超时
        pendingIdleHandlerCount = 0;
        nextPollTimeoutMillis = 0;  // IdleHandler 执行后立即检查消息
    }
}

阻塞时长计算

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

关键逻辑

  1. nativePollOnce():Native 层阻塞,通过 epoll 等待
  2. 同步屏障处理:跳过同步消息,优先处理异步消息
  3. 时间检查:判断消息是否到执行时间
  4. IdleHandler:队列空闲时执行回调
2.3.2 epoll 阻塞与唤醒机制

Native 层阻塞(nativePollOnce)

// 【Android 12】frameworks/base/core/jni/android_os_MessageQueue.cpp

static void android_os_MessageQueue_nativePollOnce(JNIEnv* env, jclass clazz,
        jlong ptr, jint timeoutMillis) {
    NativeMessageQueue* nativeMessageQueue = reinterpret_cast<NativeMessageQueue*>(ptr);
    nativeMessageQueue->pollOnce(env, obj, timeoutMillis);
}

void NativeMessageQueue::pollOnce(JNIEnv* env, jobject pollObj, int timeoutMillis) {
    mPollEnv = env;
    mPollObj = pollObj;
    mLooper->pollOnce(timeoutMillis);  // 调用 Native Looper
    mPollObj = NULL;
    mPollEnv = NULL;
}

// 【Android 12】system/core/libutils/Looper.cpp

int Looper::pollOnce(int timeoutMillis, int* outFd, int* outEvents, void** outData) {
    int result = 0;
    for (;;) {
        // 处理 Native 消息(如果有)
        while (mMessageEnvelopes.size() != 0) {
            nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
            const MessageEnvelope& messageEnvelope = mMessageEnvelopes.itemAt(0);
            if (messageEnvelope.uptime <= now) {
                // ...
            } else {
                timeoutMillis = toMillisecondTimeoutDelay(now, messageEnvelope.uptime);
                break;
            }
        }

        // ★调用 pollInner 阻塞等待
        result = pollInner(timeoutMillis);
    }
}

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 == mWakeEventFd) {
            // ★唤醒事件
            if (epollEvents & EPOLLIN) {
                awoken();  // 读取唤醒数据
            }
        } else {
            // 其他事件(如输入事件、VSYNC 等)
            // ...
        }
    }

    return POLL_WAKE;
}

void Looper::awoken() {
    uint64_t counter;
    // ★读取 eventfd,清空唤醒标志
    TEMP_FAILURE_RETRY(read(mWakeEventFd, &counter, sizeof(uint64_t)));
}

Native 层唤醒(nativeWake)

// 【Android 12】frameworks/base/core/jni/android_os_MessageQueue.cpp

static void android_os_MessageQueue_nativeWake(JNIEnv* env, jclass clazz, jlong ptr) {
    NativeMessageQueue* nativeMessageQueue = reinterpret_cast<NativeMessageQueue*>(ptr);
    nativeMessageQueue->wake();
}

void NativeMessageQueue::wake() {
    mLooper->wake();
}

// 【Android 12】system/core/libutils/Looper.cpp

void Looper::wake() {
    uint64_t inc = 1;
    // ★向 eventfd 写入数据,触发 epoll_wait 返回
    ssize_t nWrite = TEMP_FAILURE_RETRY(write(mWakeEventFd, &inc, sizeof(uint64_t)));
    if (nWrite != sizeof(uint64_t)) {
        if (errno != EAGAIN) {
            LOG_ALWAYS_FATAL("Could not write wake signal to fd %d: %s",
                    mWakeEventFd, strerror(errno));
        }
    }
}

epoll 机制流程

1. 创建阶段(Looper 构造函数):
   mWakeEventFd = eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC)
   mEpollFd = epoll_create1(EPOLL_CLOEXEC)
   epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mWakeEventFd, ...)  // 监听 eventfd

2. 阻塞阶段(nativePollOnce):
   epoll_wait(mEpollFd, ..., timeoutMillis)
      ↓
   线程进入休眠(内核态),不占用 CPU
      ↓
   等待以下事件:
   - mWakeEventFd 可读(nativeWake 写入)
   - timeout 超时
   - 其他 fd 事件(输入、VSYNC 等)

3. 唤醒阶段(nativeWake):
   write(mWakeEventFd, ...)
      ↓
   内核检测到 mWakeEventFd 可读
      ↓
   epoll_wait 返回
      ↓
   read(mWakeEventFd, ...)  清空数据
      ↓
   线程唤醒,继续执行

关键点

  1. eventfd:Linux 文件描述符,专门用于事件通知
  2. epoll:高效的 I/O 多路复用机制,可同时监听多个 fd
  3. 阻塞不占 CPU:线程在内核态休眠,不消耗 CPU 资源
  4. 唤醒迅速:写入 eventfd 立即触发 epoll_wait 返回

2.4 消息分发机制

2.4.1 Handler.dispatchMessage() 三级分发
// 【Android 12】frameworks/base/core/java/android/os/Handler.java

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) {
    // 默认空实现,子类重写
}

三级优先级

  1. msg.callback:post(Runnable) 方式,直接执行 Runnable.run()
  2. mCallback:Handler 构造时传入 Callback 接口,可拦截消息
  3. handleMessage():Handler 子类重写的方法

应用场景

// 场景1:post 方式(优先级1)
handler.post(() -> {
    // 直接执行,不走 handleMessage
    updateUI();
});

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

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

2.5 消息回收机制

2.5.1 Message 对象池
// 【Android 12】frameworks/base/core/java/android/os/Message.java

// 对象池链表头节点
private static Message sPool;

// 对象池当前大小
private static int sPoolSize = 0;

// 对象池最大容量
private static final int MAX_POOL_SIZE = 50;

// 对象池同步锁
private static final Object sPoolSync = new Object();

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++;
        }
    }
}

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();
}

对象池结构

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

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

为什么需要对象池

  1. 减少 GC 压力:避免频繁创建销毁 Message 对象
  2. 提高性能:复用对象比创建新对象快
  3. 容量限制:最多缓存 50 个,避免内存占用过大

2.6 重要细节与边界条件

细节1:为什么 loop() 是死循环不会 ANR?

原因分析

  1. 主线程本就应该持续运行

    • 主线程不是执行完任务就结束的
    • 所有 UI 事件、生命周期回调都需要主线程持续处理
    • loop() 是正常工作状态,不是异常
  2. 阻塞时主动释放 CPU

    无消息时:
    queue.next() → nativePollOnce(-1) → epoll_wait() → 线程休眠
    
    线程状态:WAITING(等待状态),不占用 CPU
    
  3. ANR 的真正原因

    • ANR 是因为单个消息处理时间过长(>5秒)
    • 不是因为 loop() 循环

验证

// ✅ 不会 ANR:1000 个消息,每个 10ms
for (int i = 0; i < 1000; i++) {
    handler.post(() -> {
        SystemClock.sleep(10);  // 单个消息 10ms
    });
}
// 总共 10 秒,但每个消息都能快速处理,不会 ANR

// ❌ 会 ANR:1 个消息,6 秒
handler.post(() -> {
    SystemClock.sleep(6000);  // 单个消息 6 秒 → ANR
});
细节2:IdleHandler 的执行时机

执行条件(满足任一):

  1. 消息队列为空(mMessages == null
  2. 队头消息未到执行时间(now < mMessages.when

执行流程

// MessageQueue.next() 中
if (pendingIdleHandlerCount < 0
        && (mMessages == null || now < mMessages.when)) {
    // ★队列空闲,准备执行 IdleHandler
    pendingIdleHandlerCount = mIdleHandlers.size();
}

// ...

// 执行所有 IdleHandler
for (int i = 0; i < pendingIdleHandlerCount; i++) {
    final IdleHandler idler = mPendingIdleHandlers[i];
    boolean keep = idler.queueIdle();
    if (!keep) {
        mIdleHandlers.remove(idler);  // 返回 false,移除
    }
}

应用场景

// Activity 启动后空闲时预加载
Looper.myQueue().addIdleHandler(() -> {
    preloadResources();
    return false;  // 只执行一次
});
细节3:同步屏障机制

同步屏障特点

  • msg.target == null(普通消息的 target 指向 Handler)
  • 阻塞所有同步消息,只处理异步消息
  • 系统级 API(@hide),应用层无法直接使用

next() 中的处理

// 检测到同步屏障
if (msg != null && msg.target == null) {
    // ★跳过所有同步消息
    do {
        prevMsg = msg;
        msg = msg.next;
    } while (msg != null && !msg.isAsynchronous());
}

应用场景:View 绘制

// ViewRootImpl.scheduleTraversals()
void scheduleTraversals() {
    // 插入同步屏障
    mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
    // 发送异步消息
    mChoreographer.postCallback(CALLBACK_TRAVERSAL, mTraversalRunnable, null);
}

三、实际应用

3.1 典型场景

场景1:监控主线程消息处理(ANR 检测)

需求:检测主线程消息处理是否耗时过长

实现方式

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

    public static void install() {
        Looper.getMainLooper().setMessageLogging(new Printer() {
            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) {
                        // 耗时过长,记录堆栈
                        String stack = getMainThreadStack();
                        Log.w("LooperMonitor", "Slow message: " + duration + "ms\n" + stack);
                    }
                }
            }
        });
    }

    private static String getMainThreadStack() {
        StackTraceElement[] traces = Looper.getMainLooper().getThread().getStackTrace();
        StringBuilder sb = new StringBuilder();
        for (StackTraceElement trace : traces) {
            sb.append(trace.toString()).append("\n");
        }
        return sb.toString();
    }
}

原理

  • loop() 在消息分发前后打印日志
  • 通过时间差计算消息处理耗时
  • BlockCanary 库就是基于此原理实现
场景2:IdleHandler 实现启动优化

需求:Activity 启动后延迟初始化非关键组件

实现方式

public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // 关键初始化
        initCriticalComponents();

        // ★非关键初始化延迟到空闲时
        Looper.myQueue().addIdleHandler(() -> {
            // 预加载图片
            preloadImages();
            // 初始化第三方 SDK
            initAnalytics();
            return false;  // 执行一次后移除
        });
    }
}

优势

  • 不阻塞主线程关键任务
  • 利用空闲时间执行低优先级任务
  • 提升启动速度
场景3:使用 Handler.Callback 统一日志

需求:统一记录所有 Handler 消息处理日志

实现方式

public class LoggingHandler extends Handler {
    private static final String TAG = "LoggingHandler";

    public LoggingHandler() {
        super(new Callback() {
            @Override
            public boolean handleMessage(Message msg) {
                // ★统一日志
                Log.d(TAG, "Handle message: what=" + msg.what);
                return false;  // 不拦截,继续传递给 handleMessage
            }
        });
    }

    @Override
    public void handleMessage(Message msg) {
        // 子类实现具体逻辑
        switch (msg.what) {
            case MSG_UPDATE:
                // ...
                break;
        }
    }
}

3.2 最佳实践

✅ 推荐做法

1. 主线程避免耗时操作

// ✅ 正确:耗时操作在子线程
new Thread(() -> {
    String data = loadDataFromNetwork();  // 耗时
    handler.post(() -> {
        textView.setText(data);  // UI 更新在主线程
    });
}).start();

// ❌ 错误:主线程执行耗时操作
handler.post(() -> {
    String data = loadDataFromNetwork();  // 会阻塞主线程
    textView.setText(data);
});

2. 使用 Message.obtain() 复用对象

// ✅ 推荐:从对象池获取
Message msg = Message.obtain();
msg.what = MSG_UPDATE;
handler.sendMessage(msg);

// ❌ 不推荐:频繁创建新对象
Message msg = new Message();
msg.what = MSG_UPDATE;
handler.sendMessage(msg);

3. IdleHandler 只执行轻量级任务

// ✅ 正确:轻量级任务
Looper.myQueue().addIdleHandler(() -> {
    preloadSmallIcons();  // 快速完成
    return false;
});

// ❌ 错误:耗时任务
Looper.myQueue().addIdleHandler(() -> {
    downloadLargeFile();  // 阻塞主线程
    return false;
});

4. 及时移除不需要的消息

@Override
protected void onDestroy() {
    super.onDestroy();
    // ★清空所有消息
    handler.removeCallbacksAndMessages(null);
}
❌ 常见错误

错误1:误以为 loop() 会卡死主线程

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

// ✅ 正确理解
// loop() 无消息时通过 epoll 阻塞,不占用 CPU
// 主线程必须持续运行,loop() 是正常工作状态

错误2:在 IdleHandler 中执行耗时操作

// ❌ 错误
Looper.myQueue().addIdleHandler(() -> {
    // 大量计算,阻塞主线程
    for (int i = 0; i < 1000000; i++) {
        doHeavyWork();
    }
    return false;
});

// ✅ 正确:耗时操作放子线程
Looper.myQueue().addIdleHandler(() -> {
    new Thread(() -> {
        doHeavyWork();
    }).start();
    return false;
});

错误3:忘记在 Callback 中返回 false

// ❌ 错误:返回 true 拦截了所有消息
Handler handler = new Handler(msg -> {
    logMessage(msg);
    return true;  // ★拦截,handleMessage 永远不会调用
});

@Override
public void handleMessage(Message msg) {
    // 永远不会执行
}

// ✅ 正确:返回 false 继续传递
Handler handler = new Handler(msg -> {
    logMessage(msg);
    return false;  // ★不拦截
});

3.3 性能优化建议

1. 避免频繁发送消息

// ❌ 不推荐:频繁发送
onScrollChanged() {
    handler.sendEmptyMessage(MSG_UPDATE);  // 滑动时频繁触发
}

// ✅ 推荐:合并消息
onScrollChanged() {
    handler.removeMessages(MSG_UPDATE);  // 移除之前的
    handler.sendEmptyMessageDelayed(MSG_UPDATE, 16);  // 延迟 16ms
}

2. 监控慢消息

// 设置慢消息日志
Looper.getMainLooper().setMessageLogging(new Printer() {
    @Override
    public void println(String x) {
        if (x.startsWith(">>>>> Dispatching")) {
            // 记录开始时间
        } else if (x.startsWith("<<<<< Finished")) {
            // 检查耗时
        }
    }
});

四、面试真题解析

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

【高频题1】Looper.loop() 的核心流程是什么?

标准答案(30秒): Looper.loop() 是一个无限循环,不断从 MessageQueue.next() 取消息并分发处理。具体流程是:第一步通过 queue.next() 获取消息,如果队列为空会调用 nativePollOnce() 阻塞等待;第二步检查消息是否为 null,如果是 null 说明队列退出,结束循环;第三步在消息分发前打印日志(如果设置了 MessageLogging);第四步调用 msg.target.dispatchMessage() 将消息分发给 Handler 处理;第五步打印处理完成日志;第六步调用 msg.recycleUnchecked() 回收消息到对象池,然后继续下一次循环。

深入展开(追问后)

完整流程源码

public static void loop() {
    final Looper me = myLooper();
    final MessageQueue queue = me.mQueue;

    for (;;) {  // ★无限循环
        // 1. 取消息(可能阻塞)
        Message msg = queue.next();
        if (msg == null) {
            return;  // 队列退出
        }

        // 2. 日志打印(开始)
        if (logging != null) {
            logging.println(">>>>> Dispatching to " + msg.target);
        }

        // 3. 分发消息
        msg.target.dispatchMessage(msg);

        // 4. 日志打印(结束)
        if (logging != null) {
            logging.println("<<<<< Finished to " + msg.target);
        }

        // 5. 回收消息
        msg.recycleUnchecked();
    }
}

面试官追问

追问1:为什么需要无限循环?

答:主线程的职责是持续处理事件,不应该执行完就结束。所有生命周期回调、UI 更新、输入事件都需要主线程持续运行。如果 loop() 退出,应用就无法响应任何事件了,所以 ActivityThread.main() 在 loop() 后会抛出异常。

追问2:如果 queue.next() 一直返回 null 会怎样?

答:只有调用 quit() 退出队列时,next() 才会返回 null。主线程的 MessageQueue 不允许退出(quitAllowed = false),所以主线程的 next() 永远不会返回 null。子线程调用 quit() 后,next() 返回 null,loop() 结束,线程可以退出。


【高频题2】MessageQueue.next() 如何实现阻塞的?

标准答案(30秒): MessageQueue.next() 通过 Native 层的 nativePollOnce() 方法实现阻塞。当队列为空时,传入 -1 表示无限阻塞;有延迟消息时,传入延迟时间表示定时阻塞。底层使用 Linux 的 epoll 机制,调用 epoll_wait() 让线程进入休眠状态,不占用 CPU。当有新消息入队时,MessageQueue.enqueueMessage() 会调用 nativeWake(),向 eventfd 写入数据,触发 epoll_wait() 返回,线程被唤醒继续执行。

深入展开(追问后)

阻塞时长计算

Message next() {
    int nextPollTimeoutMillis = 0;

    for (;;) {
        nativePollOnce(ptr, nextPollTimeoutMillis);  // ★阻塞

        synchronized (this) {
            Message msg = mMessages;
            if (msg != null) {
                if (now < msg.when) {
                    // ★计算阻塞时长
                    nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                } else {
                    return msg;  // 返回消息
                }
            } else {
                // ★队列为空,无限阻塞
                nextPollTimeoutMillis = -1;
            }
        }
    }
}

epoll 机制流程

阻塞:
nativePollOnce(timeout)
  → pollInner(timeout)
  → epoll_wait(mEpollFd, timeout)
  → 线程休眠,不占用 CPU

唤醒:
nativeWake()
  → write(mWakeEventFd)
  → epoll_wait() 检测到 fd 可读
  → 返回
  → read(mWakeEventFd) 清空数据
  → 线程唤醒

面试官追问

追问1:为什么使用 epoll 而不是 Object.wait()/notify()?

答:主要原因有三点:

  1. 跨语言支持:epoll 是 Linux 系统调用,Java 和 Native 层都可以使用
  2. 多事件监听:epoll 可以同时监听多个文件描述符(消息、输入事件、VSYNC 等)
  3. 精确定时:epoll_wait 支持超时参数,可以精确控制阻塞时长
  4. 跨进程唤醒:Binder 驱动可以通过 epoll 唤醒应用进程

追问2:如果没有 epoll 阻塞会怎样?

答:如果 loop() 不阻塞,会导致:

// 假设没有阻塞机制
for (;;) {
    Message msg = queue.next();  // 立即返回,无消息返回 null
    if (msg == null) {
        continue;  // ★CPU 空转,100% 占用
    }
    msg.target.dispatchMessage(msg);
}

CPU 会一直空转检查消息队列,导致 CPU 100% 占用,电量快速消耗,设备发热。


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

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

深入展开(追问后)

源码实现

public void dispatchMessage(Message msg) {
    // 优先级1:msg.callback (post 方式)
    if (msg.callback != null) {
        handleCallback(msg);  // msg.callback.run()
    } else {
        // 优先级2:mCallback (拦截器)
        if (mCallback != null) {
            if (mCallback.handleMessage(msg)) {
                return;  // 返回 true,拦截
            }
        }
        // 优先级3:handleMessage (子类重写)
        handleMessage(msg);
    }
}

三种使用场景

// 场景1:post 方式(优先级1)
handler.post(() -> {
    updateUI();  // 直接执行,不经过 handleMessage
});

// 场景2:Callback 拦截(优先级2)
Handler handler = new Handler(msg -> {
    // 统一日志
    log(msg);
    // 错误消息拦截
    if (msg.what == MSG_ERROR) {
        handleError(msg);
        return true;  // 拦截
    }
    return false;  // 继续传递
});

// 场景3:子类重写(优先级3)
Handler handler = new Handler() {
    @Override
    public void handleMessage(Message msg) {
        switch (msg.what) {
            case MSG_UPDATE:
                // 处理逻辑
                break;
        }
    }
};

面试官追问

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

答:这是责任链模式的应用。mCallback 作为中间层,可以:

  1. 统一处理日志、监控
  2. 根据条件决定是否继续传递
  3. 在不修改 Handler 子类的情况下扩展功能

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

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

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

// 在子线程调用
new Thread(() -> {
    mainHandler.post(() -> {
        // ★这里在主线程执行
        updateUI();
    });
}).start();

【高频题4】为什么 Looper.loop() 是死循环不会导致 ANR?

标准答案(30秒): loop() 虽然是死循环,但不会导致 ANR,原因有三点:第一,主线程本来就应该持续运行处理事件,loop() 是正常工作状态不是异常;第二,当没有消息时,MessageQueue.next() 会调用 nativePollOnce() 进入阻塞状态,通过 epoll 机制让线程休眠,不占用 CPU 资源;第三,ANR 是因为单个消息处理时间过长超过 5 秒,不是因为 loop() 循环本身。实际上如果 loop() 退出了,主线程无法处理任何事件,应用反而会崩溃。

深入展开(追问后)

线程状态分析

有消息时:
loop() → queue.next() → return msg → dispatchMessage()
线程状态:RUNNABLE(运行状态)

无消息时:
loop() → queue.next() → nativePollOnce(-1) → epoll_wait()
线程状态:WAITING(等待状态),不占用 CPU

ANR 的真正原因

// ✅ 不会 ANR:1000 个消息,每个 10ms
for (int i = 0; i < 1000; i++) {
    handler.post(() -> {
        SystemClock.sleep(10);  // 每个消息快速处理
    });
}
// 虽然总共 10 秒,但每个消息都能及时处理,不会 ANR

// ❌ 会 ANR:1 个消息,6 秒
handler.post(() -> {
    SystemClock.sleep(6000);  // 单个消息超过 5 秒 → ANR
});

如果 loop() 退出的后果

// ActivityThread.main()
Looper.loop();  // 正常情况永不返回

// 如果返回了,说明发生严重错误
throw new RuntimeException("Main thread loop unexpectedly exited");

面试官追问

追问1:如何验证 loop() 阻塞时不占用 CPU?

答:可以通过以下方式验证:

  1. adb shell top 命令:查看主线程 CPU 占用,无消息时接近 0%
  2. systrace 工具:查看线程状态,显示为 Sleeping 状态
  3. 代码验证
// 主线程空闲时
Looper.myQueue().addIdleHandler(() -> {
    Log.d("TAG", "Main thread idle");  // 说明线程在休眠
    return true;
});

追问2:主线程一直有消息会导致 ANR 吗?

答:不一定,取决于单个消息的处理时间:

场景单个消息耗时消息数量总耗时是否 ANR
场景110ms100010s❌ 不会
场景2100ms10010s❌ 不会
场景36000ms16s✅ 会 ANR

ANR 的判断依据是输入事件 5 秒无响应,而非消息队列是否繁忙。


【高频题5】IdleHandler 的作用和执行时机是什么?

标准答案(30秒): IdleHandler 是 MessageQueue 提供的空闲回调接口,在消息队列空闲时执行低优先级任务。执行时机有两种情况:第一是消息队列为空;第二是队头消息还未到执行时间。IdleHandler.queueIdle() 返回 false 表示执行一次后移除,返回 true 表示保留下次继续执行。典型应用场景是 Activity 启动后预加载资源、空闲时执行 GC、延迟初始化非关键组件等。

深入展开(追问后)

执行时机源码

Message next() {
    for (;;) {
        nativePollOnce(ptr, nextPollTimeoutMillis);

        synchronized (this) {
            Message msg = mMessages;
            // ...

            // ★执行 IdleHandler 的条件
            if (pendingIdleHandlerCount < 0
                    && (mMessages == null || now < mMessages.when)) {
                pendingIdleHandlerCount = mIdleHandlers.size();
            }

            // 执行 IdleHandler
            for (int i = 0; i < pendingIdleHandlerCount; i++) {
                boolean keep = idler.queueIdle();
                if (!keep) {
                    mIdleHandlers.remove(idler);
                }
            }
        }
    }
}

使用示例

// 示例1:Activity 启动优化
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    // 关键初始化
    initCriticalComponents();

    // ★非关键初始化延迟到空闲时
    Looper.myQueue().addIdleHandler(() -> {
        preloadImages();
        initAnalytics();
        return false;  // 只执行一次
    });
}

// 示例2:GC 优化(ActivityThread 中的应用)
Looper.myQueue().addIdleHandler(new GcIdler());

class GcIdler implements IdleHandler {
    public boolean queueIdle() {
        doGcIfNeeded();
        return false;
    }
}

面试官追问

追问1:IdleHandler 会影响性能吗?

答:如果使用不当,会影响性能

  1. 返回 true 导致重复执行
// ❌ 错误:每次空闲都执行耗时任务
Looper.myQueue().addIdleHandler(() -> {
    doHeavyWork();  // 耗时操作
    return true;  // 每次空闲都执行,影响性能
});

// ✅ 正确:执行一次后移除
Looper.myQueue().addIdleHandler(() -> {
    doHeavyWork();
    return false;
});
  1. 执行耗时任务阻塞主线程
// ❌ 错误:主线程执行耗时任务
Looper.myQueue().addIdleHandler(() -> {
    downloadLargeFile();  // 阻塞主线程
    return false;
});

// ✅ 正确:耗时任务放子线程
Looper.myQueue().addIdleHandler(() -> {
    new Thread(() -> {
        downloadLargeFile();
    }).start();
    return false;
});

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

答:

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

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

【进阶题1】同步屏障(Sync Barrier)的工作原理是什么?在什么场景使用?

参考答案

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

工作原理

// MessageQueue.next() 中的处理
Message next() {
    for (;;) {
        nativePollOnce(ptr, nextPollTimeoutMillis);

        synchronized (this) {
            Message msg = mMessages;

            // ★检测到同步屏障
            if (msg != null && msg.target == null) {
                // 跳过所有同步消息,找第一个异步消息
                do {
                    prevMsg = msg;
                    msg = msg.next;
                } while (msg != null && !msg.isAsynchronous());
            }

            // 返回异步消息(或 null)
            if (msg != null) {
                return msg;
            }
        }
    }
}

应用场景:View 绘制

// ViewRootImpl.scheduleTraversals()
void scheduleTraversals() {
    if (!mTraversalScheduled) {
        mTraversalScheduled = true;
        // ★插入同步屏障
        mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
        // 发送异步消息(绘制任务)
        mChoreographer.postCallback(CALLBACK_TRAVERSAL, mTraversalRunnable, null);
    }
}

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

为什么需要同步屏障

View 绘制是时间敏感任务,需要在 16.6ms 内完成(60fps)。如果被其他同步消息延迟,会导致掉帧。通过同步屏障,绘制任务(异步消息)可以优先执行。

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

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

  1. 风险高:同步屏障会阻塞所有同步消息,滥用会导致消息饿死
  2. 必须成对:插入后必须及时移除,否则会永久阻塞同步消息
  3. 系统级优化:只应该在可控场景下使用(View 绘制、输入事件)

【进阶题2】Message 对象池的设计原理是什么?为什么最大容量是 50?

参考答案

Message 对象池是单链表结构,使用享元模式实现对象复用。sPool 指向链表头节点,obtain() 从头部取出,recycleUnchecked() 插入到头部,最大容量 50 个。

设计原理

// 对象池结构
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;
            sPoolSize--;
            return m;
        }
    }
    return new Message();
}

// 回收对象
void recycleUnchecked() {
    // 清空所有字段(防止内存泄漏)
    flags = FLAG_IN_USE;
    what = 0;
    obj = null;
    // ...

    synchronized (sPoolSync) {
        if (sPoolSize < MAX_POOL_SIZE) {
            next = sPool;  // 插入头部
            sPool = this;
            sPoolSize++;
        }
    }
}

为什么最大容量是 50

  1. 性能和内存的平衡

    • 太小:无法满足高频消息场景,频繁创建对象
    • 太大:占用过多内存,Message 可能携带大量数据
  2. 实际场景验证

    • 普通应用:消息频率不高,50 个足够
    • 游戏等高频场景:50 个也能覆盖大部分情况
  3. GC 友好

    • 超过 50 个的 Message 直接被 GC 回收
    • 避免长期占用大量内存

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

答:防止内存泄漏。Message 可能持有 Activity、View、Bitmap 等大对象,如果不清空直接放回对象池,这些对象无法被 GC 回收:

// 场景:Message 持有 Activity 引用
Message msg = Message.obtain();
msg.obj = activity;  // 持有 Activity
handler.sendMessage(msg);

// 处理完后如果不清空
// msg 在对象池中,obj 仍然持有 activity
// activity 无法被 GC 回收 → 内存泄漏

// recycleUnchecked() 清空后
msg.obj = null;  // 释放引用,activity 可以被回收

【进阶题3】如何监控主线程消息处理实现 ANR 检测?

参考答案

通过 Looper.setMessageLogging() 监控消息处理时长。loop() 在消息分发前后会调用 Printer.println(),通过时间差计算消息处理耗时。

实现原理

public class ANRMonitor {
    private static final long THRESHOLD = 3000;  // 3秒阈值

    public static void install() {
        Looper.getMainLooper().setMessageLogging(new Printer() {
            private long startTime;
            private Handler handler = new Handler(Looper.getMainLooper());

            @Override
            public void println(String x) {
                if (x.startsWith(">>>>> Dispatching")) {
                    // ★消息开始处理
                    startTime = SystemClock.uptimeMillis();
                    // 延迟 3 秒检查是否还在处理
                    handler.postDelayed(() -> {
                        if (startTime > 0) {
                            // 3 秒后还没处理完,可能 ANR
                            String stack = getMainThreadStack();
                            reportANR(stack);
                        }
                    }, THRESHOLD);
                } else if (x.startsWith("<<<<< Finished")) {
                    // ★消息处理完成
                    startTime = 0;  // 标记完成
                    handler.removeCallbacksAndMessages(null);
                }
            }
        });
    }
}

BlockCanary 实现

public class BlockCanary {
    public void install() {
        Looper.getMainLooper().setMessageLogging(new LooperPrinter());
    }

    class LooperPrinter implements Printer {
        private long startTime;

        @Override
        public void println(String x) {
            if (x.startsWith(">>>>>")) {
                startTime = System.currentTimeMillis();
            } else if (x.startsWith("<<<<<")) {
                long duration = System.currentTimeMillis() - startTime;
                if (duration > BLOCK_THRESHOLD) {
                    // 收集堆栈、CPU、内存信息
                    BlockInfo info = collectBlockInfo();
                    notifyBlock(info);
                }
            }
        }
    }
}

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

答:

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

更好的方案

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

4.3 实战场景题

【场景题】主线程消息队列堆积导致 UI 卡顿,如何优化?

场景描述

一个列表页面滑动时会频繁发送刷新消息,导致消息队列堆积,UI 卡顿,滑动不流畅。

问题分析

  1. 根本原因:频繁发送消息导致 MessageQueue 堆积
  2. 表现:滑动时掉帧,帧率下降
  3. 诊断:通过 Choreographer 监控发现单帧耗时超过 16.6ms

解决方案

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

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

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

方案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) {
        // ★累积 10 个后一次性处理
        processBatch();
    }
}

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

追问

1. 方案缺点?

  • 方案1:可能丢失部分刷新请求,适合最终状态一致的场景
  • 方案2:主线程一直繁忙可能不执行
  • 方案3:延迟更新,用户体验稍差

2. 如何监控优化效果?

Choreographer.getInstance().postFrameCallback(new FrameCallback() {
    private long lastFrameTime;

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

五、对比与总结

5.1 关键方法对比

方法作用阻塞行为返回值调用位置
loop()开启消息循环通过 next() 间接阻塞void(永不返回)Looper 创建后
next()获取下一个消息直接阻塞(nativePollOnce)Message(或 null)loop() 内部
nativePollOnce()Native 层阻塞等待epoll_wait 阻塞voidnext() 内部
nativeWake()Native 层唤醒不阻塞voidenqueueMessage()
dispatchMessage()分发消息给 Handler不阻塞voidloop() 内部
recycleUnchecked()回收消息到对象池不阻塞voidloop() 内部

5.2 核心要点速记

一句话记忆: Looper.loop() 通过无限循环不断从 MessageQueue.next() 取消息并分发给 Handler 处理,无消息时通过 epoll 机制阻塞休眠不占用 CPU,有消息时唤醒继续执行,处理完后回收消息到对象池复用。

3个关键点

  1. 无限循环:for(;;) 持续运行,只有 quit() 时退出
  2. epoll 阻塞:无消息时通过 nativePollOnce() → epoll_wait() 休眠,不占 CPU
  3. 三级分发:msg.callback → mCallback → handleMessage

面试官最爱问

  1. loop() 的核心流程是什么?
  2. 为什么 loop() 是死循环不会 ANR?
  3. MessageQueue.next() 如何实现阻塞?
  4. Handler 消息分发的三级机制是什么?
  5. IdleHandler 的作用和执行时机?

六、关联知识点

前置知识

  • Looper 创建与启动(详见:./01-Looper创建与启动.md
  • Handler 消息机制概述(详见:../01-Handler基础/01-Handler基本概念.md

后续扩展

  • ThreadLocal 实现原理与内存泄漏(详见:./03-ThreadLocal机制.md
  • MessageQueue 消息队列原理
  • epoll I/O 多路复用机制
  • Choreographer VSYNC 机制
  • ANR 原理与定位

相关文件

  • ./01-Looper创建与启动.md - Looper 的 prepare() 和 loop() 调用时机
  • ./03-ThreadLocal机制.md - ThreadLocal 如何存储 Looper
  • ../01-Handler基础/03-Handler工作流程.md - Handler 完整工作流程