返回 Android
Android
18 分钟阅读

Android面试套路

Android 常见面试题的分层答法、关键词接龙链路与实战追问——从 ANR 到启动模式、从 Handler 到架构选型。

关于我在技术的这一面 里我写过:很多 Android 面试并不是在考「你能不能写出业务」,而是在用一套关键词接龙快速给候选人建模。这篇就是把那些套路整理成一份可查阅的笔记——既方便自己复习,也方便面试时按层级往下追问。

下面先给一张分层答题表:基础层答「会不会用」,进阶层答「为什么」,深入层答「系统 / 框架怎么协作」。再按常见链路展开具体问答。


分层答题总表

问题类型问题示例基础层(会用)进阶层(原理/机制)深入层(框架/系统)
ANR什么是 ANR?什么时候触发?ANR 是应用无响应。常见阈值:输入事件约 5s、前台 Service 约 20s、BroadcastReceiver 约 10s(具体以系统版本为准)system_server 侧监控超时;主线程消息队列长时间得不到处理AMS / InputDispatcher 协作;traces.txt 主线程堆栈;Binder 阻塞、锁竞争、IO 阻塞如何区分
集合并发修改forEach 里能否 remove 元素?直接 remove 会抛 ConcurrentModificationExceptionIterator fail-fast:modCountexpectedModCount 不一致Iterator.remove()CopyOnWriteArrayList、并发容器或显式锁;见 Java Lock
多线程/并发synchronizedReentrantLock 区别?synchronized 关键字自动释放;ReentrantLockunlock(),支持 tryLock、公平锁、可中断监视器锁 vs AQS;悲观锁思路偏向锁 → 轻量级锁 → 重量级锁膨胀;CAS 与乐观锁
Handler / Looper / MessageQueue为什么 Handler 要绑定 Looper?Handler 发消息,Looper 循环取消息分发每个线程最多一个 Looper;MessageQueue 按时间排序阻塞取消息主线程 ActivityThread.main() 启动 Looper;IdleHandler;同步屏障与 Choreographer
BroadcastReceiveronReceive 能做耗时操作吗?不行,超时可能 ANRonReceive 在主线程,阻塞即卡 UIgoAsync()WorkManager、前台 Service;有序广播与粘性广播差异
Activity 生命周期哪一步不宜做耗时操作?onCreate / onStart / onResume 都在主线程,不宜阻塞启动流程涉及 ActivityThreadInstrumentationActivityClientRecordWindowManager 布局与首帧绘制;冷启动链路
ContentProvider初始化能做耗时操作吗?不行,Application 启动阶段在主线程初始化ContentProvider.onCreate() 早于 Application.onCreate()懒加载、异步初始化框架(如 App Startup)规避启动阻塞
内存泄漏 / GCHandler 为什么容易泄漏?非静态内部类持有外部 Activity 引用静态 Handler + WeakReference<Activity>引用链:Message → Handler → Activity;GC Roots 与可达性分析
UI 阻塞主线程耗时如何避免 ANR?子线程、线程池、CoroutineWorkManager结果回主线程用 Handler / runOnUiThread / withContext(Main)Choreographer vs VSYNC;掉帧与 Systrace / Perfetto
启动模式singleTasksingleInstance 区别?standard / singleTop / singleTask / singleInstance 四种singleTask 栈内复用并 clearTopsingleInstance 独占任务栈ActivityStackTaskRecordIntent FlagsdocumentLaunchMode
View 绘制measure / layout / draw 顺序?自上而下 measurelayoutdrawMeasureSpec 模式与 wrap_content 二次测量硬件加速RenderThread、过度绘制排查
Binder / IPC为什么跨进程要 Binder?AIDL、Messenger、ContentProvider 都是 IPC 手段对比 Socket、共享内存:Binder 一次拷贝、有身份校验Binder 驱动、BBinder / BpBinder;线程池与 oneway 调用
架构模式MVP 和 MVVM 怎么选?MVP:Presenter 回调 View;MVVM:View 观察 ViewModel数据流单向性、可测试性、生命周期绑定MVI + UDF;与 SSOT 的关系
异步选型RxJava 和 Coroutines 怎么取舍?老项目 Rx 多;新项目倾向 Coroutines + Flow结构化并发、viewModelScope 自动取消异步演进Kotlin Coroutine

