返回 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 弱网策略(若岗位相关)

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

相关文章

更多
17 分钟
关于我在技术的这一面
面试关键词接龙与「答不上来是状态还是盲区」——这篇的出发点。
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 架构模式怎么在面试里讲清楚。