Android 面试的原理题有非常稳定的核心区。本文逐题给出标准答法、追问陷阱与面试官想听的那句话,覆盖 Handler 为什么不会 ANR、Binder 为什么只需一次拷贝、启动模式差异、后台限制,以及 Compose 的状态提升。
Android 面试的”原理题”有非常稳定的核心区:四大组件、Handler、Binder、View 绘制、内存泄漏、Compose。这些内容不随版本剧烈变动,但考察深度差异很大——同样问”Handler 原理”,有人说到 Looper 就停了,有人能讲清为什么主线程 Looper 死循环不会 ANR。
本文按高频考点组织,每题给出”标准答法 + 追问陷阱 + 面试官想听的那句话”。版本背景:Android 17 已 GA,两端 target SDK 的年度升级窗口同时生效。

一、Handler:面试的第一道分水岭
Q1:Handler 机制涉及哪几个类?各自做什么?
四个角色必须说全:
Message:消息载体。what/arg1/obj/target,以及用于复用的消息池(obtain()从链表中取,避免频繁创建对象)。MessageQueue:按when(执行时间)排序的单向链表。注意是链表不是队列,插入是按时间做有序插入。Looper:持有 MessageQueue,loop()方法里死循环取消息并分发。每个线程最多一个 Looper,用 ThreadLocal 保证线程隔离。Handler:发送消息到 MessageQueue,并在dispatchMessage()里处理。
// loop() 的核心逻辑(简化)
public static void loop() {
final Looper me = myLooper();
final MessageQueue queue = me.mQueue;
for (;;) {
Message msg = queue.next(); // 没消息时在这里阻塞(nativePollOnce)
if (msg == null) return;
msg.target.dispatchMessage(msg);
msg.recycleUnchecked(); // 回收进消息池
}
}
Q2:主线程的 Looper 死循环为什么不会 ANR?
这是 Handler 最经典的追问,答不上来基本判定为”背过但没理解”。
关键点:ANR 不是”卡住”,而是”该处理的事件没在规定时间内处理完”。主线程 Looper 的死循环,在没有消息时是通过 nativePollOnce 进入 epoll 等待睡眠的,不消耗 CPU;有消息到来时被唤醒继续执行。所谓 ANR,是某条消息(如 INPUT 事件、BroadcastReceiver.onReceive)执行超过阈值(输入事件 5s、前台广播 10s、Service 20s),而非循环本身。
一句话版:死循环在”等消息”,不在”忙等”;唤醒靠 Linux 的 epoll 机制;ANR 是单条消息执行超时,不是循环导致的。
Q3:Handler.postDelayed 是精确延时吗?
不是。它只是在 MessageQueue 里按 when = 当前时间 + delay 插入,到点后能否立即执行取决于前面有没有积压的消息。如果主线程正被一个耗时操作占着,延时会被顺延。此外系统还可能因为对齐唤醒(Alarm alignment)、Doze 模式做批量延迟。
需要精确计时的场景(如倒计时)应当用系统时间戳实时计算剩余值,而不是累加 postDelayed 的 delay。
Q4:Handler 引起内存泄漏的原因与解法?
泄漏链:延时消息 msg.target 持有 Handler → Handler 是非静态内部类,隐式持有 Activity → Activity 已 finish 但消息还在队列里 → Activity 无法回收。
// 反例:非静态内部类 + 延时消息
class MainActivity : AppCompatActivity() {
private val handler = Handler(Looper.getMainLooper()) // 隐式持有 Activity
override fun onCreate(...) {
handler.postDelayed({ updateUI() }, 60_000)
}
}
// 正例1:静态内部类 + 弱引用
class SafeHandler(activity: MainActivity) : Handler(Looper.getMainLooper()) {
private val ref = WeakReference(activity)
override fun handleMessage(msg: Message) {
ref.get()?.updateUI()
}
}
// 正例2:生命周期感知,销毁时清空消息
override fun onDestroy() {
super.onDestroy()
handler.removeCallbacksAndMessages(null) // 关键:移除所有回调
}
二、Binder:Android 的 IPC 心脏
Q5:为什么 Android 用 Binder 而不用 Socket 或共享内存?
从三个维度对比:
要点展开:
- 性能:Binder 通过
mmap把内核缓冲区同时映射到内核空间和服务端用户空间,数据只需从发送方用户空间拷贝一次到内核,接收方直接读映射区。共享内存虽然零拷贝,但需要自己解决并发同步,且没有访问控制。 - 安全:Binder 驱动在内核态记录调用方的 UID/PID,服务端可以据此做权限校验——这是 Android 权限体系的基石。Socket 和共享内存拿不到可靠的身份标识。
- 易用性:Binder 是面向对象的(AIDL 生成的代理模式),调用远程方法像调用本地方法。
Q6:Binder 通信的完整流程?
- 注册:Server 向 ServiceManager(本身也是一个 Binder 服务,handle = 0)注册服务。
- 获取:Client 向 ServiceManager 查询,拿到服务的代理对象(
BinderProxy)。 - 调用:Client 调用代理方法,数据打包成
Parcel,通过transact()发给 Binder 驱动。 - 驱动转发:驱动根据 handle 找到目标进程,拷贝一次数据,把请求加入服务端的 todo 队列,唤醒服务端 Binder 线程。
- 执行与返回:服务端在
onTransact()中解包执行,结果沿原路返回。
同步调用时客户端线程会阻塞,所以要避免在主线程调用耗时远程方法。
Q7:AIDL 的 oneway、in/out/inout 是什么?
oneway:异步调用,立即返回不阻塞,也不接收返回值。适用于”通知”型接口。in:数据只从客户端流向服务端(默认)。out:数据只从服务端写回客户端,客户端传入的对象内容不会被读取。inout:双向。代价是两次序列化开销,实践中很少用。
三、四大组件与生命周期
Q8:Activity 启动模式有哪几种?
standard:默认,每次都新建实例,压入启动它的那个任务栈。singleTop:栈顶复用。若目标已在栈顶则复用,回调onNewIntent()。适合防止重复点击打开同一个页面。singleTask:栈内复用。若栈中已有实例,则把它上面的所有 Activity 全部出栈(clearTop),并回调onNewIntent()。适合主页、登录页这类全局唯一的入口。singleInstance:独占一个任务栈,系统内全局唯一。适合来电界面、锁屏这类需要独立存在的页面。
追问”taskAffinity 有什么用“——它指定 Activity 归属的任务栈名,与 singleTask 或 allowTaskReparenting 配合使用时才生效,可以实现”把某个 Activity 挪到另一个任务栈”。
Q9:横竖屏切换时 Activity 的生命周期?
默认情况下会销毁重建:onPause → onStop → onDestroy → onCreate → onStart → onResume。
若配置 android:configChanges="orientation|screenSize",则不重建,只回调 onConfigurationChanged()。
现代答法:不要靠
configChanges逃避重建,而应该用ViewModel正确保存状态。ViewModel的生命周期独立于 Activity 的配置变更,数据在重建后自动保留。这也是 Google 官方推荐的架构方式——把”配置变更”当成一次正常的生命周期事件来处理,而不是绕过它。
Q10:Service 的两种启动方式有什么区别?
高频追问:后台限制。Android 8.0 起禁止后台应用创建后台服务,长期任务必须用前台服务(startForegroundService + 常驻通知),或改用 WorkManager 处理可延迟的后台工作。
Q11:BroadcastReceiver 的两种注册方式?
- 静态注册(Manifest):应用未启动也能收到,但 Android 8.0 起大部分隐式广播被禁止静态注册(豁免列表内的除外)。
- 动态注册(
registerReceiver):跟随组件生命周期,必须在onDestroy/onStop中反注册,否则内存泄漏。
四、View 绘制与性能
Q12:View 的绘制流程?
三个依次进行的阶段,由 ViewRootImpl 驱动:
- measure:递归测量,父 View 通过
MeasureSpec(模式 + 尺寸)把约束传给子 View,子 View 计算出自己的宽高并调用setMeasuredDimension()。 - layout:父 View 调用子 View 的
layout(l,t,r,b),确定四个顶点位置。 - draw:依次绘制背景 → 自身(onDraw)→ 子 View(dispatchDraw)→ 滚动条等装饰。
MeasureSpec 三种模式必须记住:EXACTLY(精确值或 match_parent)、AT_MOST(wrap_content,上限为父的剩余空间)、UNSPECIFIED(无约束,常见于 ScrollView 内部、ListView 测量)。
Q13:为什么自定义 View 的 wrap_content 失效?
因为 View 的默认 onMeasure() 实现里,只要不是 EXACTLY 模式,就直接把父容器给的最大可用尺寸当成自己的尺寸——于是 wrap_content 表现得和 match_parent 一样。解法是重写 onMeasure,在 AT_MOST 模式下自己算出内容所需尺寸再 setMeasuredDimension()。
Q14:UI 卡顿的原因与排查?
根因就两类:主线程做了耗时操作(IO、大量计算、JSON 解析、数据库查询),或绘制本身太重(布局层级过深、过度绘制、频繁触发 requestLayout)。
排查手段:Systrace / Perfetto 看主线程帧耗时、Layout Inspector 看层级、GPU 过度绘制调试看叠加层数、Choreographer.FrameCallback 做线上帧率监控、BlockedCanary 之类工具抓慢方法。
五、Jetpack Compose:2026 年的必考区
Q15:Compose 和传统 View 体系的根本区别?
- 声明式:UI 是状态的函数
UI = f(state),状态变了自动重组,不需要手动setText()。 - 无 View 实例:Compose 用
LayoutNode树而非View树,没有findViewById,也不需要ViewHolder。 - 智能重组:只有读取了变化状态的可组合项才会重组,未读取的部分会被跳过(这是”重组作用域”的意义)。
Q16:什么是”状态提升”(State Hoisting)?
把状态从可组合项内部移到调用方,让可组合项变成无状态(stateless)的纯函数:参数进、事件出。
// 反例:状态藏在内部,调用方无法控制、难以复用和测试
@Composable
fun Counter() {
var count by remember { mutableIntStateOf(0) }
Button(onClick = { count++ }) { Text("$count") }
}
// 正例:状态提升,组件变成无状态的
@Composable
fun Counter(count: Int, onIncrement: () -> Unit) {
Button(onClick = onIncrement) { Text("$count") }
}
// 调用方持有状态,单一数据源
@Composable
fun CounterScreen() {
var count by remember { mutableIntStateOf(0) }
Counter(count = count, onIncrement = { count++ })
}
价值:单一数据源、可复用、可预览、可测试,并且便于把状态放进 ViewModel 从而在配置变更后保留。
Q17:remember 和 rememberSaveable 的区别?
remember:只在重组之间保留,配置变更(旋转屏幕)或进程重建后会丢失。rememberSaveable:把状态写入Bundle,能跨配置变更和进程重建恢复。要求对象可放入 Bundle,或用自定义Saver。
六、小结
Android 原理题的考察主线是“机制 + 取舍 + 踩坑”:Handler 考的是消息循环与线程模型,Binder 考的是跨进程通信在性能与安全上的权衡,组件考的是生命周期与后台限制,Compose 考的则是有没有真正从命令式思维转到声明式思维。
准备策略:每条主线准备一个自己真踩过的坑——比如”延时消息导致 Activity 泄漏,最后用弱引用加 onDestroy 清理解决的”。这类具体经历比完整背诵源码更能拿到高分。
参考:Android 开发者指南、Jetpack Compose 文档、AOSP 架构文档。行为随 Android 版本演进,涉及后台限制与权限的部分请以目标 API 等级的官方文档为准。
#Android 面试题#Handler#Binder#面试