关键词接龙:怎么从一道题聊到架构师

面试里常见的不止单题,而是一条链路。上位者不需要每个细节都精通,只要备好题型,顺着关键词往下接即可。下面几条是 Android / Java 岗最高频的链子。

链路一:集合 → 并发

ArrayList forEach remove
  → ConcurrentModificationException
  → fail-fast / fail-safe
  → synchronized / ReentrantLock
  → AQS / CAS
  → ConcurrentHashMap
  → 分段锁(JDK 7)/ CAS + synchronized 桶锁(JDK 8+)

若从 HashMap 切入,链子是:

HashMap 1.7 vs 1.8
  → 数组 + 链表 → 红黑树
  → 头插法扩容成环(1.7)
  → hash 扰动、负载因子、树化阈值
  → 线程不安全 → ConcurrentHashMap

详细笔记见 HashMapJava Lock分段锁

链路二:主线程 → 消息机制 → ANR

主线程不能做什么
  → Handler / Looper / MessageQueue
  → 消息延时与屏障
  → 耗时操作为什么 ANR
  → traces.txt 怎么看
  → Binder 慢调用 / 锁等待 / IO

链路三:四大组件 → 启动 → 进程

Activity 生命周期
  → 启动模式 + Task 栈
  → Application / ContentProvider 初始化顺序
  → 冷启动优化
  → 多进程与 Binder

链路四:UI → 性能

布局嵌套过深
  → measure 两次问题
  → RecyclerView 卡顿
  → Bitmap 采样 / 复用 / 池化
  → 内存抖动 → GC → 掉帧
  → Systrace / Android Profiler

链路五:架构 → 工程化

God Activity
  → MVP / MVVM / MVI
  → Repository + SSOT
  → 依赖注入 Hilt
  → 模块化 / 路由
  → 测试金字塔

经典起手式:ArrayList 能在 forEach 里 remove 吗?

这是 技术那一面 里提到的经典起手式,也是很多面试官用来快速分流的第一题。

fun main() {
  val data = mutableListOf(1, 2, 3, 4, 5, 6, 7, 8)
 
  data.forEach {
    data.remove(it) // 这样会出现问题吗?
  }
}

标准答法

不可以。 遍历过程中结构性修改集合,会抛 ConcurrentModificationException

追问:为什么?

forEach 底层走迭代器。迭代器每次 next() 会检查 modCount == expectedModCount;你对 dataremove() 会改 modCount,检查失败即 fail-fast 抛异常。

再追问:怎么安全删除?

方式适用场景
iterator.remove()单线程遍历中删当前元素
倒序 for (i in list.lastIndex downTo 0)按索引删,避免错位
removeIf { }(Java 8+)按条件批量删
CopyOnWriteArrayList读多写少、并发读
新建集合收集「要保留的」函数式 filter,不改原集合

建模意义

  • 答「可以」→ 基础并发意识薄弱,通常不必深聊。
  • 答「不可以」且能说出 ConcurrentModificationException → 可接 链路。
  • 能主动提到 CopyOnWriteArrayList 与适用边界 → 有实战经验。

启动模式:A → B → C → B 返回栈是什么?

technical-side 里那道 SingleInstance 题,是 Android 面试里很典型的「背了定义、一举例就宕机」类型。

设定: ActivityA、ActivityC 为 standard,ActivityB 为 singleInstance。启动顺序 A → B → C → B,再逐个按返回键。

各模式速记

模式行为
standard每次 startActivity 新建实例,入当前 Task
singleTop栈顶已是该 Activity 则复用,触发 onNewIntent
singleTask栈内已有则清顶复用;可能把实例提到前台
singleInstance独占一个 Task,全局仅此实例

本题推演

  1. A → B:A 在 Task1;B 是 singleInstance,系统为 B 新建 Task2,B 独占 Task2。
  2. B → C:从 B 启动 C(standard),C 进入 Task2(与 B 同栈,因为 B 所在 Task 成为前台)。
  3. C → B:B 已在 Task2 栈内,singleInstance 不会新建 B,而是把 B 之上的 C 出栈(或触发 onNewIntent 把 B 带到前台)。此时 Task2 栈:B(C 被干掉)。
  4. 按返回:在 B 按返回 → Task2 空 → 回到 Task1 的 A。在 A 按返回 → 退出。

