Android面试题附答案(二)——— Handler机制
一、面试题与答案
1. Handler机制简述
Android的线程间消息通信机制,主要作用是线程间的消息传递与任务调度。
核心角色:
- Message:消息载体
- MessageQueue:单向链表,按时间排序
- Looper:消息队列管家,负责循环读取消息
- Handler:消息的收发和处理
工作流程:
ActivityThread.main()创建了Looper和MessageQueueLooper.loop()开启死循环不断读取消息- Handler发送的消息被存放到MessageQueue里
- Looper将消息分发到目标Handler来处理
对应关系: 一个线程对应一个Looper和MessageQueue,一个Looper可以有多个Handler。
2. 主线程的Looper在哪里创建的?死循环为什么不会阻塞主线程?
创建位置: ActivityThread.main()里,调用Looper.prepareMainLooper()创建。
为什么不会阻塞:
Looper.loop()本质是阻塞循环,没有消息时会阻塞休眠,不消耗CPU- 底层依赖Linux的管道/epoll机制,无事件时进入休眠
- 如果没有loop循环,主线程运行完就会直接退出,loop()是维持主线程运行的核心
3. 主线程loop()死循环为什么不会导致ANR?
导致ANR的不是loop循环本身,而是handleMessage()里做了耗时操作,导致消息来不及处理。
loop()只是在等待消息,没有消息时处于休眠状态,不占CPU。
4. epoll机制原理?nativePollOnce的作用?
epoll机制: Linux系统的I/O多路复用技术,无事件时会阻塞休眠,不占CPU。epoll_wait()阻塞等待,直到有事件就绪才返回。
nativePollOnce的作用:
Looper.loop()里会调用MessageQueue.next()取消息- 当
next()发现没有消息时,调用nativePollOnce() - 底层调用Linux的
epoll_wait(),主线程进入阻塞休眠,不占CPU - Handler往消息队列发送消息时,会触发IO事件,唤醒主线程继续处理
5. 有哪些唤醒主线程的信号?
绘制、触摸、生命周期等系统UI事件,延迟消息倒计时结束等。只要主线程的MessageQueue里有需要处理的消息就会唤醒。
6. 为什么Android中UI更新被设置成了单线程?子线程真的不能更新UI吗?SurfaceView为什么可以直接子线程绘制?
单线程原因: 确保线程安全,保证UI数据一致。
子线程能更新UI的情况:
- ViewRootImpl创建之前(onResume之前)可以更新(
checkThread()里抛出的异常) - SurfaceView可以在子线程绘制
SurfaceView为什么可以:
- 主线程是共享Surface,统一绘制的;SurfaceView有独立的Surface
- SurfaceView的绘制接口(
lockCanvas/unlockCanvasAndPost)都是线程安全的,内部做了同步 - 双缓冲机制,后台缓冲区绘制,前台缓冲区显示
注意:SurfaceView也不是所有操作都可以在子线程进行,只是绘制操作可以,布局和属性操作依然必须在主线程修改。
7. ThreadLocal在Handler机制中的作用?
确保每个线程拥有独立的一个Looper。
ThreadLocal为每个线程维护独立的变量副本,Looper.prepare()时将Looper存入当前线程的ThreadLocal,Looper.myLooper()时从当前线程的ThreadLocal取出,保证线程隔离。
8. HandlerThread是什么?
封装好了Looper的Thread。run()方法里已经启动了Looper,可以直接在子线程里使用Handler收发消息。
典型使用场景是需要在子线程里串行处理任务,比如IntentService内部就使用了HandlerThread。
9. 什么是IdleHandler?使用场景?
是什么: 消息队列里没有要处理的消息时,会处理IdleHandler。
Looper.loop()死循环里,会一直调用MessageQueue.next()读取消息,如果没有消息,会调用nativePollOnce进入阻塞休眠,但在这之前,会先查看是否有IdleHandler需要处理。
使用场景:
- 延迟初始化非必须组件(不阻塞启动流程)
- GC触发,Glide清理资源
- 上传日志和异常信息等
10. 同步消息屏障?什么时候插入?有哪些异步消息?
是什么: target = null的特殊消息,拦截同步消息,放行异步消息,让异步消息优先处理。
什么时候插入: View绘制时,在ViewRootImpl.scheduleTraversals()方法里插入,确保VSYNC信号被优先处理。
异步消息有哪些:
- Choreographer的VSYNC回调
- ViewRootImpl.scheduleTraversals()触发的遍历消息
- 输入事件的部分处理
11. 消息池大小?什么时候初始化?
上限50个(MAX_POOL_SIZE静态常量,类加载时默认初始化)。
第一次调用Message.obtain()时才开始填充消息池。消息池是全局共享的(静态变量)。
12. Message.obtain()和Handler.obtainMessage()的区别?
区别是后者设置了msg.target为这个Handler,其余逻辑相同,最终都是从消息池复用Message对象。
13. MessageQueue是什么数据结构?
单向链表。需要动态插入(消息需要按照时间排序),所以不能是队列。
插入时按when(触发时间)从小到大排序,next()取出链表头部到期的消息。
14. 主线程的Looper死循环,如何处理其他事务?
主线程只有Looper一个循环,所有的主线程任务(触摸、绘制、生命周期事件等)最终都被封装成Message,在Looper循环里进行处理。
15. Looper.quit()和quitSafely()的区别?
- 主线程的Looper不允许
quit() - 子线程可以
quit(),会立即清空消息队列并退出 quitSafely()会处理完当前时间之前的消息再退出,更安全
16. 子线程可以直接创建Handler吗?有什么注意事项?
不能直接创建,需要先调用Looper.prepare()创建Looper,否则会崩溃。Handler构造函数里会调用Looper.myLooper()获取当前线程的Looper,为null时抛异常。
注意事项:
- 必须先开启Looper,再创建Handler,再开启循环
- 不能更新UI
- 注意内存泄露:
Looper.loop()是死循环,线程会一直存活,必须手动退出Looper
推荐直接使用HandlerThread。
17. Handler.post()和View.post()的区别?
View.post()能保证在完成测量布局之后才发送消息;Handler.post()是直接发送- 两者都运行在主线程,都可以更新UI
View.post()如果View还没有attach到Window,会把Runnable存到mRunQueue里,等dispatchAttachedToWindow()时再统一发送到Handler,所以能保证在View attach之后执行- 如果View创建了但没有添加到布局,
View.post()的Runnable永远不会执行,且可能造成内存泄露
18. 使用Handler的postDelay后消息队列会有什么变化?会延迟插入吗?
不会延迟插入,会立即插入消息队列,并设置触发时间when。消息队列里的消息按照时间从小到大排序。
注意:如果队列里有耗时任务,
postDelayed的Runnable不会准时执行,会被前面的消息阻塞。
19. Handler.post()和sendMessage()的区别?
post()会先把Runnable封装成Message.callback,两者最终都调用sendMessage发送到消息队列。区别在于callback不同,导致最后的处理消息逻辑不同。
handler的分发优先级:
msg.callback.run()Handler.mCallbackHandler.handleMessage()
20. Handler为什么会导致内存泄露?解决方案?
本质: 长生命周期的对象持有短生命周期对象的引用,导致短生命周期的对象无法被销毁。
泄露链路: 消息队列有未处理的消息 → 消息持有Handler引用 → 非静态内部类的Handler持有外部Activity引用 → Activity无法被销毁
解决方案:
- 静态内部类 + WeakReference
- 页面销毁时移除所有消息和回调(
handler.removeCallbacksAndMessages(null)) - 使用协程替换Handler
二、Handler机制原理
核心角色
| 角色 | 说明 |
|---|---|
| Message | 消息载体,携带数据和目标Handler |
| MessageQueue | 单向链表,按时间排序存储消息 |
| Looper | 消息队列管家,负责循环读取消息并分发 |
| Handler | 消息的发送者和处理者 |
工作流程
Handler.sendMessage()
│
▼
MessageQueue.enqueueMessage() ← 按时间排序插入链表
│
▼
Looper.loop() 死循环
│
▼
MessageQueue.next() ← 取出到期消息
│
▼
msg.target.dispatchMessage() ← 分发给目标Handler
│
▼
Handler.handleMessage() ← 处理消息
对应关系
- 一个线程 → 一个 Looper → 一个 MessageQueue
- 一个 Looper → 可以有多个 Handler
消息分发优先级
1. msg.callback.run() // Handler.post(Runnable) 时设置
2. Handler.mCallback // 构造函数传入的 Callback
3. Handler.handleMessage() // 重写的方法