Android 高级工程师面试参考答案:架构设计、Jetpack 与 Compose

架构设计、Jetpack 与 Compose

高级岗位的架构题,本质上不是考你会不会背 MVVMMVIClean Architecture 这些词,而是看你能不能在复杂业务里把状态、职责、依赖和变更成本控制住。

1. 为什么很多项目从 MVP 转向 MVVM

参考答案

MVP 的核心问题通常不是理论不成立,而是在中大型项目里容易出现大量接口、样板代码和状态分散。MVVM 借助 ViewModel、响应式数据流和生命周期感知能力,更容易把页面状态集中管理,减少 Activity/Fragment 直接承载业务逻辑。

但不要把它回答成"MVVM 一定比 MVP 高级"。更准确的说法是:MVVM 在现代 Android 生态里和 Jetpack 套件协同更自然,所以维护成本通常更低。

面试官继续追问什么

  • MVVM 的状态膨胀问题怎么处理?
  • 为什么有些团队又转向 MVI
  • 架构分层多了以后,如何避免过度设计?

追问怎么答

  • 状态膨胀要靠分层和拆域处理,不要把所有弹窗、列表、筛选和事件都堆进一个巨大的页面状态里。
  • 团队转向 MVI,通常是因为状态来源太散、排查困难,希望用单向数据流把复杂度收回来。
  • 避免过度设计的关键是按当前复杂度付费,能稳定解决问题即可,不要为了"看起来完整"提前抽太多层。

直接套用句式

"我理解从 MVP 转向 MVVM 不是因为它更新,而是因为页面复杂度上来以后,原来的回调和接口方式很难继续控住状态。我们更需要的是把页面状态和职责收回来,而不是单纯换个名词。"

2. ViewModel 为什么适合承载页面逻辑?

参考答案

ViewModel 的核心价值有三点:

  • 跨配置变更保留页面状态
  • 让 UI 容器与业务逻辑解耦
  • 更适合作为状态流的汇聚点

它不应该变成"什么都往里塞"的大仓库。好的 ViewModel 更像页面状态协调者:接收用户意图、调用用例或仓库层、转换成可观察状态,再把一次性事件和稳定状态分开暴露给 UI。

面试官继续追问什么

  • 为什么不建议在 ViewModel 持有 ActivityView 引用?
  • SavedStateHandle 适合保存什么?
  • ViewModelRepository 的职责边界怎么划?

追问怎么答

  • ViewModel 生命周期往往比页面实例更长,持有 ActivityView 容易导致泄漏和职责混乱。
  • SavedStateHandle 适合保存少量关键恢复信息,比如页签、查询词、对象 ID,不适合塞大对象。
  • ViewModel 负责页面状态和意图处理,Repository 负责数据获取与整合,前者别越界到底层细节,后者别反过来操作 UI 语义。

直接套用句式

"我会把 ViewModel 当成页面状态协调者,而不是业务大仓库。它负责收口页面意图和状态,真正的数据获取和存储细节我会继续放在更下层。"

3. LiveDataStateFlowSharedFlow 怎么在页面架构里分工?

参考答案

一个比较稳的回答是:

  • StateFlow 用来承载稳定页面状态,比如加载中、成功、失败、列表数据、筛选条件。
  • SharedFlow 用来处理一次性事件,比如弹 Toast、跳转页面、关闭弹窗。
  • LiveData 在旧项目或与现有框架兼容时仍然可以用,但在复杂状态流场景下,Flow 体系组合能力更强。

很多项目里的坑都来自"状态和事件不分"。比如把一次性跳转事件放进 StateFlow,页面重建后可能重复消费。

面试官继续追问什么

  • 为什么单次事件会产生"粘性问题"?
  • repeatOnLifecyclelaunchWhenStarted 有什么差别?
  • 页面多个数据源汇总时如何避免状态分裂?

追问怎么答

  • 单次事件会"粘",是因为容器保留了上一次值,新观察者重新订阅时又会收到一次,本来只该执行一次的动作就重复了。
  • repeatOnLifecycle 会在进入目标状态时启动、离开时取消并重建收集,更适合长期流;launchWhenStarted 的边界通常没它清晰。
  • 多数据源时要先定义统一状态出口,让所有结果先归并再渲染,而不是每个源各自改一块 UI。

直接套用句式

"我在页面架构里会刻意把状态和事件分开,因为这两类东西生命周期不一样。稳定状态应该能被重复观察,一次性事件则必须防止重放。"

4. RoomWorkManagerNavigation 分别解决什么问题?

参考答案

  • Room 解决的是本地数据库访问的可维护性问题,让 SQL、实体、迁移、线程约束有更明确的结构。
  • WorkManager 解决的是"需要在系统约束下可靠执行的后台任务",比如延迟任务、重试任务、带网络条件的同步任务。
  • Navigation 解决的是复杂页面跳转、参数传递和回退栈管理问题,尤其适合中大型项目统一导航策略。

