1、Handler 机制四大角色是什么?各自作用?
答案
- Handler:发送消息、处理消息、切换线程(子线程发消息,主线程更新 UI)。
- Message:消息载体,存放 what、arg1、arg2、obj、执行时间 when。
- MessageQueue :消息队列,底层单向链表,按执行时间排序,存放所有 Message。
- Looper :消息轮询器,死循环不断从 MessageQueue 取消息,分发给 Handler 处理。
2、Handler 整体运行流程?
答案
子线程通过 Handler 发 Message → 消息存入 MessageQueue 排队 → Looper 无限循环从队列取消息 → 回调给对应 Handler 的 handleMessage → 主线程执行更新 UI。
3、Looper 原理是什么?为什么不会卡死 ANR?
答案
Looper 是死循环 ,不停调用 MessageQueue.next ();队列无消息或未到延时时间时,底层靠 epoll 阻塞休眠,释放 CPU,不占用主线程资源,所以不会 ANR。
4、MessageQueue 底层是什么结构?
答案
底层是单向链表,不是数组队列;
按 when 执行时间 从小到大排序,时间早的排在前面,方便 Looper 按时间取消息。
5、Message 为什么推荐用 obtain () 而不是 new Message ()?
答案
Message 内部有消息缓存池(静态链表);
obtain () 从缓存池复用对象,避免频繁 new 创建对象、减少 GC;
用完 recycle () 回收放回缓存池。
6、延时消息 postDelayed /sendMessageDelayed 底层怎么实现延时?
答案
- 不开启定时器、不轮询倒计时;
- 计算 当前时间 + 延时时间 赋值给 Message 的 when;
- 消息按 when 时间插入 MessageQueue 链表有序排队;
- Looper 取消息时,没到执行时间就阻塞等待,时间到自动唤醒执行。
7、主线程为什么可以直接 new Handler?不用手动创建 Looper?
答案
App 主线程 ActivityThread 的 main 方法里,已经默认调用了 Looper.prepare() + Looper.loop() ;
主线程自带 Looper 和 MessageQueue,所以直接 new Handler 就能用。
8、子线程可以直接 new Handler 吗?会报错吗?怎么解决?
答案
子线程直接 new Handler 会报错,因为没有 Looper;
解决:手动执行Looper.prepare() → new Handler() → Looper.loop()
9、Handler 会造成内存泄漏吗?原因怎么解决?
答案
会泄漏。
原因:匿名内部类 Handler 隐式持有 Activity 引用,延时消息没执行完,页面 finish 后 Handler 还在排队持有 Activity,GC 无法回收。
解决:
- 使用静态内部类 + 弱引用持有 Activity;
- 页面销毁
onDestroy调用removeCallbacksAndMessages(null)清空所有消息。
10、什么是消息屏障、同步消息、异步消息?
答案
- 同步屏障:往队列插一个特殊标记,只放行异步消息,卡住普通同步消息;
- 用来优先执行 UI 绘制、刷新界面,保证绘制优先级最高。
11、Intent 大数据不行,Handler 能跨进程通信吗?
答案
普通 Handler 只同进程内线程间通信;不能跨进程;跨进程用 Binder、AIDL。
12、Looper 的 loop () 是死循环,为什么不会耗 CPU?
答案
无消息或延时未到,MessageQueue 底层Linux epoll 阻塞,线程休眠放弃 CPU;有消息才唤醒,几乎不耗性能。
13、postDelayed 延时消息原理
1、 先记住核心一句话
根本没有单独开定时器、也没有子线程倒计时,只是记了个「未来执行时间」,插到队列里排队,Looper 等到点再执行。
2. 发延时消息时
你调用 postDelayed(Runnable, 1000)系统做两件事:
- 拿当前系统时间 + 你设置的延时 1000 毫秒
- 算出一个未来准确时间点 when,塞到 Message 里面
3. 消息入队
把这条带 when 时间 的 Message,插入 MessageQueue 单向链表 。队列是按时间从小到大排序的:
- 时间越早的排越前面
- 延时久的排后面
4. Looper 不停干活
Looper 死循环调用 MessageQueue.next() 拿消息:
- 拿到队头消息,先对比 现在时间 和 消息 when 时间
- 还没到时间 :线程直接阻塞休眠,不轮询、不耗 CPU
- 时间到了:立刻唤醒线程,取出消息,交给 Handler 执行
5. 极简背诵版
postDelayed 先计算当前时间加延时时长,生成未来执行时间 when 存入 Message;消息按时间有序插入 MessageQueue;Looper 循环取消息,未到执行时间就阻塞休眠,时间到再唤醒分发执行,全程无需额外定时器。