Framework专项聊完,进入Compose专项。Jetpack Compose是Android UI未来方向,腾讯新项目已大量采用。高级岗不光问"会不会写",要问编译原理和性能机制。Compose面试最大坑:用View思维答题。状态管理是重组不是setter,生命周期是Side Effects不是Activity回调。今天8道题覆盖核心机制。
Q1:Compose编译器插件做了什么?为什么需要它?
Compose的@Composable函数不是普通函数——编译器插件把它们转换成支持重组的特殊函数。
编译器做了什么:给每个@Composable函数加一个隐藏的Composer参数,Composer管理Slot Table(存储组合树的状态和结构)。函数体被改写成增量更新逻辑——参数没变就跳过执行(智能重组)。
为什么需要编译器插件:手写增量更新代码太复杂。编译器插件把声明式的@Composable转换成高效的增量执行代码,开发者只写简单函数编译器帮你做优化。
追问:Compose的稳定性推断(Stability Inference)是什么?编译器分析参数类型是否"稳定"(不可变或变化可追踪)。稳定类型的参数没变时跳过重组,不稳定类型每次都重组。所以用data class和immutableList比mutableList性能好——前者是稳定的后者不是。
Q2:重组(Recomposition)机制?什么时候触发?
重组:状态变化时Compose重新执行受影响的@Composable函数更新UI。只有读取了变化状态的函数才重组。
触发条件:读取了MutableState的值且该值发生变化。Compose用equals()比较新旧值——相同不重组。
作用域限制:编译器追踪每个@Composable读取了哪些State,只有读取了变化State的最小作用域才重组。外层不读取就不重组。
追问:怎么避免不必要的重组?1.把State下放到最小的Composable 2.参数用稳定类型(data class/immutableList) 3.用derivedStateOf派生状态避免中间状态触发重组 4.用key()强制区分列表项避免整列表重组。
Q3:Side Effects有哪些?各自场景?
LaunchedEffect:协程作用域执行挂起操作,依赖key变化重新执行。场景:页面加载发请求、防抖搜索。
DisposableEffect:带清理逻辑的副作用,onDispose释放资源。场景:注册/注销监听器。
SideEffect:每次成功重组后执行。场景:状态同步给非Compose代码。
produceState:外部异步数据源转Compose State。场景:Flow/LiveData转Compose状态。
rememberCoroutineScope:获取协程作用域用于用户交互触发的异步操作。跟LaunchedEffect区别是不自动执行,要手动launch。
Q4:remember和rememberSaveable区别?
remember:重组时保留值,首次执行计算初始值后续返回缓存。配置变更(旋转屏幕)或进程死亡会丢失。
rememberSaveable:额外支持配置变更和进程死亡恢复。内部用Bundle保存(跟onSaveInstanceState同一机制)。只能存Bundle支持的类型,自定义类型要实现Saver接口。
追问:ViewModel在Compose里怎么用?viewModel()函数获取实例。ViewModel比remember好——跨配置变更存活、多Composable共享状态、支持SavedStateHandle。
Q5:Navigation Compose怎么工作?跟传统Fragment导航区别?
原理:NavHost是一个@Composable函数,内部维护导航栈。composable("route") { ... }注册路由和对应的Composable。navController.navigate("route")入栈,popBackStack()出栈。
跟Fragment导航区别:没有Fragment事务、没有Fragment生命周期。每个路由就是一个Composable函数,状态用remember管理。参数传递用路由参数("user/{id}"+navArgs)而不是Bundle。
深层链接:支持DeepLink(navController.navigate(uri))和条件导航(登录后返回之前页面用popUpTo+inclusive)。
追问:Compose导航怎么做底部Tab?用NavigationBar+NavHost,每个Tab对应一个嵌套NavGraph。切换Tab用popUpTo(startRoute) { saveState = true }+restoreState = true保留每个Tab的导航状态。
Q6:Compose的性能基准测试怎么做?
Macrobenchmark(androidx.benchmark):测启动时间、帧渲染时间,真机运行多次采样取中位数。
Compose专属指标:重组次数(Layout Inspector查看)、首帧渲染时间(setContent到首帧)、重组耗时(应在1ms内)。
优化方向:减少不必要重组(Stable类型+作用域限制)、避免重组时分配对象(remember缓存)、列表用LazyColumn。
追问:Compose跟View性能对比?复杂UI场景Compose更好(增量更新vs全量measure/layout),简单UI差不多。Compose的编译时优化是View没有的优势。
Q7:自定义Layout怎么做?跟自定义View区别?
Layout Composable:用Layout函数自定义布局。lambda里拿到Measurables列表,对每个调measure拿Placeable,layout(width, height)定容器尺寸,placeable.place(x, y)定位。
跟自定义View区别:Compose测量和布局一次遍历(View两次)。每个子组件必须measure一次且仅一次。
SubcomposeLayout:支持依赖子组件测量结果决定其他子组件测量。缺点是性能开销,非必须不用。
Q8:Compose的Modifier链怎么工作?顺序重要吗?
Modifier是不变对象的链式组合。Modifier.padding(16.dp).background(Color.Red).clickable { }——每次调用返回新Modifier节点链接到链尾。
顺序非常重要:Modifier按声明顺序从外到内应用。padding(16).background(Red)先padding再画背景(背景不含padding区域)。background(Red).padding(16)先画背景再padding(背景覆盖全部区域)。效果完全不同。
自定义Modifier:Modifier.NodeAPI(Compose 1.3+)实现自定义LayoutModifier/DrawModifier/PointerInputModifier。性能比Modifier.composed { }好——composed每次重组创建新节点,Node复用。
面试Tips:Compose面试核心考"理解声明式UI思维"。编译器插件考Slot Table和智能重组、Side Effects考使用场景、重组考触发条件和优化、Navigation考跟Fragment区别。性能优化要能说出Compose专属指标(重组次数/耗时)。最好有实际项目经验——Compose从View迁移踩过的坑比背理论值钱。
下一篇进入网络编程专项,HTTP/2多路复用原理?OkHttp连接池策略?DNS优化怎么做?网络层是中级以上必考方向。
用Compose做过完整项目的同学评论区举手,你觉得Compose最大的坑是什么?
本系列连载中,关注不迷路,下一篇:腾讯中级Android(网络编程专项)面试真题
系列简介:Android大厂面经连载,覆盖字节跳动、腾讯、阿里、美团等40+企业,从初级到架构师全岗位覆盖。每篇文章包含真实面试题+详细答案+代码示例,帮你拿到大厂Offer。