回答时别只说"官方推荐"。高级岗位更喜欢听到你知道这些组件分别解决了哪一类复杂度。

面试官继续追问什么

  • 为什么 WorkManager 不适合所有后台任务?
  • 数据库迁移如果失败,线上会有什么风险?
  • 多模块项目里怎么避免 Navigation 图过于集中?

追问怎么答

  • WorkManager 适合可延迟、可重试、受系统约束的任务,不适合必须立刻执行、强实时反馈的交互场景。
  • 数据库迁移失败会直接导致崩溃、数据不可读,严重时还会让线上版本回滚和兼容都很难做。
  • 多模块项目里不要把所有路由都堆在一个大图里,可以按业务域拆图,再在上层聚合。

直接套用句式

"我回答 Jetpack 组件时一般不会说'官方推荐',而是会先说它到底帮我控制了哪类复杂度。比如 WorkManager 控的是受系统约束的后台任务,Room 控的是本地结构化数据和迁移风险。"

5. 依赖注入为什么能提升工程可维护性?

参考答案

依赖注入的核心收益不是"少写 new",而是把对象创建和对象使用分离。这样做的直接好处是:

  • 依赖关系更清晰
  • 测试替换更容易
  • 生命周期更容易统一管理
  • 公共能力更适合下沉为可复用模块

Hilt/Dagger 的价值在于把这些依赖关系显式化、可验证化,减少手写装配带来的错误。

面试官继续追问什么

  • 为什么过度注入也会让代码更难读?
  • 单例、页面级、功能级依赖怎么划生命周期?
  • 你们项目里最大的依赖治理问题是什么?

追问怎么答

  • 过度注入会让对象来源过于隐蔽,读代码时看不出依赖从哪来,理解和排查成本都会上升。
  • 单例适合全局共享、线程安全且代价高的对象;页面级依赖跟页面同生命周期;功能级依赖围绕某个流程或子域存在。
  • 真正的依赖治理难点通常不是有没有框架,而是边界是否清晰、版本是否统一、公共能力是否被滥用。

直接套用句式

"我看依赖注入最大的价值,不是少写几个 new,而是把依赖关系显式化、可替换化。这样页面、模块和测试的边界都会更清楚。"

6. 页面状态复杂时,MVI 或单向数据流为什么更容易控住局面?

参考答案

当页面存在多个数据源、多个用户动作、多个异步结果,以及复杂的加载态和失败态时,最大的风险不是"代码写不出来",而是状态来源太多、修改点太散,最后谁都不敢动。

单向数据流的价值在于把流程收敛成:

  1. 用户产生意图
  2. 逻辑层处理意图
  3. 输出新的统一状态
  4. UI 只根据状态渲染

这样更容易排查、回放和测试。但它也有代价,比如样板代码增多、状态建模要求更高,所以不是所有页面都要强行上完整 MVI

面试官继续追问什么

  • 如何避免一个巨大的 UiState 越来越难维护?
  • 为什么有些页面适合 MVVM,有些更适合 MVI
  • 你如何设计"加载中 + 部分失败 + 局部重试"这种复杂状态?

追问怎么答

  • 巨大的 UiState 要靠子状态拆分和领域分层来控制,不要把所有局部细节都平铺进一个总状态。
  • 交互简单、状态变化少的页面用 MVVM 往往就够;多数据源、多事件、多失败态页面,MVI 更容易收口。
  • 这类复杂状态要先分全局和局部,再给每个区域定义自己的失败和重试语义,避免一个错误把整页状态打乱。

直接套用句式

"我选 MVVM 还是 MVI,不会按喜好来,而是看页面状态复杂度。如果页面已经有多个数据源、多个交互入口和复杂失败态,我更倾向用单向数据流把状态收口。"

7. Compose 和传统 View 的本质区别是什么?

参考答案

传统 View 更偏命令式:你通过更新控件属性、调用方法去改变界面。Compose 更偏声明式:你描述当前状态下 UI 应该长什么样,框架负责在状态变化后重新计算和刷新。

这意味着 Compose 的核心不只是"新写法",而是状态驱动 UI。你如果没有先把状态设计好,到了 Compose 时代问题只会暴露得更明显。

面试官继续追问什么

  • 为什么声明式 UI 更依赖状态建模?
  • Compose 混合旧 View 体系时,最常见的问题是什么?
  • 什么时候不应该急着全量迁移到 Compose

追问怎么答

  • 声明式 UI 是"状态决定界面",状态建模一乱,重组范围和 UI 行为就会一起乱。
  • 混用时最常见的是生命周期、状态同步、滚动和输入焦点等边界没处理好,导致两套体系互相影响。
  • 老项目收益不明显、团队不熟或基础组件没铺平时,不必急着全量迁移,先从高收益局部试点更稳。

直接套用句式

"我理解 Compose 的本质不是新语法,而是声明式状态驱动 UI。所以如果状态建模没做好,迁到 Compose 只是把原来的问题更早暴露出来。"

