高级 Android 开发者面试题:Kotlin、Compose 与架构

Milad Bonakdar
作者
围绕 Kotlin 协程、Jetpack Compose 状态、应用架构、性能、测试、离线优先数据和安全,准备高级 Android 开发者面试。
引言
高级 Android 开发者面试通常会考察架构取舍、生命周期安全的并发、Compose 状态归属、离线优先的数据流、性能诊断、测试策略和安全。好的回答不只是说会用哪个 API,还要说明它为什么符合产品约束,以及在生产环境中如何排查问题。
你可以把这份指南当作实战清单。优先准备 Kotlin 协程与 Flow、ViewModel 与状态管理、基于 Room 的 repository、Compose 与旧 UI 的取舍、启动和内存性能,以及你能从自己项目中讲清楚的例子。
如何使用这些问题
- 把每个回答练成一个简短决策:背景、取舍、实现、失败模式。
- 将技术选择连接到用户影响,例如更快启动、可靠离线读取、更安全的 token 存储或更少生命周期 bug。
- 为架构、性能、测试和安全各准备一个真实项目故事。
高级 Kotlin & 语言特性 (5 题)
1. 解释 Kotlin 协程及其相对于线程的优势。
答案: 协程是轻量级的并发原语,允许以顺序方式编写异步代码。
- 相对于线程的优势:
- 轻量级: 可以创建数千个协程而不会出现性能问题
- 结构化并发: 父子关系确保正确的清理
- 取消支持: 内置取消传播
- 异常处理: 结构化异常处理
- 关键组件:
- CoroutineScope: 定义生命周期
- Dispatchers: 控制执行上下文(Main, IO, Default)
- suspend 函数: 可以暂停和恢复
稀有度: 非常常见 难度: 困难
2. 什么是密封类?应该在何时使用它们?
答案: 密封类表示受限的类层次结构,其中所有子类在编译时都是已知的。
- 优点:
- 详尽的
when表达式 - 类型安全的状态管理
- 比枚举更适合复杂数据
- 详尽的
- 用例: 表示状态、结果、导航事件
稀有度: 常见 难度: 中等
3. 解释 Kotlin Flow 及其与 LiveData 的区别。
答案: Flow 是 Kotlin 的冷异步流,它按顺序发出值。
- Flow vs LiveData:
- Flow: 冷流,支持操作符,不感知生命周期,更灵活
- LiveData: 热流,感知生命周期,Android 特有,对于 UI 更简单
- Flow 类型:
- Flow: 冷流(在收集时启动)
- StateFlow: 热流,持有当前状态
- SharedFlow: 热流,用于事件
稀有度: 非常常见 难度: 困难
4. 什么是内联函数?应该在何时使用它们?
答案: 内联函数将函数体复制到调用点,避免了函数调用开销。
- 优点:
- 消除 lambda 分配开销
- 允许从 lambda 进行非局部返回
- 对于高阶函数有更好的性能
- 用例: 带有 lambda 参数的高阶函数
- 权衡: 增加代码大小
稀有度: 中等 难度: 困难
5. 解释 Kotlin 中的委托。
答案: 委托允许对象将其某些职责委托给另一个对象。
- 类委托:
by关键字 - 属性委托: Lazy, observable, delegates
- 优点: 代码重用,组合优于继承
稀有度: 中等 难度: 中等
架构模式 (6 题)
6. 解释 MVVM 架构及其优点。
答案: MVVM (Model-View-ViewModel) 将 UI 逻辑与业务逻辑分离。
- Model: 数据层(repositories, data sources)
- View: UI 层(Activities, Fragments, Composables)
- ViewModel: 演示逻辑,在配置更改后依然存在
- 优点: 可测试,关注点分离,感知生命周期
稀有度: 非常常见 难度: 中等
7. 什么是整洁架构?如何在 Android 中实现它?
答案: 整洁架构将代码分离成具有清晰依赖关系的不同层。
- Presentation: UI, ViewModels
- Domain: Use Cases, Business Logic, Entities
- Data: Repositories, Data Sources (API, Database)
- 依赖规则: 内部层不知道外部层
稀有度: 常见 难度: 困难
8. 解释依赖注入和 Dagger/Hilt。
答案: 依赖注入为类提供依赖项,而不是在内部创建它们。
- 优点: 可测试性,松耦合,可重用性
- Dagger: 编译时 DI 框架
- Hilt: 简化了 Android 的 Dagger
稀有度: 非常常见 难度: 困难
9. 什么是 Repository 模式?为什么要使用它?
答案: Repository 模式抽象了数据源,为数据访问提供了一个干净的 API。
- 优点:
- 单一数据源
- 集中式数据逻辑
- 易于切换数据源
- 可测试
- 实现: 在多个数据源之间进行协调
稀有度: 非常常见 难度: 中等
10. 解释单 Activity 架构。
答案: 单 Activity 架构使用一个 Activity 搭配多个 Fragment,由 Navigation Component 管理。
- 优点:
- 简化导航
- Fragment 之间共享 ViewModel
- 更好的动画
- 更容易的深度链接
- Navigation Component: 处理 fragment 事务、返回栈、参数
稀有度: 常见 难度: 中等
11. 什么是 MVI (Model-View-Intent) 架构?
答案: MVI 是一种受 Redux 启发的单向数据流架构。
- 组件:
- Model: 表示 UI 状态
- View: 渲染状态,发出 intents
- Intent: 用户操作/事件
- 优点: 可预测状态,更容易调试,时间旅行调试
稀有度: 中等 难度: 困难
性能 & 优化 (5 题)
12. 如何优化 RecyclerView 性能?
答案: 多种策略可以提高 RecyclerView 的滚动性能:
- ViewHolder 模式: 重用视图(内置)
- DiffUtil: 高效的列表更新
- Stable IDs: 覆盖
getItemId()并设置setHasStableIds(true) - 预取: 增加预取距离
- 图片加载: 使用 Glide/Coil 等库,并进行适当的大小调整
- 避免繁重操作: 不要在
onBindViewHolder中执行昂贵的计算 - 嵌套 RecyclerView: 设置
setRecycledViewPool()和setHasFixedSize(true)
稀有度: 非常常见 难度: 中等
13. 如何检测和修复 Android 中的内存泄漏?
答案: 当对象在内存中保留的时间超过需要的时间时,就会发生内存泄漏。
- 常见原因:
- Context 泄漏(Activity/Fragment 引用)
- 静态引用
- 匿名内部类
- 监听器未注销
- 协程未取消
- 检测工具:
- LeakCanary 库
- Android Studio Memory Profiler
- Heap dumps
稀有度: 非常常见 难度: 中等
14. 如何优化应用启动时间?
答案: 更快的启动速度可以改善用户体验:
- 延迟初始化: 仅在需要时才初始化对象
- 避免在 Application.onCreate() 中进行繁重的工作:
- 移动到后台线程
- 延迟非关键初始化
- Content Providers: 最小化或延迟加载
- 减少依赖项: 库越少 = 启动速度越快
- App Startup Library: 结构化初始化
- Baseline Profiles: 提前编译提示
稀有度: 常见 难度: 中等
15. 如何有效地处理位图加载和缓存?
答案: 高效的图像处理对于性能至关重要:
- 库: Glide, Coil (自动处理缓存)
- 手动优化:
- 缩减采样(加载较小的图像)
- 内存缓存 (LruCache)
- 磁盘缓存
- 位图池
稀有度: 常见 难度: 困难
16. 什么是 ANR?如何防止它?
答案: ANR(Application Not Responding,应用程序无响应)发生在主线程被阻塞太长时间时。
- 原因:
- 在主线程上进行繁重的计算
- 在主线程上进行网络调用
- 在主线程上进行数据库操作
- 死锁
- 预防:
- 将繁重的工作移动到后台线程
- 使用带有适当 dispatchers 的协程
- 避免在主线程上使用 synchronized 块
- 使用 WorkManager 进行后台任务
稀有度: 常见 难度: 简单
测试 (3 题)
17. 如何为 ViewModels 编写单元测试?
答案: 应该使用模拟的依赖项隔离地测试 ViewModels。


