一、Handler 机制核心原理
1. ThreadLocal 与线程绑定
真题:
- 为什么每个线程只能有一个 Looper?
- ThreadLocal 是如何实现线程隔离的?
源码注释:
public final class Looper {
// 静态 ThreadLocal 存储每个线程的 Looper 实例
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<>();
// 主线程 Looper(由 ActivityThread 自动创建)
private static Looper sMainLooper;
// 初始化当前线程的 Looper
public static void prepare() {
prepare(false); // 主线程 quitAllowed 为 false
}
private static void prepare(boolean quitAllowed) {
// 检查当前线程是否已存在 Looper
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
// 将新创建的 Looper 存入当前线程的 ThreadLocal
sThreadLocal.set(new Looper(quitAllowed));
}
// 获取当前线程的 Looper
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
}
面试回答:
ThreadLocal 通过为每个使用该变量的线程都提供一个独立的变量副本,每个线程都可以独立地改变自己的副本,而不会影响其他线程所对应的副本。在 Looper 中,
sThreadLocal.get()
会返回当前线程存储的 Looper 实例,从而实现线程隔离。若重复调用prepare()
,会因检测到已有 Looper 而抛出异常,保证每个线程仅有一个 Looper。
2. 消息池机制
真题:
- Message 为什么要复用?如何实现复用的?
- 消息池的最大容量是多少?
源码注释:
public final class Message implements Parcelable {
// 消息池的头节点(静态变量,所有线程共享)
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--; // 池大小减1
return m;
}
}
return new Message(); // 池为空时创建新实例
}
// 回收消息到池中
void recycleUnchecked() {
if (isInUse()) { // 确保消息未被使用
throw new IllegalStateException("This message cannot be recycled because it is still in use.");
}
// 重置消息状态
flags = FLAG_IN_USE;
what = 0;
arg1 = 0;
arg2 = 0;
obj = null;
replyTo = null;
sendingUid = -1;
when = 0;
target = null;
callback = null;
data = null;
synchronized (sPoolSync) {
if (sPoolSize < MAX_POOL_SIZE) {
next = sPool; // 插入链表头部
sPool = this;
sPoolSize++; // 池大小加1
}
}
}
}
面试回答:
Message 复用主要是为了减少对象创建和垃圾回收的开销,提升性能。通过静态链表实现消息池,最大容量为 50。
Message.obtain()
优先从池中获取复用,recycleUnchecked()
将消息回收至池中。这种设计在高频发送消息的场景(如动画、网络回调)中尤为重要,可显著降低内存抖动。
二、消息发送与处理机制
1. 消息入队逻辑
真题:
- MessageQueue 是如何保证线程安全的?
- 为什么 MessageQueue 是单链表而非队列?
源码注释:
boolean enqueueMessage(Message msg, long when) {
// 消息必须有 target(即发送该消息的 Handler)
msg.target = this;
msg.workSourceUid = ThreadLocalWorkSource.getUid();
synchronized (this) { // 同步锁保证线程安全
if (mQuitting) { // 队列已退出,回收消息
IllegalStateException e = new IllegalStateException(
msg.target + " sending message to a Handler on a dead thread");
Log.w(TAG, e.getMessage(), e);
msg.recycle();
return false;
}
msg.markInUse();
msg.when = when; // 设置消息执行时间
Message p = mMessages; // 当前头节点
boolean needWake;
// 按 when 排序插入(新消息时间更早,插入头部)
if (p == null || when == 0 || when < p.when) {
msg.next = p;
mMessages = msg;
needWake = mBlocked; // 若队列阻塞,需唤醒
} else {
// 遍历链表找到插入位置(按 when 升序)
Message prev;
for (;;) {
prev = p;
p = p.next;
if (p == null || when < p.when) break;
}
msg.next = p;
prev.next = msg;
needWake = mBlocked && p.target == null && msg.isAsynchronous();
}
// 若队列阻塞且需要唤醒,通过 native 方法唤醒
if (needWake) nativeWake(mPtr);
}
return true;
}
面试回答:
- 线程安全:通过
synchronized (this)
保证同一时间只有一个线程能修改消息队列,确保多线程发送消息时不会冲突。- 单链表优势:单链表可按
when
灵活插入,支持延迟消息和异步消息插队,比传统 FIFO 队列更适合消息调度场景。
2. 消息分发优先级
真题:
- Handler 处理消息的优先级顺序是怎样的?
- 如何让一个消息优先被处理?
源码注释:
public void dispatchMessage(Message msg) {
// 优先级1:Message 自带的 callback(通过 post(Runnable) 设置)
if (msg.callback != null) {
handleCallback(msg);
}
// 优先级2:Handler 的 Callback(构造函数传入)
else if (mCallback != null) {
if (mCallback.handleMessage(msg)) return; // 若 Callback 处理成功,不再继续
}
// 优先级3:用户重写的 handleMessage 方法
handleMessage(msg);
}
private static void handleCallback(Message message) {
message.callback.run(); // 执行 Runnable
}
面试回答:
消息处理优先级为:
Message.callback
>Handler.Callback
>Handler.handleMessage
。若需让消息优先处理,可:
- 使用
post(Runnable)
让消息自带 callback;- 设置 Handler 的 Callback 并返回 true 拦截消息;
- 将消息设为异步(
setAsynchronous(true)
),可在同步屏障中优先执行。
三、阻塞唤醒机制与性能优化
1. 消息循环与阻塞原理
真题:
- Looper.loop () 为什么不会阻塞主线程?
- MessageQueue.next () 是如何实现阻塞的?
源码注释:
public static void loop() {
final Looper me = myLooper(); // 获取当前线程的 Looper
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
final MessageQueue queue = me.mQueue;
for (;;) { // 无限循环
Message msg = queue.next(); // 可能阻塞
if (msg == null) return; // 队列退出
// 分发消息到对应的 Handler
msg.target.dispatchMessage(msg);
// 回收消息到池
msg.recycleUnchecked();
}
}
Message next() {
final long ptr = mPtr; // 指向 native 层的 MessageQueue
int pendingIdleHandlerCount = -1;
int nextPollTimeoutMillis = 0;
for (;;) {
// 关键阻塞方法:通过 native 层实现休眠
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
final long now = SystemClock.uptimeMillis();
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 {
// 取出消息
mBlocked = false; // 标记为非阻塞状态
if (prevMsg != null) prevMsg.next = msg.next;
else mMessages = msg.next;
msg.next = null;
return msg;
}
} else {
// 无消息,永久阻塞
nextPollTimeoutMillis = -1;
}
// 检查队列是否退出
if (mQuitting) {
dispose();
return null;
}
}
}
}
面试回答:
Looper.loop () 不会导致 ANR 的核心原因是:
- 当队列无消息时,
nativePollOnce
通过 epoll 机制进入内核休眠,释放 CPU 资源;- 消息入队时通过
nativeWake
写入管道数据,唤醒休眠线程;- ANR 的触发条件是主线程在规定时间内未处理完特定类型的消息(如输入事件),而非 Looper 循环本身。
2. 同步屏障机制
真题:
- 什么是同步屏障?有什么应用场景?
- 如何实现一个 UI 刷新的高优先级消息?
源码注释:
// MessageQueue 中的同步屏障插入方法
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; // 使用 arg1 存储 token
// 同步屏障消息的 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;
}
}
// next() 方法中处理同步屏障的逻辑
if (msg != null && msg.target == null) {
// 找到下一个异步消息
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
面试回答:
同步屏障是一种特殊消息(target 为 null),插入后会阻塞所有同步消息,仅允许异步消息通过。应用场景包括:
- UI 刷新:Choreographer 通过同步屏障确保 Vsync 信号触发的绘制任务优先执行;
- 优先级调度:通过
Message.setAsynchronous(true)
将关键消息设为异步,避免被普通消息阻塞。
四、内存泄漏与线程通信
1. Handler 内存泄漏
真题:
- 为什么非静态内部类 Handler 会导致内存泄漏?
- 如何正确在 Activity 中使用 Handler?
错误代码示例:
public class MainActivity extends AppCompatActivity {
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
// 更新 UI
textView.setText("Update");
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 发送延迟消息
mHandler.postDelayed(() -> {
// 模拟耗时操作
}, 10000);
}
}
正确实现方案:
public class MainActivity extends AppCompatActivity {
private TextView textView;
// 使用静态内部类 + 弱引用
private static class MyHandler extends Handler {
private final WeakReference<MainActivity> activityWeakReference;
public MyHandler(MainActivity activity) {
activityWeakReference = new WeakReference<>(activity);
}
@Override
public void handleMessage(Message msg) {
MainActivity activity = activityWeakReference.get();
if (activity != null) {
activity.textView.setText("Update");
}
}
}
private final MyHandler mHandler = new MyHandler(this);
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
textView = findViewById(R.id.textView);
}
@Override
protected void onDestroy() {
super.onDestroy();
// 移除所有未处理的消息和回调
mHandler.removeCallbacksAndMessages(null);
}
}
面试回答:
非静态内部类会隐式持有外部类的引用。若 Handler 中有延迟消息未处理,而 Activity 已销毁,Handler 会继续持有 Activity 引用,导致 GC 无法回收 Activity。正确做法是:
- 使用静态内部类 + 弱引用;
- 在 Activity 的
onDestroy
中移除所有消息(removeCallbacksAndMessages(null)
)。
2. 线程间通信
真题:
- 如何在子线程中更新 UI?
- 除了 Handler,还有哪些线程通信方式?
多场景解决方案:
// 方式1:主线程创建 Handler,子线程发送消息
public class MainActivity extends AppCompatActivity {
private Handler mHandler = new Handler(Looper.getMainLooper()) {
@Override
public void handleMessage(Message msg) {
// 更新 UI
}
};
public void startBackgroundTask() {
new Thread(() -> {
// 子线程执行耗时操作
mHandler.sendEmptyMessage(1); // 发送消息到主线程
}).start();
}
}
// 方式2:Activity.runOnUiThread
new Thread(() -> {
// 耗时操作
runOnUiThread(() -> {
// 更新 UI
});
}).start();
// 方式3:View.post
view.post(() -> {
// 更新 view
});
// 方式4:AsyncTask(已弃用,但原理类似)
private class MyTask extends AsyncTask<Void, Void, String> {
@Override
protected String doInBackground(Void... voids) {
return "result";
}
@Override
protected void onPostExecute(String result) {
// 更新 UI
}
}
面试回答:
线程间通信方式包括:
- Handler:最基础方式,通过消息队列实现线程安全通信;
- Activity.runOnUiThread:内部封装了 Handler,简化调用;
- View.post:View 持有主线程 Handler,可直接切换到主线程;
- AsyncTask:封装了线程池和 Handler,适合短期异步任务;
- LiveData:基于观察者模式,自动在主线程回调;
- RxJava:通过 Scheduler 灵活切换线程。
五、进阶问题与扩展考点
1. Handler 与 Choreographer 的关系
真题:
- Choreographer 是如何利用 Handler 机制的?
- 为什么 UI 刷新要通过 Choreographer 而非直接 Handler?
解析:
- Choreographer 原理:
- 向 VSYNC 信号注册回调;
- VSYNC 到来时,通过 Handler 发送异步消息(带有同步屏障);
- 优先执行绘制任务,确保在 16ms 内完成一帧渲染。
源码关键:
// Choreographer 内部使用 Handler 发送异步消息
private final class FrameHandler extends Handler {
public FrameHandler(Looper looper) {
super(looper);
}
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_DO_FRAME:
doFrame(System.nanoTime(), 0);
break;
case MSG_DO_SCHEDULE_VSYNC:
doScheduleVsync();
break;
case MSG_DO_SCHEDULE_CALLBACK:
doScheduleCallback(msg.arg1);
break;
}
}
}
// 发送异步消息
private void sendFrameCallback(int callbackType, long dueTimeNanos) {
Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, callbackType, 0);
msg.setAsynchronous(true); // 设置为异步消息
mHandler.sendMessageAtTime(msg, dueTimeNanos / TimeUtils.NANOS_PER_MS);
}
面试回答:
Choreographer 通过 Handler 发送异步消息,并配合同步屏障确保绘制任务优先执行。相比直接使用 Handler,Choreographer 的优势在于:
- 与屏幕刷新率(VSYNC)同步,避免丢帧;
- 通过同步屏障提升绘制优先级,减少卡顿;
- 统一管理动画、输入和绘制的时间点,优化性能。
2. HandlerThread 的应用场景
真题:
- HandlerThread 与普通 Thread 有何区别?
- 何时应该使用 HandlerThread?
源码关键:
public class HandlerThread extends Thread {
int mPriority;
int mTid = -1;
Looper mLooper;
private @Nullable Handler mHandler;
public HandlerThread(String name) {
super(name);
mPriority = Process.THREAD_PRIORITY_DEFAULT;
}
@Override
protected void onLooperPrepared() {
// Looper 准备好后调用
}
@Override
public void run() {
mTid = Process.myTid();
Looper.prepare(); // 创建 Looper
synchronized (this) {
mLooper = Looper.myLooper();
notifyAll(); // 通知 Looper 已创建
}
Process.setThreadPriority(mPriority);
onLooperPrepared();
Looper.loop(); // 启动消息循环
mTid = -1;
}
// 获取 Looper
public Looper getLooper() {
if (!isAlive()) return null;
synchronized (this) {
while (isAlive() && mLooper == null) {
try {
wait(); // 等待 Looper 创建完成
} catch (InterruptedException e) {}
}
}
return mLooper;
}
// 终止消息循环
public boolean quit() {
Looper looper = getLooper();
if (looper != null) {
looper.quit();
return true;
}
return false;
}
}
面试回答:
HandlerThread 是一个自带 Looper 的线程,相比普通 Thread:
- 内置 Looper 和 MessageQueue,可直接创建 Handler;
- 提供
getLooper()
方法获取线程的 Looper;- 适合需要长期运行并处理消息的场景(如文件操作、网络请求)。
应用场景:
- 后台任务需要串行执行(如数据库操作);
- 定时任务(配合
sendMessageDelayed
);- 不需要高并发的异步处理。