【Android面试】Handler专题

文章目录

  • 一、基础原理与核心概念(答案)
    • [1. Handler、Message、MessageQueue、Looper 四者关系](#1. Handler、Message、MessageQueue、Looper 四者关系)
    • [2. Looper.prepare () 和 loop () 做了什么,主线程为什么不用 prepare](#2. Looper.prepare () 和 loop () 做了什么,主线程为什么不用 prepare)
    • [3. 一个线程能有几个 Looper、MessageQueue](#3. 一个线程能有几个 Looper、MessageQueue)
    • [4. Handler 发送消息方式及区别](#4. Handler 发送消息方式及区别)
    • [5. 消息屏障是什么,作用](#5. 消息屏障是什么,作用)
    • [6. 同步消息 vs 异步消息](#6. 同步消息 vs 异步消息)
  • 二、源码深度剖析(答案)
    • [1. Looper.loop () 流程,为什么不 ANR](#1. Looper.loop () 流程,为什么不 ANR)
    • [2. MessageQueue.enqueueMessage 如何排序](#2. MessageQueue.enqueueMessage 如何排序)
    • [3. MessageQueue.next () 阻塞与唤醒](#3. MessageQueue.next () 阻塞与唤醒)
    • [4. Handler.dispatchMessage 执行顺序](#4. Handler.dispatchMessage 执行顺序)
    • [5. Message.obtain() vs new Message()](#5. Message.obtain() vs new Message())
    • [6. Handler 内存泄漏原因](#6. Handler 内存泄漏原因)
  • 三、实战问题与异常场景(答案)
    • [1. 子线程直接 new Handler 会怎样](#1. 子线程直接 new Handler 会怎样)
    • [2. 子线程创建 Handler 建议](#2. 子线程创建 Handler 建议)
    • [3. 移除消息方式与原理](#3. 移除消息方式与原理)
    • [4. post (Runnable) 运行在哪个线程](#4. post (Runnable) 运行在哪个线程)
    • [5. Handler 处理消息抛异常会怎样](#5. Handler 处理消息抛异常会怎样)
    • [6. 跨线程通信原理](#6. 跨线程通信原理)
  • 四、进阶优化与大厂高频(答案)
    • [1. IdleHandler 是什么](#1. IdleHandler 是什么)
    • [2. postDelayed 为什么不精准](#2. postDelayed 为什么不精准)
    • [3. loop 死循环为什么进程不退出](#3. loop 死循环为什么进程不退出)
    • [4. Handler 实现线程切换](#4. Handler 实现线程切换)
    • [5. 系统哪些地方用 Handler](#5. 系统哪些地方用 Handler)
    • [6. 内存泄漏解决方案](#6. 内存泄漏解决方案)
    • [7. Handler 性能优化](#7. Handler 性能优化)
    • [8. Android 10+ 对 Handler 的变更](#8. Android 10+ 对 Handler 的变更)
    • [9. Handler vs RxJava vs 协程](#9. Handler vs RxJava vs 协程)
    • [10. 消息复用池实现](#10. 消息复用池实现)
  • 五、综合设计与原理延伸(答案)
    • [1. 手写简易 Handler 核心思路](#1. 手写简易 Handler 核心思路)
    • [2. Handler 实现定时任务](#2. Handler 实现定时任务)
    • [3. 跨进程 Handler 能用吗](#3. 跨进程 Handler 能用吗)
    • [4. Handler 与 UI 刷新、VSYNC](#4. Handler 与 UI 刷新、VSYNC)
    • [5. 用 Handler 排查卡顿](#5. 用 Handler 排查卡顿)

一、基础原理与核心概念(答案)

1. Handler、Message、MessageQueue、Looper 四者关系

Looper:不断从 MessageQueue 中取消息,驱动消息循环,一个线程只有一个 Looper。

MessageQueue:消息队列,按时间优先级维护消息链表。

Message:承载任务内容、回调、标记、延迟时间。

Handler:负责发送消息到 MessageQueue、处理消息。

整体流程:Handler sendMessage → Message 入队 MessageQueue → Looper.loop () 不断 next () 取消息 → 分发给 Handler 处理。

2. Looper.prepare () 和 loop () 做了什么,主线程为什么不用 prepare

Looper.prepare():创建 Looper 实例,绑定到当前线程(ThreadLocal),并创建 MessageQueue。一个线程只能调用一次,否则抛异常。

Looper.loop():开启死循环,不断从 MessageQueue.next () 取消息,有消息就处理,没消息就阻塞休眠。

主线程为什么不用手动 prepare:ActivityThread.main () 中系统已经调用 Looper.prepareMainLooper () 和 Looper.loop ()。

3. 一个线程能有几个 Looper、MessageQueue

一个线程只能有 1 个 Looper,由 ThreadLocal 保证唯一。

Looper 内部持有唯一 1 个 MessageQueue。

所以:1 线程 = 1 Looper = 1 MessageQueue。

4. Handler 发送消息方式及区别

常用:

sendMessage(Message)

sendMessageDelayed(Message, delay)

sendMessageAtTime(Message, uptime)

post (Runnable) → 封装成 Message.callback

postDelayed / postAtTime

区别:

post 系列最终都是 sendMessage。

post (Runnable) 会把 Runnable 赋值给 Message.callback。

delayed 是相对时间,atTime 是绝对开机时间。

5. 消息屏障是什么,作用

消息屏障(Barrier)是一种特殊消息,target 为 null。

作用:拦截同步消息,只允许异步消息通过。

典型场景:UI 绘制、VSYNC 到来时,优先处理绘制消息,保证界面流畅。

6. 同步消息 vs 异步消息

同步消息:普通消息,遇到屏障会被阻塞。

异步消息:设置 flag = FLAG_ASYNCHRONOUS,不受屏障拦截。

发送异步消息:

message.setAsynchronous(true)

Handler 创建时传 async = true(API 28+)

二、源码深度剖析(答案)

1. Looper.loop () 流程,为什么不 ANR

loop () 核心逻辑:

死循环 for (;😉

msg = queue.next()

如果 msg 不为空,调用 msg.target.dispatchMessage (msg)

回收消息 msg.recycle ()

为什么不会 ANR:

ANR 是消息处理超时,不是 loop 死循环本身。

没有消息时线程会阻塞在 nativePollOnce,释放 CPU,不卡主线程。

2. MessageQueue.enqueueMessage 如何排序

消息按 when(执行时间) 从小到大插入链表。

时间更早的插前面,时间相同按先后顺序。

不是队列,是有序单链表。

3. MessageQueue.next () 阻塞与唤醒

没有消息或未到执行时间:调用 nativePollOnce 阻塞,释放 CPU。

有新消息入队:nativeWake 唤醒线程。

屏障存在时:只遍历异步消息,同步消息跳过。

4. Handler.dispatchMessage 执行顺序

优先级:

Message.callback(Runnable)优先执行

否则 Handler 的 handleMessage (msg)

最后才是 Callback 接口

源码顺序:if (msg.callback != null) → handleCallback (msg)else if (mCallback != null) → mCallback.handleMessage (msg)else → handleMessage (msg)

5. Message.obtain() vs new Message()

new Message ():每次新建对象,频繁创建会 GC。

obtain ():从全局消息池(sPool) 复用对象,避免频繁 GC。

池大小默认 50,复用后 recycle 放回池。

6. Handler 内存泄漏原因

Handler 是非静态内部类,持有外部类(Activity)隐式引用。

Message 持有 Handler 引用(msg.target = this)。

Message 在队列中延迟 / 排队 → GC 无法回收 Activity。

主线程 Handler 泄漏:消息生命周期 > Activity 生命周期。

子线程 Handler 泄漏:子线程生命周期可能比 Activity 长,同样持有引用。

三、实战问题与异常场景(答案)

1. 子线程直接 new Handler 会怎样

会崩溃:

Can't create handler inside thread that has not called Looper.prepare()

原因:子线程默认没有 Looper,无法创建 Handler。

解决:Looper.prepare ()Handler handler = new Handler ()Looper.loop ()

2. 子线程创建 Handler 建议

不建议直接在子线程随便创建并长期持有。规范用法:

使用 HandlerThread(自带 Looper)

用完调用 quit/quitSafely

静态内部类 + 弱引用

3. 移除消息方式与原理

removeMessages(int what)

removeCallbacks(Runnable)

removeCallbacksAndMessages(null)

原理:遍历 MessageQueue 链表,把 target 是当前 Handler、匹配 what 或 callback 的消息从链表摘除。

4. post (Runnable) 运行在哪个线程

运行在 Looper 所在线程。主线程 Handler → 主线程执行。子线程 Handler → 子线程执行。

5. Handler 处理消息抛异常会怎样

当前消息崩溃,会被 catch 住。

Looper 不会退出,继续取下一条消息。

主线程不会因此崩溃,只是该消息任务失败。

6. 跨线程通信原理

核心:Looper 与线程绑定,Handler 发消息到对应线程的队列。A 线程 Handler 发送 → B 线程 Looper 取出 → 在 B 线程执行。

典型:子线程发消息 → 主线程 Handler 更新 UI。

四、进阶优化与大厂高频(答案)

1. IdleHandler 是什么

队列空闲时执行的回调。

当 MessageQueue 没有待执行消息时触发 queueIdle ()。

使用场景:

启动优化:延迟初始化、 you-get 等轻任务

测量 View 绘制完成

低优先级任务

原理:next () 空闲时执行。

2. postDelayed 为什么不精准

延迟基于 系统开机时间 uptimeMillis。

消息队列可能被前面消息阻塞。

系统休眠、CPU 调度会影响执行时机。

所以只能保证 "至少延迟",不保证精准。

3. loop 死循环为什么进程不退出

主线程 loop 是进程生命线,退出 loop 则 APP 进程结束。

无消息时阻塞在 native,不占 CPU。

系统事件、触摸、广播、服务等都会唤醒消息循环。

4. Handler 实现线程切换

原理:Handler 绑定目标线程 Looper → 发送消息 → 目标线程 Looper 调度执行。

案例:子线程网络请求 → 主线程 Handler 更新 UI。

5. 系统哪些地方用 Handler

Activity 生命周期调度

View 绘制、触摸事件分发

BroadcastReceiver 回调

Service 启动、绑定

AsyncTask(旧版本)

Choreographer 刷新 UI

6. 内存泄漏解决方案

静态内部类 + WeakReference

Activity 销毁时 removeCallbacksAndMessages (null)

避免使用非静态内部类、匿名内部类

使用 Lifecycle 自动解绑

子线程 Handler 用完 quit

7. Handler 性能优化

避免大量频繁发送消息

使用 Message.obtain () 复用

避免在 handleMessage 做耗时操作

延迟消息及时移除

避免消息堆积

IdleHandler 做低优先级任务

8. Android 10+ 对 Handler 的变更

加强异步消息限制,部分系统 API 限制使用异步消息

对后台队列、隐式消息做更严格管控

修复部分同步屏障、消息调度漏洞

更严格的线程检查与异常提示

9. Handler vs RxJava vs 协程

Handler:轻量、系统原生、适合简单线程切换

RxJava:流式操作、线程切换强、组合事件强、学习成本高

协程:轻量线程、结构化并发、代码更简洁、无回调地狱

10. 消息复用池实现

sPool 是静态全局 Message 链表

obtain () 从头部取

recycle () 清空字段并放回头部

限制最大数量,避免内存占用过高

优势:减少 GC,提升流畅度。

五、综合设计与原理延伸(答案)

1. 手写简易 Handler 核心思路

需要实现:

ThreadLocal 保存 Looper

Looper 持有 MessageQueue

MessageQueue 用链表维护消息

Handler 发送、入队、调度

loop () 循环取消息执行

最简模型:Looper.prepare () → 创建队列Handler.sendMessage → 入队loop () 轮询 → 分发处理

2. Handler 实现定时任务

方式:sendMessageDelayed 递归发送。

优点:简单、轻量、同线程安全。缺点:不准、队列阻塞会延迟、进程休眠不准。对比 WorkManager/AlarmManager:后者更适合后台精确定时。

3. 跨进程 Handler 能用吗

不能直接用。

Handler 基于同一进程内存空间。

跨进程需要 Binder 机制。

替代:AIDL、Messenger、ContentProvider、Broadcast。

4. Handler 与 UI 刷新、VSYNC

Choreographer 依赖 Handler 接收 VSYNC 信号。

绘制消息通过异步消息 + 屏障优先执行。

保证 UI 刷新消息优先级高于普通业务消息。

5. 用 Handler 排查卡顿

监控消息 dispatch 前后耗时

自定义 Handler 统计排队时间

利用 Looper 的 logging 监听消息执行

定位哪个消息执行超时

结合 systrace 看消息阻塞链

相关推荐
tkevinjd3 小时前
Redis主从复制
数据库·redis·后端·缓存·面试
进击的女IT3 小时前
Java使用poi-tl实现word模版渲染文本/图片
java·数据库·word
不会写DN3 小时前
PHP mysqli 实用开发指南
android·开发语言·php
庞轩px3 小时前
ThreadLocal 源码分析与内存泄漏问题
java·jvm·线程·threadlocal·内存泄露·key-value
小江的记录本3 小时前
【Logback】Logback 日志框架 与 SLF4J绑定、三层模块、MDC链路追踪、异步日志、滚动策略
java·spring boot·后端·spring·log4j·maven·logback
随风,奔跑3 小时前
Spring Boot笔记
java·spring boot·笔记·后端
ruiang3 小时前
Spring Boot 3.3.4 升级导致 Logback 之前回滚策略配置不兼容问题解决
java·spring boot·logback
yoothey3 小时前
我对Java Web开发中多线程的困惑
java·开发语言·前端
xiufeia3 小时前
JMeter
java·jmeter·tomcat·高并发