8. 什么是重组?怎么避免无意义重组?

参考答案

重组可以理解为:当 Compose 观察到某些状态变化后,会重新执行相关组合函数,计算新的 UI 描述。重组本身不是坏事,关键是它是否发生在该发生的范围内。

避免无意义重组,常见做法包括:

  • 状态下沉到最小需要感知的范围
  • 使用稳定的数据结构
  • 不在组合函数里做重逻辑
  • 避免把频繁变化的大对象整体传下去

面试官继续追问什么

  • rememberrememberSaveable 的区别?
  • derivedStateOf 适合什么场景?
  • 为什么有时看起来变的是一个字段,却导致整块 UI 重组?

追问怎么答

  • remember 只在当前组合存活期间记住值;rememberSaveable 还能在配置变更或恢复时保留可保存状态。
  • derivedStateOf 适合从多个输入派生一个高频读取、低频真正变化的结果,减少无意义计算和重组。
  • 因为你可能把整个大对象作为输入往下传了,只要对象整体被认为变了,依赖它的整块 UI 都可能跟着重组。

直接套用句式

"我看重组问题时,重点不在于'有没有重组',而在于'重组范围是不是合理'。因为重组本身是正常机制,真正要避免的是无意义的大范围连带刷新。"

9. LaunchedEffectDisposableEffectSideEffect 怎么区分?

参考答案

  • LaunchedEffect 适合启动与组合生命周期绑定的协程副作用,比如进入页面后拉数据。
  • DisposableEffect 适合需要注册和清理的副作用,比如监听器、生命周期观察者。
  • SideEffect 适合在成功重组后,把当前组合状态同步到外部对象。

面试时的关键点是:副作用 API 的本质,是让"声明式 UI"与"不可避免的命令式世界"安全协作。

面试官继续追问什么

  • 为什么不能在组合函数里直接随便发请求?
  • key 变化会怎么影响副作用重新执行?
  • 页面退出时如何确保资源释放?

追问怎么答

  • 组合函数会被反复执行,直接在里面发请求会让副作用重复触发,行为不可控。
  • key 是副作用的重启条件,key 一变,对应协程或监听通常就会取消并重新建立。
  • 需要注册和释放的资源要放到明确有清理时机的副作用 API 里,比如 DisposableEffect,而不是散落在 UI 代码中。

直接套用句式

"我回答 Compose 副作用时,一般会强调一点:声明式 UI 不等于没有副作用,而是副作用必须放到受控的生命周期里执行。"

10. 高级岗位讲架构,面试官真正要听什么?

参考答案

他真正想听的通常不是你背了多少模式,而是这几个问题:

  • 复杂度是怎么被拆开的?
  • 状态是怎么被收敛的?
  • 依赖是怎么被约束的?
  • 这套方案为什么比你们过去的方式更稳?
  • 代价是什么,哪些地方你刻意没有过度设计?

所以回答架构题时,尽量别停在"我们用了 MVVM + Hilt + Repository"。更好的说法是:

"我们原来页面逻辑散在 FragmentAdapter 和回调里,导致状态不可控。后面把状态集中到 ViewModel,用 StateFlow 表达稳定状态,用事件流表达一次性动作,再通过依赖注入把数据源切换和测试替身成本降下来。收益是页面迭代稳定性更高,代价是前期状态建模更严格。"

这才像高级工程师的回答。

面试里可以这样收口

"所以我讲架构题时,一般不会停在'我们用了什么模式',而是会继续讲:原来哪里失控、为什么要这么改、改完后复杂度是怎么收回来的,以及代价是什么。"

相关推荐

《Android 手把手带你搭建一个组件化项目架构》

《Android 高级工程师面试参考答案:语言基础与并发》

《Android 高级工程师面试参考答案:Framework、生命周期、View 与 Binder》

相关推荐
limingade2 小时前
Dialer3.0智能拨号器Android版功能说明书
android·蓝牙电话·手机转sip·手机蓝牙·智能拨号器
天涯明月19932 小时前
QClaw完全指南_AI代理网关架构与多代理管理实战
人工智能·架构·大模型·agent
陈天伟教授2 小时前
Gemma 4 模型-可变分辨率(令牌预算)
人工智能·安全·架构
鹏程十八少2 小时前
6. 2026金三银四 面试官最爱的 Binder:一次拷贝、Activity 启动流程,这篇全搞定
前端·面试·前端框架
JJay.2 小时前
Android BLE 的 notify 和 indicate 到底有什么区别
android
橙子199110162 小时前
Android 异步任务和消息机制
android
人道领域2 小时前
【LeetCode刷题日记】20.有效的括号
算法·leetcode·职场和发展
zshs0002 小时前
重读《凤凰架构》,从分布式演进史看技术选型的本质
分布式·后端·架构
生活观察站2 小时前
地铁隧道5G工业专网规划:基于室内覆盖架构的Ranplan全场景解决方案
5g·架构