要点: singleInstance 的 Activity 所在 Task 里,通常只有它自己是「根」,后续 standard 启动的页面会进这个 Task,但不会再新建第二个 B 实例。

面试时画图比背文字靠谱——画两个 Task 框,比干念 API 清晰得多。


Handler 机制:必问三板斧

1. 结构

  • Handler:发消息、处理消息(sendMessage / post / handleMessage)。
  • Looper:死循环取消息,分发给 Handler。
  • MessageQueue:单链表优先级队列,按 when 阻塞在 nativePollOnce

2. 主线程 Handler 从哪来?

ActivityThread.main()
  → Looper.prepareMainLooper()
  → Looper.loop()

主线程的 LooperActivityThread 启动时创建,因此主线程 Handler 默认就有消息循环。

3. 内存泄漏怎么答?

非静态内部类 Handler 隐式持有外部类引用;若 Message 在队列里延时未处理,Activity 已 finish 仍无法回收。

修法: 静态内部类 Handler + WeakReference 持 Activity;或在 onDestroyremoveCallbacksAndMessages(null)

4. 常见追问

追问要点
postsendMessage 区别?最终都进队列;post(Runnable) 封装成 Message,callback 字段执行
子线程能 new Handler 吗?可以,但必须先 Looper.prepare() + Looper.loop(),否则无消息循环
同步屏障是什么?插入屏障 Message 后,同步消息暂停,优先处理异步消息;Choreographer 利用此机制优先处理帧回调

ANR:怎么答才像做过线上排查

触发条件(记大概,别死背数字)

场景常见超时
输入分发(点击等)约 5s
BroadcastReceiver约 10s(前台)/ 60s(后台,视版本)
前台 Service约 20s
ContentProvider 发布约 10s

排查步骤

  1. traces.txt 或 Bugly / Firebase 上的 ANR 堆栈。
  2. 主线程 在等什么:BLOCKED(锁)、Native(IO / Binder)、Runnable(自己的耗时逻辑)。
  3. 对照 Binder 线程 是否也有阻塞——有时是同步 Binder 调用拖死主线程。
  4. 复现路径 + Systrace / StrictMode 辅助定位。

修法清单

  • IO / 网络 / 大 JSON 解析 → 后台线程或 Coroutine Dispatchers.IO
  • 主线程锁等待 → 调整锁粒度、避免主线程持锁等子线程。
  • 启动阶段初始化过重 → 懒加载、App Startup、异步预热。
  • 广播 / Provider 里做重活 → 异步化或拆进程。

View 与渲染:RecyclerView 卡顿怎么聊

面试官问「列表滑动掉帧你怎么查」,可以按这条线答:

  1. 是不是主线程干了活——在 onBindViewHolder 里解码大图、算复杂布局。
  2. 布局层级——ConstraintLayout 扁平化;避免 wrap_content 嵌套导致多次 measure。
  3. 图片——采样 inSampleSize、RGB_565、Glide 尺寸与缓存策略。
  4. 对象创建——滑动时频繁 new 触发 GC 抖动,看 Memory Profiler 锯齿。
  5. 预取与缓存——setItemViewCacheSizeRecycledViewPool 共享、DiffUtil 减少全量刷新。
  6. 工具——GPU 过度绘制、Systrace 看 Choreographer#doFrame 是否超过 16.6ms。

Binder 与跨进程:问到哪一层

基础: Android 四大组件跨进程靠 Binder;AIDL 生成 Stub / Proxy。

进阶: 一次拷贝(相比传统 IPC);由 binder 驱动在内核完成;调用方通过 BpBinder 代理,服务端 BBinder 处理。

深入: transact 同步调用默认占 Binder 线程池;主线程里同步跨进程大流量容易卡 UI;oneway 异步但不保证顺序。

业务里常举的例子:ActivityManagersystem_server 通信、ContentProvider 跨进程查询、Messenger 轻量 IPC。


架构与 Jetpack:高级工程师常问什么

