Jetpack Compose经验笔记
Jetpack Compose面试题:
答案来自ChatGPT
1. Jetpack Compose有了解吗?和传统Android UI有什么不同?
Jetpack Compose是一个用于构建Android应用UI的现代工具包,旨在简化和加速UI开发。与传统的Android UI开发方式(基于XML布局文件和View体系)相比,Jetpack Compose具有以下不同点:
- 声明式UI编程:Jetpack Compose采用声明式编程风格,开发者可以直接描述UI的外观和行为,而不是像传统方式那样通过命令式编程逐步构建UI。
- 更少的代码:由于声明式编程和内置的许多功能,Compose通常需要更少的代码来实现相同的UI。
- 可组合性:Compose中的UI组件称为Composable functions,可以轻松地组合和重用。
- 响应式编程:Compose能够自动响应状态变化并更新UI,而传统UI需要手动处理这些更新。
- 更好的可测试性:由于其声明式和响应式的特性,Compose的UI组件更容易进行单元测试和UI测试。
2. DisposableEffect、SideEffect、LaunchedEffect之间的区别?
- DisposableEffect:用于在Composable function的生命周期内执行副作用,并在其离开组合时清理这些副作用。适用于需要清理的副作用,例如注册和注销广播接收器。
- SideEffect:用于在组合完成后执行副作用,且这些副作用不会影响UI的重组。适用于不需要清理的副作用,例如记录日志。
- LaunchedEffect:用于在组合完成后启动协程,以执行异步操作。适用于需要异步执行的副作用,例如网络请求。
3. pointer事件在各个Composable function之间是如何处理的?
在Jetpack Compose中,pointer事件由Modifier.pointerInput
处理。每个Composable function都可以使用这个修饰符来拦截和处理pointer事件。事件处理可以通过awaitPointerEventScope
和其他相关函数来实现,允许开发者自定义事件的处理逻辑和传播方式。
4. 自定义Layout?
自定义Layout可以通过Layout
函数来实现。开发者需要提供一个measurePolicy
,定义子组件的测量和摆放逻辑。以下是一个简单的例子:
@Composable
fun CustomLayout(
modifier: Modifier = Modifier,
content: @Composable () -> Unit
) {
Layout(
modifier = modifier,
content = content
) { measurables, constraints ->
// Measure children
val placeables = measurables.map { measurable ->
measurable.measure(constraints)
}
// Determine the size of the layout
val width = constraints.maxWidth
val height = constraints.maxHeight
// Layout children
layout(width, height) {
placeables.forEach { placeable ->
placeable.placeRelative(0, 0)
}
}
}
}
5. CompositionLocal起什么作用?staticCompositionLocalOf和compositionLocalOf有什么区别?
CompositionLocal
用于在Composable tree中共享数据,而无需显式地将数据作为参数传递。staticCompositionLocalOf
和compositionLocalOf
的区别在于:
staticCompositionLocalOf
:用于定义静态的CompositionLocal,生命周期与整个应用程序的生命周期一致。compositionLocalOf
:用于定义动态的CompositionLocal,生命周期与其所在的Composable tree一致。
6. Composable function的状态是如何持久化的?
Composable function的状态通过remember
和rememberSaveable
来持久化。remember
用于在Composable function重新组合时保持状态,而rememberSaveable
在配置更改(如屏幕旋转)时也能保持状态。
7. LazyColumn是如何做Composable function缓存的?
LazyColumn
通过内部的缓存机制来优化性能。它只会创建和组合当前可见的项,并在项滚动出视图时将其回收。这样可以减少不必要的重组和重绘,提高性能。
8. 如何解决LazyColumn和其他Composable function的滑动冲突?
可以使用Modifier.nestedScroll
来解决LazyColumn
和其他Composable function的滑动冲突。这个修饰符允许多个滑动容器协同工作,处理滑动事件的嵌套。
9. @Composable的作用是什么?
@Composable
注解用于标记一个函数为Composable function,使其能够在Jetpack Compose的UI树中使用。它指示编译器生成必要的代码,以支持声明式UI编程和状态管理。
10. Jetpack Compose是用什么渲染的?执行流程是怎么样的?与flutter/react那样做diff有什么区别/优劣?
Jetpack Compose使用Android的Canvas API进行渲染。其执行流程包括:
- 组合阶段:构建和更新UI树。
- 布局阶段:测量和定位UI组件。
- 绘制阶段:渲染UI组件到屏幕上。
与Flutter和React的diff算法不同,Compose通过组合和重组机制来高效地更新UI。Compose的优势在于与Android平台的紧密集成和更自然的Kotlin语言支持,而Flutter和React则具有跨平台能力。
11. Jetpack Compose多线程执行是如何实现的?
Jetpack Compose通过Kotlin协程来实现多线程执行。LaunchedEffect
和rememberCoroutineScope
等工具允许在Composable function中启动和管理协程,以执行异步任务。
12. 什么是有状态的 Composable 函数?什么是无状态的 Composable 函数?
- 有状态的Composable函数:内部管理自己的状态,并根据状态的变化更新UI。
- 无状态的Composable函数:不管理自己的状态,依赖外部传入的状态来更新UI。
13. Compose 的状态提升如何理解?有什么好处?
状态提升是指将状态从子Composable函数提升到父Composable函数进行管理。好处包括:
- 更好的状态管理:父组件可以集中管理状态,避免状态分散。
- 更好的可重用性:子组件不依赖特定的状态实现,可以在不同的上下文中重用。
14. 如何理解 MVI 架构?和 MVVM、MVP、MVC 有什么不同的?
MVI(Model-View-Intent)是一种单向数据流架构,主要特点是:
- Model:表示应用的状态。
- View:渲染UI并接收用户输入。
- Intent:表示用户意图,触发状态变化。
与其他架构的区别:
- MVVM:通过ViewModel管理状态,View和ViewModel之间通过数据绑定进行通信。
- MVP:通过Presenter协调View和Model之间的交互。
- MVC:通过Controller处理用户输入,并更新Model和View。
15. 在 Android 上,当一个 Flow 被 collectAsState,应用转入后台时,如果这个 Flow 再进行更新,对应的 State 会不会更新?对应的 Composable 函数会不会更新?
当应用转入后台时,如果Flow
继续更新,collectAsState
将继续接收这些更新并更新对应的State。由于Composable函数依赖于这个State,因此它们也会相应地更新。
Jetpack Compose知识普及:
Compose 架构:单向数据流
单向数据流 (UDF) 是一种设计模式,在该模式下状态向下流动,事件向上流动。通过采用单向数据流,您可以将在界面中显示状态的可组合项与应用中存储和更改状态的部分分离开来。
使用单向数据流的应用的界面更新循环如下所示:
- 事件:界面的某一部分生成一个事件,并将其向上传递,例如将按钮点击传递给 ViewModel 进行处理;或者从应用的其他层传递事件,如指示用户会话已过期。
- 更新状态:事件处理脚本可能会更改状态。
- 显示状态:状态容器向下传递状态,界面显示此状态。
单向数据流的状态
-
remember { mutableStateOf(value) }
或rememberSaveable { mutableStateOf(value)
-
借助
ViewModel
和mutableStateOf
或者StateFlow -
多UI状态或者状态类 StateHolder(结合remember和mutableStateOf(状态类不需要这个) 内部存储持有多状态的对象)
class ExampleState {
// 状态类内部状态需要使用mutableStateOf
private var _isShow by mutableStateOf(false)
private var _isExpand by mutableStateOf(false)
private var _isSelected by mutableStateOf(false)
val isShow get() = _isShow
val isExpand get() = _isExpand
val isSelected get() = _isSelected
fun setShowState(isShow: Boolean) {
_isShow = isShow
}
fun setExpandState(isExpand: Boolean) {
_isExpand = isExpand
}
fun setSelectedState(isSelected: Boolean) {
_isSelected = isSelected
}
}
@Composable
fun rememberExampleState(
isShow: Boolean = false,
isExpand: Boolean = false,
isSelected: Boolean = false
) = remember(isShow, isExpand, isSelected) {
val exampleState = ExampleState()
exampleState.setShowState(isShow)
exampleState.setExpandState(isExpand)
exampleState.setSelectedState(isSelected)
exampleState
}
Jetpack Compose独特API
derivedStateOf
派生状态
derivedStateOf
是Jetpack Compose中的一个API,用于创建派生状态。它帮助开发者在状态变化时高效地计算新的状态,而不必在每次重组时重新计算。这对于需要从其他状态派生出新状态的场景特别有用。
derivedStateOf
的作用
-
优化性能:
derivedStateOf
用于避免不必要的计算。只有在其依赖的状态发生变化时,derivedStateOf
的计算才会执行。- 这有助于减少重组过程中重复计算的开销,提高性能。
-
声明式依赖:
derivedStateOf
明确声明了派生状态的依赖关系。这样,当依赖的状态变化时,Compose会自动更新派生状态。- 这使得代码更具可读性和可维护性,因为依赖关系是显式声明的。
-
简化逻辑:
- 使用
derivedStateOf
可以将复杂的状态逻辑简化为声明式的依赖关系,减少了手动管理状态变化的复杂性。
- 使用
使用示例
以下是一个示例,展示了如何使用derivedStateOf
来创建派生状态:
@Composable
fun DerivedStateExample() {
// 基础状态
var input by remember { mutableStateOf("") }
// 派生状态:只有当 input 发生变化时,isValid 才会重新计算
val isValid by remember {
derivedStateOf { input.length > 3 }
}
Column {
TextField(
value = input,
onValueChange = { input = it },
label = { Text("Enter text") }
)
Text(
text = if (isValid) "Valid input" else "Input too short",
color = if (isValid) Color.Green else Color.Red
)
}
}
关键点
- 记忆化计算:
derivedStateOf
会记忆上一次计算的结果,只有在依赖的状态发生变化时才会重新计算。 - 依赖关系:通过
derivedStateOf
,可以清晰地定义派生状态与其基础状态之间的依赖关系。 - 性能优化:通过减少不必要的计算,
derivedStateOf
可以显著优化性能,特别是在复杂的UI中。
总结
derivedStateOf
用于创建依赖于其他状态的派生状态,只有在依赖的状态变化时才会重新计算。- 它帮助优化性能,减少不必要的计算,特别是在状态复杂或变化频繁的场景中。
- 通过声明式的依赖关系,
derivedStateOf
使得代码更具可读性和可维护性。
CompositionLocal
CompositionLocal
是在Jetpack Compose中用于跨组件树传递数据的一种机制。它允许你在不通过显式参数传递的情况下,在Composable层次结构中共享数据。这种机制类似于依赖注入,可以用于提供主题、配置、用户信息或其他全局上下文数据。
CompositionLocal
的特性
-
全局数据共享:
CompositionLocal
允许你定义全局的数据源,可以在Composable的任意层次访问这些数据,而不需要通过参数逐层传递。
-
动态作用域:
CompositionLocal
的值可以在特定的Composable作用域中被覆盖,这使得在特定的UI部分中可以使用不同的配置或数据。
-
类型安全:
CompositionLocal
是类型安全的,每个CompositionLocal
实例都绑定到特定的数据类型。
Jetpack Compose 错误使用
两年 Compose 项目实践总结,常见错误一览
刚学Jetpack Compose?最好不要踩这些新手容易碰到的坑
LaunchedEffect和rememberCoroutineScope()
对于只需要运行一次的副作用,如数据获取或初始化任务,我们应该使用 LaunchedEffect,而不是 rememberCoroutineScope。这可以保证副作用只在必要的时候执行,防止多次执行。
LaunchedEffect 的副作用 API 有助于在组合阶段通过协程调用挂起函数。但是,在某些情况下,我们希望执行一些操作,但不是在组合中立即执行,而是在以后的某个时间点执行,例如当用户在 UI 上执行某些操作时。为此,我们需要一个作用域来启动协程,而 rememberCoroutineScope 提供了一个协程作用域,与调用它的 Composable 的作用域绑定,以便了解 Composable 的生命周期,并在离开组合时取消协程。通过该作用域,我们可以在不在组合内部时调用协程,即可以在用户操作期间在非 Composable 的作用域内启动协程。
配置更改(比如:屏幕旋转)
如果需要保持屏幕旋转后的状态,则应用使用rememberSaveable否则使用remember
当组件被重新创建时,remember 中存储的数据会丢失。remember
用于在可组合函数的重组过程中保持状态,但当组件被重新创建时,例如由于配置更改(如屏幕旋转)或导航到新页面再返回时,remember
中的数据确实会丢失。这是因为remember
的状态仅在当前的组合生命周期内有效。
解决方案:使用rememberSaveable
。为了在组件被重新创建时仍然保持状态,可以使用rememberSaveable
。rememberSaveable
不仅在重组过程中保持状态,还能够在配置更改或进程终止后恢复状态。它利用了Android的SavedInstanceState机制来保存和恢复状态。
及时关闭资源流
对于资源管理,例如打开和关闭连接或订阅,及时使用 DisposableEffect 监听onDispose事件,及时关闭资源。
Jetpack Compose 之 KMP 应用
1. 官方KMP&Compose示例模版
Klibs:klibs.io/
KMP 组件集:github.com/terrakok/km…
KMP模版:github.com/Kotlin/KMP-…
Jetpack Compose官方示例:github.com/android/now…
Jetpack Compose官方文档:developer.android.google.cn/develop/ui/…