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 核心流程概览
scss
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() 回收消息
↓
继续下一次循环
}
关键设计:
- 无限循环:for(;;) 持续运行,只有 quit() 时才退出
- 阻塞等待:无消息时通过 epoll 阻塞,不占用 CPU
- 消息分发:通过 msg.target 将消息回调给发送它的 Handler
- 日志监控:提供 setMessageLogging() 接口监控消息处理
2.2 源码分析
2.2.1 Looper.loop() 主循环
java
// 【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 表示队列退出
}
}
}
关键点:
- 前置检查:确保当前线程已调用 prepare() 创建 Looper
- Binder 身份:清除远程调用者身份,确保是本地进程
- 无限循环:for(;;) 只有在 quit() 时通过 loopOnce() 返回 false 才退出
- 性能监控:支持通过系统属性配置慢消息阈值
2.2.2 loopOnce() 处理单次循环
java
// 【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; // 继续循环
}
关键步骤:
- queue.next():阻塞式获取消息
- 日志打印 :
>>>>> Dispatching标记开始处理 - 消息分发 :
msg.target.dispatchMessage(msg) - 日志打印 :
<<<<< Finished标记处理完成 - 性能监控:检测慢消息并警告
- 消息回收 :
recycleUnchecked()放回对象池
2.3 MessageQueue.next() 阻塞机制
2.3.1 next() 核心源码
java
// 【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 | 不阻塞,立即返回 |
关键逻辑:
- nativePollOnce():Native 层阻塞,通过 epoll 等待
- 同步屏障处理:跳过同步消息,优先处理异步消息
- 时间检查:判断消息是否到执行时间
- IdleHandler:队列空闲时执行回调
2.3.2 epoll 阻塞与唤醒机制
Native 层阻塞(nativePollOnce):
cpp
// 【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):
cpp
// 【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 机制流程:
scss
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, ...) 清空数据
↓
线程唤醒,继续执行
关键点:
- eventfd:Linux 文件描述符,专门用于事件通知
- epoll:高效的 I/O 多路复用机制,可同时监听多个 fd
- 阻塞不占 CPU:线程在内核态休眠,不消耗 CPU 资源
- 唤醒迅速:写入 eventfd 立即触发 epoll_wait 返回
2.4 消息分发机制
2.4.1 Handler.dispatchMessage() 三级分发
java
// 【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) {
// 默认空实现,子类重写
}
三级优先级:
- msg.callback:post(Runnable) 方式,直接执行 Runnable.run()
- mCallback:Handler 构造时传入 Callback 接口,可拦截消息
- handleMessage():Handler 子类重写的方法
应用场景:
java
// 场景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 对象池
java
// 【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();
}
对象池结构:
scss
sPool → [Message1] → [Message2] → [Message3] → ... → null
(最新回收) (次新) (次次新)
obtain() 从头部取出
recycle() 插入到头部
为什么需要对象池:
- 减少 GC 压力:避免频繁创建销毁 Message 对象
- 提高性能:复用对象比创建新对象快
- 容量限制:最多缓存 50 个,避免内存占用过大
2.6 重要细节与边界条件
细节1:为什么 loop() 是死循环不会 ANR?
原因分析:
-
主线程本就应该持续运行:
- 主线程不是执行完任务就结束的
- 所有 UI 事件、生命周期回调都需要主线程持续处理
- loop() 是正常工作状态,不是异常
-
阻塞时主动释放 CPU:
scss无消息时: queue.next() → nativePollOnce(-1) → epoll_wait() → 线程休眠 线程状态:WAITING(等待状态),不占用 CPU -
ANR 的真正原因:
- ANR 是因为单个消息处理时间过长(>5秒)
- 不是因为 loop() 循环
验证:
java
// ✅ 不会 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 的执行时机
执行条件(满足任一):
- 消息队列为空(
mMessages == null) - 队头消息未到执行时间(
now < mMessages.when)
执行流程:
java
// 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,移除
}
}
应用场景:
java
// Activity 启动后空闲时预加载
Looper.myQueue().addIdleHandler(() -> {
preloadResources();
return false; // 只执行一次
});
细节3:同步屏障机制
同步屏障特点:
msg.target == null(普通消息的 target 指向 Handler)- 阻塞所有同步消息,只处理异步消息
- 系统级 API(
@hide),应用层无法直接使用
next() 中的处理:
java
// 检测到同步屏障
if (msg != null && msg.target == null) {
// ★跳过所有同步消息
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
应用场景:View 绘制
java
// ViewRootImpl.scheduleTraversals()
void scheduleTraversals() {
// 插入同步屏障
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
// 发送异步消息
mChoreographer.postCallback(CALLBACK_TRAVERSAL, mTraversalRunnable, null);
}
三、实际应用
3.1 典型场景
场景1:监控主线程消息处理(ANR 检测)
需求:检测主线程消息处理是否耗时过长
实现方式:
java
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 启动后延迟初始化非关键组件
实现方式:
java
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 消息处理日志
实现方式:
java
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. 主线程避免耗时操作
java
// ✅ 正确:耗时操作在子线程
new Thread(() -> {
String data = loadDataFromNetwork(); // 耗时
handler.post(() -> {
textView.setText(data); // UI 更新在主线程
});
}).start();
// ❌ 错误:主线程执行耗时操作
handler.post(() -> {
String data = loadDataFromNetwork(); // 会阻塞主线程
textView.setText(data);
});
2. 使用 Message.obtain() 复用对象
java
// ✅ 推荐:从对象池获取
Message msg = Message.obtain();
msg.what = MSG_UPDATE;
handler.sendMessage(msg);
// ❌ 不推荐:频繁创建新对象
Message msg = new Message();
msg.what = MSG_UPDATE;
handler.sendMessage(msg);
3. IdleHandler 只执行轻量级任务
java
// ✅ 正确:轻量级任务
Looper.myQueue().addIdleHandler(() -> {
preloadSmallIcons(); // 快速完成
return false;
});
// ❌ 错误:耗时任务
Looper.myQueue().addIdleHandler(() -> {
downloadLargeFile(); // 阻塞主线程
return false;
});
4. 及时移除不需要的消息
java
@Override
protected void onDestroy() {
super.onDestroy();
// ★清空所有消息
handler.removeCallbacksAndMessages(null);
}
❌ 常见错误
错误1:误以为 loop() 会卡死主线程
java
// ❌ 错误理解
// "Looper.loop() 是死循环,会卡死主线程"
// ✅ 正确理解
// loop() 无消息时通过 epoll 阻塞,不占用 CPU
// 主线程必须持续运行,loop() 是正常工作状态
错误2:在 IdleHandler 中执行耗时操作
java
// ❌ 错误
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
java
// ❌ 错误:返回 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. 避免频繁发送消息
java
// ❌ 不推荐:频繁发送
onScrollChanged() {
handler.sendEmptyMessage(MSG_UPDATE); // 滑动时频繁触发
}
// ✅ 推荐:合并消息
onScrollChanged() {
handler.removeMessages(MSG_UPDATE); // 移除之前的
handler.sendEmptyMessageDelayed(MSG_UPDATE, 16); // 延迟 16ms
}
2. 监控慢消息
java
// 设置慢消息日志
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() 回收消息到对象池,然后继续下一次循环。
深入展开(追问后):
完整流程源码:
java
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() 返回,线程被唤醒继续执行。
深入展开(追问后):
阻塞时长计算:
java
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 机制流程:
scss
阻塞:
nativePollOnce(timeout)
→ pollInner(timeout)
→ epoll_wait(mEpollFd, timeout)
→ 线程休眠,不占用 CPU
唤醒:
nativeWake()
→ write(mWakeEventFd)
→ epoll_wait() 检测到 fd 可读
→ 返回
→ read(mWakeEventFd) 清空数据
→ 线程唤醒
面试官追问:
追问1:为什么使用 epoll 而不是 Object.wait()/notify()?
答:主要原因有三点:
- 跨语言支持:epoll 是 Linux 系统调用,Java 和 Native 层都可以使用
- 多事件监听:epoll 可以同时监听多个文件描述符(消息、输入事件、VSYNC 等)
- 精确定时:epoll_wait 支持超时参数,可以精确控制阻塞时长
- 跨进程唤醒:Binder 驱动可以通过 epoll 唤醒应用进程
追问2:如果没有 epoll 阻塞会怎样?
答:如果 loop() 不阻塞,会导致:
java
// 假设没有阻塞机制
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() 方法。这种设计提供了灵活的消息处理机制,支持简单任务、拦截处理和复杂逻辑处理三种场景。
深入展开(追问后):
源码实现:
java
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);
}
}
三种使用场景:
java
// 场景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 作为中间层,可以:
- 统一处理日志、监控
- 根据条件决定是否继续传递
- 在不修改 Handler 子类的情况下扩展功能
追问2:post 的 Runnable 在哪个线程执行?
答:在 Handler 绑定的 Looper 所在线程执行,与调用 post 的线程无关。
java
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() 退出了,主线程无法处理任何事件,应用反而会崩溃。
深入展开(追问后):
线程状态分析:
scss
有消息时:
loop() → queue.next() → return msg → dispatchMessage()
线程状态:RUNNABLE(运行状态)
无消息时:
loop() → queue.next() → nativePollOnce(-1) → epoll_wait()
线程状态:WAITING(等待状态),不占用 CPU
ANR 的真正原因:
java
// ✅ 不会 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() 退出的后果:
java
// ActivityThread.main()
Looper.loop(); // 正常情况永不返回
// 如果返回了,说明发生严重错误
throw new RuntimeException("Main thread loop unexpectedly exited");
面试官追问:
追问1:如何验证 loop() 阻塞时不占用 CPU?
答:可以通过以下方式验证:
- adb shell top 命令:查看主线程 CPU 占用,无消息时接近 0%
- systrace 工具:查看线程状态,显示为 Sleeping 状态
- 代码验证:
java
// 主线程空闲时
Looper.myQueue().addIdleHandler(() -> {
Log.d("TAG", "Main thread idle"); // 说明线程在休眠
return true;
});
追问2:主线程一直有消息会导致 ANR 吗?
答:不一定,取决于单个消息的处理时间:
| 场景 | 单个消息耗时 | 消息数量 | 总耗时 | 是否 ANR |
|---|---|---|---|---|
| 场景1 | 10ms | 1000 | 10s | ❌ 不会 |
| 场景2 | 100ms | 100 | 10s | ❌ 不会 |
| 场景3 | 6000ms | 1 | 6s | ✅ 会 ANR |
ANR 的判断依据是输入事件 5 秒无响应,而非消息队列是否繁忙。
【高频题5】IdleHandler 的作用和执行时机是什么?
标准答案(30秒): IdleHandler 是 MessageQueue 提供的空闲回调接口,在消息队列空闲时执行低优先级任务。执行时机有两种情况:第一是消息队列为空;第二是队头消息还未到执行时间。IdleHandler.queueIdle() 返回 false 表示执行一次后移除,返回 true 表示保留下次继续执行。典型应用场景是 Activity 启动后预加载资源、空闲时执行 GC、延迟初始化非关键组件等。
深入展开(追问后):
执行时机源码:
java
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);
}
}
}
}
}
使用示例:
java
// 示例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 会影响性能吗?
答:如果使用不当,会影响性能:
- 返回 true 导致重复执行:
java
// ❌ 错误:每次空闲都执行耗时任务
Looper.myQueue().addIdleHandler(() -> {
doHeavyWork(); // 耗时操作
return true; // 每次空闲都执行,影响性能
});
// ✅ 正确:执行一次后移除
Looper.myQueue().addIdleHandler(() -> {
doHeavyWork();
return false;
});
- 执行耗时任务阻塞主线程:
java
// ❌ 错误:主线程执行耗时任务
Looper.myQueue().addIdleHandler(() -> {
downloadLargeFile(); // 阻塞主线程
return false;
});
// ✅ 正确:耗时任务放子线程
Looper.myQueue().addIdleHandler(() -> {
new Thread(() -> {
downloadLargeFile();
}).start();
return false;
});
追问2:IdleHandler 和 postDelayed(0) 有什么区别?
答:
| 对比项 | IdleHandler | postDelayed(0) |
|---|---|---|
| 执行时机 | 队列空闲时 | 立即插入队列 |
| 优先级 | 最低(所有消息后) | 与普通消息相同 |
| 适用场景 | 非紧急任务、启动优化 | 切换到主线程执行 |
| 可能不执行 | 队列一直繁忙可能不执行 | 一定会执行 |
4.2 进阶加分题(P6/P6+)
【进阶题1】同步屏障(Sync Barrier)的工作原理是什么?在什么场景使用?
参考答案:
同步屏障是一种特殊的 Message,它的 target 字段为 null(普通消息的 target 指向 Handler)。当 MessageQueue 中存在同步屏障时,MessageQueue.next() 会跳过所有同步消息,只取异步消息执行。
工作原理:
java
// 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 绘制
java
// 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 方法,应用层无法调用。原因:
- 风险高:同步屏障会阻塞所有同步消息,滥用会导致消息饿死
- 必须成对:插入后必须及时移除,否则会永久阻塞同步消息
- 系统级优化:只应该在可控场景下使用(View 绘制、输入事件)
【进阶题2】Message 对象池的设计原理是什么?为什么最大容量是 50?
参考答案:
Message 对象池是单链表结构,使用享元模式实现对象复用。sPool 指向链表头节点,obtain() 从头部取出,recycleUnchecked() 插入到头部,最大容量 50 个。
设计原理:
java
// 对象池结构
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:
-
性能和内存的平衡:
- 太小:无法满足高频消息场景,频繁创建对象
- 太大:占用过多内存,Message 可能携带大量数据
-
实际场景验证:
- 普通应用:消息频率不高,50 个足够
- 游戏等高频场景:50 个也能覆盖大部分情况
-
GC 友好:
- 超过 50 个的 Message 直接被 GC 回收
- 避免长期占用大量内存
追问:为什么 recycle 要清空所有字段?
答:防止内存泄漏。Message 可能持有 Activity、View、Bitmap 等大对象,如果不清空直接放回对象池,这些对象无法被 GC 回收:
java
// 场景: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(),通过时间差计算消息处理耗时。
实现原理:
java
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 实现:
java
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);
}
}
}
}
}
追问:这种方案的缺点是什么?
答:
- 性能开销:每个消息都会调用 println,频繁打印日志影响性能
- 无法监控 Native:只能监控 Java 层消息,Native 层耗时无法感知
- 堆栈不准确:打印堆栈时消息可能已处理完,堆栈信息不准确
更好的方案:
- Choreographer.FrameCallback:监控帧率,超过 16.6ms 即为掉帧
- 插桩:编译时插桩,精确记录方法耗时
- Systrace:系统工具,全面的性能分析
4.3 实战场景题
【场景题】主线程消息队列堆积导致 UI 卡顿,如何优化?
场景描述:
一个列表页面滑动时会频繁发送刷新消息,导致消息队列堆积,UI 卡顿,滑动不流畅。
问题分析:
- 根本原因:频繁发送消息导致 MessageQueue 堆积
- 表现:滑动时掉帧,帧率下降
- 诊断:通过 Choreographer 监控发现单帧耗时超过 16.6ms
解决方案:
方案1:合并消息(推荐)
java
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 延迟非紧急刷新
java
Looper.myQueue().addIdleHandler(() -> {
// ★空闲时执行非紧急刷新
updateNonCriticalUI();
return false;
});
方案3:分批处理
java
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. 如何监控优化效果?
java
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 阻塞 | void | next() 内部 |
| nativeWake() | Native 层唤醒 | 不阻塞 | void | enqueueMessage() |
| dispatchMessage() | 分发消息给 Handler | 不阻塞 | void | loop() 内部 |
| recycleUnchecked() | 回收消息到对象池 | 不阻塞 | void | loop() 内部 |
5.2 核心要点速记
一句话记忆: Looper.loop() 通过无限循环不断从 MessageQueue.next() 取消息并分发给 Handler 处理,无消息时通过 epoll 机制阻塞休眠不占用 CPU,有消息时唤醒继续执行,处理完后回收消息到对象池复用。
3个关键点:
- 无限循环:for(;;) 持续运行,只有 quit() 时退出
- epoll 阻塞:无消息时通过 nativePollOnce() → epoll_wait() 休眠,不占 CPU
- 三级分发:msg.callback → mCallback → handleMessage
面试官最爱问:
- loop() 的核心流程是什么?
- 为什么 loop() 是死循环不会 ANR?
- MessageQueue.next() 如何实现阻塞?
- Handler 消息分发的三级机制是什么?
- 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 完整工作流程