主题基础答法可加深
MVVMView 观察 ViewModel 的 LiveData / StateFlowviewModelScope 生命周期;配置变更数据保留
MVI单一 State + Intent / Event,单向数据流与 Compose 契合;状态可预测、易测试
Repository统一数据入口,屏蔽网络 / 本地SSOT 原则
RoomSQLite 封装 + Flow 观察迁移、多表关联、与 RemoteMediator 分页
Compose声明式 UI、重组稳定性、rememberderivedStateOf 减少重组
Hilt编译期 DI,减少手写 Module作用域:Singleton / ActivityRetained / ViewModel

不必在面试里站队「Compose 一定取代 XML」——把团队熟练度、招聘、存量代码、设计稿复杂度讲清楚,比喊口号加分。


异步编程:Callback → Rx → Coroutines

Android 岗几乎都会问异步。建议按 异步演进 的脉络答:

范式一句话
Callback简单场景够用;链长了嵌套地狱
RxJava流式组合强;学习曲线陡,生命周期要管好
Coroutines同步写法写异步;结构化并发 + Flow 是当下默认推荐

面试金句: 主线程只做 UI;IO 切 Dispatchers.IO;结果回 UI 用 withContext(Main)flowOn;在 viewModelScope 里启动,页面销毁自动取消。


内存与稳定性

OOM 与泄漏

  • 泄漏: 静态变量持 Activity、匿名内部类、未注销监听、单例持 Context(应持 ApplicationContext)。
  • OOM: 大图、缓存无上限、泄漏堆积。工具:LeakCanary、Heap Dump + MAT。
  • GC: 记住「从 GC Roots 不可达则回收」;频繁分配小对象 → 年轻代 GC 频繁 → 卡顿。

Crash 与线上质量

  • 混淆堆栈 → 用 mapping 还原。
  • Crash 率突增 → 看版本、渠道、系统分布;能否热修 vs 回滚。
  • 灰度 5% 指标异常 → 停扩量、保留现场、对比基线。

给面试双方的一点实话

对面试官

  • 引导式追问比「连珠炮」更能区分真懂和背题。对方答出关键词后,往他熟悉的项目场景引,比硬抠红黑树左旋右旋体面得多。
  • 偶尔答不上来是状态问题,连续答不上来才是盲区——见 技术那一面
  • 用关键词链建模很快,但别用一条链否定一个人;Android 工程师的价值不只在背八股。

对候选人

  • 准备2~3 个深挖项目:启动优化、卡顿治理、架构重构、线上事故——比散点背题更能打。
  • 八股要会分层答:先 30 秒基础答案,看对方眼神再决定要不要展开 Binder 驱动。
  • 真不会的,诚实说「这块我回去补」不丢人;硬编更容易被接龙击穿。

速查:还可以往哪些方向接

若时间充裕,下面这些在 Android 中高级面试里出现频率也很高,可按同样「基础 → 进阶 → 深入」准备:

方向示例问题
Kotlininlinereified协程挂起原理sealed class 使用场景
网络HTTPS 握手、证书锁定、Retrofit 动态代理、超时与重试策略
存储SharedPreferences 陷阱、DataStore vs MMKV、Room 事务
安全加固、反编译、密钥存放、组件导出风险
Gradle编译慢怎么优化、多 Module 依赖、版本对齐
兼容性分区存储、通知权限、厂商 ROM 差异
音视频硬解软解、Surface 渲染、RTC 弱网策略(若岗位相关)

面试是缘分,也是抽样。把套路看懂,是为了知道自己该补哪一块——不是为了在一场对话里证明你什么都会。

相关文章

更多
公开
13 分钟
非主流工程师的自白
面试关键词接龙与「答不上来是状态还是盲区」——这篇的出发点。
Java / Kotlin
公开
8 分钟
HashMap in Java
关键词链第一棒——HashMap 1.7 / 1.8 数据结构变化。
Java / Kotlin
公开
8 分钟
Java Lock
接在 ConcurrentModificationException 之后——synchronized、Lock、CAS。
Java / Kotlin
公开
9 分钟
Kotlin Coroutine
异步选型追问——Callback、RxJava、Coroutines 怎么答。
Android
公开
17 分钟
当我们在谈MVP、MVVM、MVI的时候,我们到底在谈什么?
MVP / MVVM / MVI 架构模式怎么在面试里讲清楚。