Q1. Handler为什么可以切换线程?
答案: Handler实现线程切换的关键在于:不同线程的Handler与特定的Looper绑定,而Looper运行在其绑定的线程中。
图解:
源码解析:
java
public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
msg.target = this; // 绑定当前Handler
return queue.enqueueMessage(msg, uptimeMillis);
}
Q2. 为什么不能在子线程直接更新 UI?
答案:
- UI控件非线程安全,多线程并发操作会导致崩溃
- 加锁会降低UI访问效率
- Android采用单线程UI模型
源码解析:
java
// ViewRootImpl.java
void checkThread() {
if (mThread != Thread.currentThread()) {
throw new CalledFromWrongThreadException(...);
}
}
Q3. 请描述 Handler、Looper、MessageQueue、Message 四者的关系。
答案: Handler发送Message到MessageQueue,Looper循环取出Message并派发给Handler处理。
图解:
数量关系: 1个线程 : 1个Looper : 1个MessageQueue : N个Handler
Q4. Looper 是怎么工作的?主线程和子线程 Looper 区别?
答案:
- 主线程:ActivityThread.main()中自动调用prepareMainLooper()和loop()
- 子线程:需要手动调用Looper.prepare()和Looper.loop()
图解:
Q5. Looper.loop() 是死循环,为什么不会卡死主线程?
答案: 无消息时nativePollOnce()阻塞,线程休眠释放CPU;有消息时nativeWake()唤醒。
图解:
源码解析:
java
Message next() {
for (;;) {
nativePollOnce(ptr, nextPollTimeoutMillis); // 阻塞等待
}
}
Q6. Message 如何创建更好?为什么?
答案: 使用Message.obtain()或handler.obtainMessage(),从对象池复用,避免频繁GC。
图解:
Q7. 消息是怎么按时间排序执行的?
答案: MessageQueue是单向链表,按when升序排列。延时消息插入到合适位置,Looper取队首,时间未到则阻塞。
图解:
Q8. 一个线程可以有几个 Handler、Looper、MessageQueue?
答案:
- Looper:1个/线程(ThreadLocal保证)
- MessageQueue:1个/线程
- Handler:多个
图解:
Q9. quit() 和 quitSafely() 区别?
答案:
- quit():立即退出,丢弃所有未处理消息
- quitSafely():安全退出,处理完当前已分发消息后退出
图解:
Q10. 主线程怎么切到子线程?
答案: 在子线程中先prepare Looper,创建Handler,然后loop;主线程调用该Handler发送消息。
图解:
Q11. Handler 为什么会导致内存泄漏?
答案: 非静态内部类Handler隐式持有Activity引用,延迟消息未处理时Message持有Handler,导致Activity无法被GC。
图解:
Q12. 如何解决 Handler 内存泄漏?
答案:
- 静态内部类 + WeakReference
- onDestroy中调用removeCallbacksAndMessages(null)
源码解析:
java
static class MyHandler extends Handler {
private final WeakReference<MainActivity> mActivity;
// ...
}
@Override
protected void onDestroy() {
handler.removeCallbacksAndMessages(null);
super.onDestroy();
}
Q13. 为什么主线程不会因为 Handler 泄漏?
答案: 主线程Looper生命周期和应用进程一致,始终存在,不存在回收问题。
Q14. sendMessage 和 post(Runnable) 区别?
答案: 本质无区别,post内部将Runnable封装成Message(callback=Runnable),最终都调用sendMessageAtTime。
源码解析:
java
public final boolean post(Runnable r) {
return sendMessageDelayed(getPostMessage(r), 0);
}
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
msg.callback.run(); // post方式
} else {
handleMessage(msg); // sendMessage方式
}
}
Q15. 子线程中可以创建 Handler 吗?需要注意什么?
答案: 可以,需要先调用Looper.prepare(),再创建Handler,最后调用Looper.loop()。
图解:
Q16. Handler 与 AsyncTask、RxJava、协程的区别?
答案:
Q17. IdleHandler 是什么?有什么用?
答案: MessageQueue空闲时回调的接口。用于启动优化(延迟初始化SDK)、空闲时释放内存等。
源码解析:
java
Looper.myQueue().addIdleHandler(new IdleHandler() {
@Override
public boolean queueIdle() {
initThirdPartySDK();
return false;
}
});
Q18. 什么是同步屏障?什么是异步消息?
答案: 同步屏障是target=null的特殊消息,插入后Looper跳过所有同步消息,只处理异步消息。用于保证UI绘制优先执行。
图解:
Q19. HandlerThread 是什么?与普通 Thread 的区别?
答案: HandlerThread是自带Looper的子线程,内部封装了prepare和loop。普通Thread执行完run就销毁,HandlerThread可以持续接收消息。
图解:
Q20. IntentService 与 HandlerThread 的关系?
答案: IntentService内部使用HandlerThread,通过ServiceHandler串行处理Intent,处理完自动销毁。
Q21. 多个 Handler 往同一个线程发送消息,Message 是如何区分属于哪个 Handler 的?
答案: 通过Message的target字段区分,它指向发送该消息的Handler对象。
图解:
Q22. 主线程的 Looper 在哪个类里被初始化的?
答案: 在ActivityThread的main()方法中。
源码解析:
java
public static void main(String[] args) {
Looper.prepareMainLooper();
Looper.loop();
}
Q23. 线程和 Looper 是如何保证一对一关系的?
答案: 通过ThreadLocal实现,每个线程存储自己的Looper副本。
源码解析:
java
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<>();
private static void prepare(boolean quitAllowed) {
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper per thread");
}
sThreadLocal.set(new Looper(quitAllowed));
}
Q24. Looper.loop() 是死循环,它后面的代码什么时候执行?
答案: 只有调用quit()退出循环后才会执行,正常情况下永远不会执行。
图解:
Q25. View.post(Runnable) 为什么能拿到 View 的宽高?
答案: View.post将Runnable发送到主线程Handler,且排在绘制流程之后执行,此时View已完成测量和布局。
图解:
Q26. 完整描述 Handler 消息分发流程
图解:
Q27. Handler 可能引起的问题
答案:
Q28. Handler 发送延迟消息(postDelayed)的原理是什么?
答案: 计算执行时间when=当前时间+delayMillis,按when插入队列,Looper取队首时若时间未到则nativePollOnce阻塞。
图解: