当前位置: 首页 > news >正文

Android Jetpack Compose介绍

Android Jetpack Compose

Android Jetpack Compose 是 Google 推出的现代 UI 工具包,用于以声明式的方式构建 Android 应用的 UI。它摒弃了传统的 XML 布局方式,完全基于 Kotlin 编写,提供了更简洁、更强大的 UI 开发体验。以下是 Compose 的使用方式、原理和核心概念的详细解析。

1. Compose 的核心概念

1.1 声明式 UI

  • Compose 采用声明式编程范式,开发者只需描述 UI 应该是什么样子,而不需要关心其具体实现。
  • 与传统的命令式 UI(如 XML + View 系统)不同,Compose 的 UI 是动态的,可以根据状态自动更新。

1.2 Composable 函数

  • Compose 的 UI 由 @Composable 函数定义。这些函数是纯函数,接收输入参数并返回 UI 组件。
  • 例如:
    @Composable
    fun Greeting(name: String) {Text(text = "Hello, $name!")
    }
    

1.3 状态管理

  • Compose 使用 State 来管理 UI 的状态。当状态发生变化时,Compose 会自动重新绘制相关的 UI。
  • 例如:
    @Composable
    fun Counter() {val count = remember { mutableStateOf(0) }Button(onClick = { count.value++ }) {Text("Clicked ${count.value} times")}
    }
    

1.4 重组(Recomposition)

  • 当状态发生变化时,Compose 会触发重组(Recomposition),重新调用相关的 @Composable 函数来更新 UI。
  • Compose 会自动优化重组过程,只更新需要变化的部分。

2. Compose 的基本使用

2.1 设置项目

build.gradle 中添加 Compose 的依赖:

dependencies {implementation "androidx.compose.ui:ui:1.3.3"implementation "androidx.compose.material:material:1.3.3"implementation "androidx.compose.runtime:runtime:1.3.3"implementation "androidx.activity:activity-compose:1.6.1"
}

2.2 创建 Composable 函数

定义一个简单的 UI 组件:

@Composable
fun Greeting(name: String) {Text(text = "Hello, $name!")
}

2.3 在 Activity 中使用 Compose

在 Activity 中设置 Compose 的内容:

class MainActivity : ComponentActivity() {override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContent {Greeting(name = "Compose")}}
}

3. Compose 的高级特性

3.1 布局

Compose 提供了多种布局组件,如 ColumnRowBox 等:

@Composable
fun ProfileCard() {Column {Text("John Doe")Text("Software Engineer")}
}

3.2 主题

可以通过 MaterialTheme 定义应用的主题:

@Composable
fun App() {MaterialTheme {Greeting(name = "Compose")}
}

3.3 动画

Compose 提供了强大的动画支持:

@Composable
fun AnimatedButton() {val enabled = remember { mutableStateOf(true) }Button(onClick = { enabled.value = !enabled.value }) {Text(if (enabled.value) "Enabled" else "Disabled")}
}

3.4 状态提升

将状态提升到父组件中,以实现更灵活的状态管理:

@Composable
fun Parent() {val count = remember { mutableStateOf(0) }Counter(count = count.value, onIncrement = { count.value++ })
}@Composable
fun Counter(count: Int, onIncrement: () -> Unit) {Button(onClick = onIncrement) {Text("Clicked $count times")}
}

4. Compose 的工作原理

4.1 Compose 的架构

  • Compose 基于 Kotlin 的编译器插件,将 @Composable 函数转换为高效的 UI 渲染代码。
  • Compose 使用 Slot TableGap Buffer 技术来管理 UI 的状态和更新。

4.2 重组机制

  • 当状态发生变化时,Compose 会标记受影响的 @Composable 函数,并重新调用它们。
  • Compose 会通过比较前后两次调用的参数,决定是否需要重组。

4.3 状态管理

  • Compose 使用 remembermutableStateOf 来保存和更新状态。
  • 状态的变化会触发重组,从而更新 UI。

4.4 布局和绘制

  • Compose 的布局系统基于 ConstraintLayoutMeasure 机制,支持灵活的 UI 布局。
  • 绘制过程使用 GPU 加速,性能高效。

前置学习

Kotlin remember

remember用于在组件的重新组合(Recomposition)过程中保留状态或计算结果,避免不必要的重复计算或初始化。

remember 的作用

remember 的作用是缓存一个值,并在组件的多次重新组合中保持该值不变,除非它的依赖项发生了变化。它通常用于管理组件的内部状态或缓存昂贵的计算结果。

remember 的语法

remember 的常见用法有两种:

  1. 不带依赖项的 remember

    val value = remember { initialValue }
    
    • 这里的 initialValue 是一个 lambda 表达式,它会在第一次组合时执行,并将结果缓存。在后续的重新组合中,remember 会直接返回缓存的值,而不会重新执行 lambda。
  2. 带依赖项的 remember

    val value = remember(key1, key2, ...) { initialValue }
    
    • 这里的 key1key2 等是依赖项。当这些依赖项发生变化时,remember 会重新执行 lambda 并更新缓存的值;否则,直接返回缓存的值。

remember 的示例

  1. 缓存状态

    @Composable
    fun Counter() {val count = remember { mutableStateOf(0) } // 缓存一个状态Button(onClick = { count.value++ }) {Text("Clicked ${count.value} times")}
    }
    
    • 这里使用 remember 缓存了一个 MutableState,确保在重新组合时不会重新初始化 count
  2. 缓存计算结果

    @Composable
    fun ExpensiveCalculation(input: Int) {val result = remember(input) { // 缓存计算结果,依赖 inputperformExpensiveCalculation(input)}Text("Result: $result")
    }
    
    • 这里使用 remember 缓存了一个昂贵的计算结果,只有当 input 发生变化时才会重新计算。
  3. 缓存对象

    @Composable
    fun MyComponent() {val myObject = remember { MyObject() } // 缓存一个对象Text("Object: $myObject")
    }
    
    • 这里使用 remember 缓存了一个对象,避免在每次重新组合时重新创建。

remember 的注意事项

  1. remember 只能在 @Composable 函数中使用,因为它依赖于 Compose 的重组机制。
  2. remember 缓存的值只在当前组件的生命周期内有效。如果组件被移除或重建,缓存的值会丢失。
  3. remember 不能用于跨组件的状态共享,如果需要跨组件共享状态,应该使用 rememberSaveableViewModel

rememberrememberSaveable

rememberSaveableremember 的增强版,它可以在配置更改(如屏幕旋转)或进程重建时保存状态。例如:

@Composable
fun MyComponent() {val state = rememberSaveable { mutableStateOf(0) }Button(onClick = { state.value++ }) {Text("Clicked ${state.value} times")}
}

Compose添加动画

1. 状态驱动的动画

Compose 的动画是基于状态驱动的。通过监听状态的变化,Compose 会自动在状态之间进行平滑的过渡。

示例:简单的淡入淡出动画
@Composable
fun FadeInOutAnimation() {var visible by remember { mutableStateOf(true) } // 状态控制显示/隐藏val alpha by animateFloatAsState(targetValue = if (visible) 1f else 0f, // 目标透明度animationSpec = tween(durationMillis = 1000) // 动画配置)Column {Button(onClick = { visible = !visible }) {Text("Toggle Visibility")}Box(modifier = Modifier.fillMaxWidth().height(100.dp).background(Color.Blue).alpha(alpha) // 应用透明动画)}
}
  • animateFloatAsState:用于在状态变化时平滑过渡一个 Float 值。
  • tween:定义动画的持续时间和缓动效果。

2. 过渡动画

Transition 用于在多个状态之间进行复杂的动画过渡。

示例:切换大小和颜色的动画
@Composable
fun TransitionAnimation() {var toggled by remember { mutableStateOf(false) }val transition = updateTransition(targetState = toggled, label = "Toggle Transition")val size by transition.animateDp(transitionSpec = { tween(durationMillis = 500) },label = "Size Animation",targetValueByState = { isToggled -> if (isToggled) 200.dp else 100.dp })val color by transition.animateColor(transitionSpec = { tween(durationMillis = 500) },label = "Color Animation",targetValueByState = { isToggled -> if (isToggled) Color.Red else Color.Blue })Column {Button(onClick = { toggled = !toggled }) {Text("Toggle Animation")}Box(modifier = Modifier.size(size).background(color))}
}
  • updateTransition:创建一个过渡动画对象。
  • animateDpanimateColor:分别在状态之间过渡 DpColor 值。

3. 无限循环动画

使用 InfiniteTransition 可以创建无限循环的动画。

示例:无限旋转的圆圈
@Composable
fun InfiniteRotationAnimation() {val infiniteTransition = rememberInfiniteTransition()val rotation by infiniteTransition.animateFloat(initialValue = 0f,targetValue = 360f,animationSpec = infiniteRepeatable(animation = tween(durationMillis = 1000, easing = LinearEasing),repeatMode = RepeatMode.Restart))Box(modifier = Modifier.fillMaxSize().wrapContentSize(Alignment.Center)) {Box(modifier = Modifier.size(100.dp).background(Color.Blue).graphicsLayer {rotationZ = rotation // 应用旋转动画})}
}
  • infiniteRepeatable:定义一个无限循环的动画。

4. 手势驱动的动画

Compose 支持将动画与手势结合,实现更自然的交互效果。

示例:拖动动画
@Composable
fun DraggableBox() {var offsetX by remember { mutableStateOf(0f) }var offsetY by remember { mutableStateOf(0f) }Box(modifier = Modifier.fillMaxSize().pointerInput(Unit) {detectDragGestures { change, dragAmount ->offsetX += dragAmount.xoffsetY += dragAmount.y}}) {Box(modifier = Modifier.offset { IntOffset(offsetX.toInt(), offsetY.toInt()) }.size(100.dp).background(Color.Red))}
}
  • pointerInputdetectDragGestures:用于监听拖动事件。
  • offset:根据拖动距离更新组件的位置。

5. 自定义动画

如果需要更复杂的动画,可以使用 AnimatableAnimationState 进行自定义控制。

示例:使用 Animatable 实现弹簧动画
@Composable
fun SpringAnimation() {val animatable = remember { Animatable(0f, Dp.VectorConverter) }LaunchedEffect(Unit) {animatable.animateTo(targetValue = 200.dp.value,animationSpec = spring(dampingRatio = Spring.DampingRatioLowBouncy,stiffness = Spring.StiffnessLow))}Box(modifier = Modifier.offset { IntOffset(0, animatable.value.toInt()) }.size(100.dp).background(Color.Green))
}
  • Animatable:用于创建自定义动画。
  • spring:定义弹簧动画的特性。

6. 动画的可见性

Compose 提供了 AnimatedVisibility,可以方便地实现组件的显示和隐藏动画。

示例:滑动显示/隐藏动画
@Composable
fun AnimatedVisibilityExample() {var visible by remember { mutableStateOf(true) }Column {Button(onClick = { visible = !visible }) {Text("Toggle Visibility")}AnimatedVisibility(visible = visible,enter = slideInVertically() + fadeIn(),exit = slideOutVertically() + fadeOut()) {Box(modifier = Modifier.fillMaxWidth().height(100.dp).background(Color.Blue))}}
}
  • AnimatedVisibility:根据状态控制组件的显示和隐藏。
  • slideInVerticallyfadeIn:定义进入动画。

Slot Table

Slot Table 是 Jetpack Compose 中用于管理组件状态和结构的关键机制。它是 Compose 运行时(Compose Runtime)的核心部分,负责存储和更新组件的状态、布局信息以及其他元数据。理解 Slot Table 的工作原理有助于更好地掌握 Compose 的内部机制。

1. Slot Table 的作用

Slot Table 是 Compose 用于存储组件树(Composable Tree)的底层数据结构。它的主要功能包括:

  • 存储组件状态:保存 Composable 函数的状态(如 mutableStateOf)。
  • 管理组件结构:记录组件的层次结构和布局信息。
  • 支持重组(Recomposition):在状态变化时,Compose 通过 Slot Table 高效地更新 UI。

2. Slot Table 的结构

Slot Table 可以理解为一个二维表格,其中包含以下部分:

  • Slots:表格中的单元格,用于存储数据(如状态、布局信息等)。
  • Groups:表格中的行,表示一个 Composable 函数或组件。每个 Group 包含多个 Slots。
示例:简单的 Slot Table

假设有以下 Composable 函数:

@Composable
fun MyComponent() {val count = remember { mutableStateOf(0) }Text("Count: ${count.value}")
}

在 Slot Table 中,它可能被表示为:

GroupSlot 1Slot 2
MyComponentcount (state)Text (content)
Text“Count: 0”-
  • MyComponent 是一个 Group,它包含两个 Slots:count(状态)和 Text(子组件)。
  • Text 是另一个 Group,它包含一个 Slot:"Count: 0"(文本内容)。

3. Slot Table 的工作原理

初始组合(Initial Composition)

当 Composable 函数首次执行时,Compose 会:

  1. 创建 Slot Table,并记录组件树的结构和状态。
  2. 将状态和布局信息存储在 Slots 中。
重组(Recomposition)

当状态发生变化时,Compose 会:

  1. 根据 Slot Table 中的信息,判断哪些组件需要更新。
  2. 重新执行受影响的 Composable 函数,并更新 Slot Table 中的 Slots。
  3. 仅更新 UI 中发生变化的部分,避免不必要的重新绘制。
示例:重组过程

假设 count0 变为 1,Compose 会:

  1. 找到 MyComponent 的 Group,并更新 count 的 Slot。
  2. 重新执行 Text Composable,并更新其 Slots。
  3. 最终,UI 中的文本从 "Count: 0" 更新为 "Count: 1"

4. Slot Table 的优势

  • 高效的重组:通过 Slot Table,Compose 可以精确地知道哪些部分需要更新,避免不必要的重新绘制。
  • 状态管理:Slot Table 集中管理所有组件的状态,确保状态的一致性和可预测性。
  • 结构清晰:Slot Table 以树形结构记录组件层次,便于调试和分析。

5. 与 Slot Table 相关的概念

Positional Memoization

Compose 使用位置记忆(Positional Memoization)来跟踪 Composable 函数的执行。每个 Composable 在 Slot Table 中都有一个固定的位置,Compose 通过位置来识别和更新组件。

Gap Buffer

Slot Table 使用间隙缓冲区(Gap Buffer)来高效地插入和删除数据。这种数据结构使得 Composable 的增删操作更加高效。

State

状态(State)是 Slot Table 中存储的重要数据。Compose 通过 mutableStateOf 等 API 将状态存储在 Slot Table 中,并在状态变化时触发重组。


源码分析

1. Compose 的架构

Compose 的源码分为 编译器插件运行时库 两部分:

1.1 Compose 编译器插件

Compose 编译器插件是 Compose 的核心,负责将 @Composable 函数转换为高效的 UI 渲染代码。它的主要功能包括:

  • 代码生成:将 @Composable 函数转换为可执行的 UI 渲染逻辑。
  • 状态管理:自动插入状态管理和重组逻辑。
  • 优化:通过静态分析和优化减少不必要的重组。

1.2 Compose 运行时库

Compose 运行时库提供了 Compose 的核心功能,包括:

  • Slot Table:用于存储 UI 组件的状态和层次结构。
  • Recomposition:管理 UI 的重组逻辑。
  • Layout 和 Drawing:负责 UI 的布局和绘制。

2. Compose 的核心机制

2.1 Slot Table

  • 作用:Slot Table 是 Compose 的核心数据结构,用于存储 UI 组件的状态和层次结构。
  • 实现:它是一个线性表,存储了所有组件的状态和属性。
  • 优化:通过 Gap Buffer 技术高效管理插入、删除和更新操作。

2.2 Recomposition(重组)

  • 触发条件:当状态(如 StateMutableState)发生变化时,Compose 会触发重组。
  • 过程
    1. 标记受影响的 @Composable 函数。
    2. 重新调用这些函数,生成新的 UI 组件树。
    3. 比较新旧组件树,只更新变化的部分。
  • 优化:Compose 通过静态分析和缓存机制减少不必要的重组。

2.3 状态管理

  • remember:用于在重组之间保存状态。
  • mutableStateOf:用于创建可变状态,当状态变化时触发重组。
  • 状态提升:将状态提升到父组件中,以实现更灵活的状态管理。

3. Compose 的关键源码解析

3.1 @Composable 函数

@Composable 函数是 Compose 的基本构建块。编译器插件会将 @Composable 函数转换为以下结构:

@Composable
fun Example() {// 原始代码Text("Hello, Compose!")
}// 编译后
fun Example(composer: Composer, key: Int) {composer.start(key)Text(composer, "Hello, Compose!")composer.end()
}
  • composer:用于管理 UI 组件的状态和层次结构。
  • key:用于标识组件的唯一性。
1. Composer 的作用

Composer 的主要职责包括:

  • 管理 Composable 函数的执行:通过调用 Composable 函数,生成 UI 组件树。
  • 状态管理:存储和更新 Composable 函数中的状态(如 mutableStateOf)。
  • 支持重组(Recomposition):在状态变化时,重新执行受影响的 Composable 函数,并更新 UI。
  • 协调布局和绘制:将生成的组件树传递给布局和绘制系统。
2. Composer 的核心方法

Composer 接口定义了一系列方法,用于管理 Composable 函数的执行和状态存储。以下是关键方法及其作用:

startend
fun start(key: Int, group: Int)
fun end()
  • start:标记一个 Composable 函数的开始,并分配一个唯一的 keygroup
  • end:标记一个 Composable 函数的结束。
  • 这两个方法用于构建组件树的层次结构。
createNode
fun createNode(factory: () -> T)
  • 创建一个 UI 节点(如 LayoutNodeTextNode),并将其插入组件树。
setValue
fun setValue(value: Any?)
  • 将状态值存储到 Slot Table 中。
changed
fun changed(value: Any?): Boolean
  • 检查状态值是否发生变化,以决定是否需要触发重组。
remember
fun <T> remember(value: T): T
  • 将值存储在 Slot Table 中,并在后续执行中返回相同的值(除非依赖项发生变化)。
updateScope
fun updateScope(scope: () -> Unit)
  • 注册一个更新作用域,用于在重组时重新执行 Composable 函数。
3. Composer 的实现

Composer 的核心实现是 ComposerImpl 类,它负责具体的逻辑。以下是 ComposerImpl 的关键机制:

Slot Table
  • ComposerImpl 使用 Slot Table 来存储 Composable 函数的状态和元数据。
  • Slot Table 是一个二维表格,包含 SlotsGroups,分别用于存储数据和描述组件结构。
Gap Buffer
  • Slot Table 使用 Gap Buffer 来高效地插入和删除数据,支持动态组件树的构建。
Positional Memoization
  • ComposerImpl 使用 位置记忆 来跟踪 Composable 函数的执行。每个 Composable 函数在 Slot Table 中都有一个固定的位置,Composer 通过位置来识别和更新组件。
重组机制
  • 当状态发生变化时,ComposerImpl 会重新执行受影响的 Composable 函数,并更新 Slot Table 和 UI。
4. Composable 函数的执行流程

以下是 Composable 函数在 Composer 中的执行流程:

  1. 开始 Composable 函数
    • 调用 start 方法,分配 keygroup,标记 Composable 函数的开始。
  2. 创建 UI 节点
    • 调用 createNode 方法,生成 UI 节点并插入组件树。
  3. 存储状态
    • 调用 setValue 方法,将状态值存储到 Slot Table 中。
  4. 检查状态变化
    • 调用 changed 方法,判断是否需要触发重组。
  5. 结束 Composable 函数
    • 调用 end 方法,标记 Composable 函数的结束。
  6. 触发重组
    • 如果状态发生变化,重新执行受影响的 Composable 函数,并更新 UI。
5. 源码分析示例

以下是一个简单的 Composable 函数及其在 Composer 中的执行过程:

Composable 函数
@Composable
fun MyComponent() {val count = remember { mutableStateOf(0) }Text("Count: ${count.value}")
}
执行流程
  1. 调用 start,标记 MyComponent 的开始。
  2. 调用 remember,将 count 存储在 Slot Table 中。
  3. 调用 start,标记 Text 的开始。
  4. 调用 createNode,创建 TextNode 并插入组件树。
  5. 调用 end,标记 Text 的结束。
  6. 调用 end,标记 MyComponent 的结束。

3.2 Slot Table

Slot Table 是 Compose 的核心数据结构,源码位于 androidx.compose.runtime 包中。它的主要实现包括:

  • SlotTable.kt:定义 Slot Table 的结构和操作。
  • GapBuffer.kt:实现 Gap Buffer 技术,用于高效管理插入和删除操作。

SlotTableJetpack Compose 运行时的核心数据结构,用于存储 Composable 函数的状态、组件结构和其他元数据。它是 Compose 实现高效重组(Recomposition)和状态管理的关键组件。通过分析 SlotTable 的源码,可以深入理解 Compose 的内部工作机制。

1. SlotTable 的作用

SlotTable 的主要职责包括:

  • 存储 Composable 函数的状态:如 mutableStateOf 的值。
  • 记录组件树的结构:描述 Composable 函数的层次关系。
  • 支持高效的重组:在状态变化时,快速定位和更新受影响的组件。
  • 管理动态数据:支持插入、删除和更新操作。
2. SlotTable 的结构

SlotTable 是一个二维表格,包含以下部分:

  • Slots:表格中的单元格,用于存储数据(如状态、布局信息等)。
  • Groups:表格中的行,表示一个 Composable 函数或组件。每个 Group 包含多个 Slots。
数据结构

SlotTable 的核心字段包括:

class SlotTable {private val slots: IntArray // 存储 Slot 数据private val groups: IntArray // 存储 Group 元数据private var groupsSize: Int // Group 的数量private var slotsSize: Int // Slot 的数量
}
  • Slotsslots 是一个 IntArray,用于存储具体的数据(如状态值、文本内容等)。
  • Groupsgroups 是一个 IntArray,用于存储 Group 的元数据(如类型、父 Group、子 Group 等)。
3. Group 的表示

每个 Group 在 groups 数组中占用了多个连续的槽位,用于存储以下信息:

  • Group 类型:表示 Group 的种类(如 Composable 函数、布局节点等)。
  • 父 Group:指向父 Group 的索引。
  • 子 Group:指向子 Group 的索引。
  • 数据范围:指向 slots 数组中与该 Group 相关的数据。
Group 的布局

groups 数组中,一个 Group 的布局如下:

索引字段描述
0KeyGroup 的唯一标识符
1Parent父 Group 的索引
2FirstChild第一个子 Group 的索引
3NextSibling下一个兄弟 Group 的索引
4DataStart数据在 slots 中的起始索引
5DataEnd数据在 slots 中的结束索引
4. SlotTable 的核心操作

SlotTable 提供了一系列方法,用于管理数据和组件结构。

插入 Group
fun insertGroup(key: Int, parent: Int, dataStart: Int, dataEnd: Int): Int
  • 插入一个新的 Group,并返回其索引。
  • 更新 groupsslots 数组,确保数据的一致性。
删除 Group
fun removeGroup(group: Int)
  • 删除指定的 Group。
  • 更新 groupsslots 数组,回收资源。
更新 Slot 数据
fun setSlot(index: Int, value: Any?)
  • 更新 slots 数组中指定索引的值。
  • 如果值发生变化,标记 Group 为需要重组。
查找 Group
fun findGroup(key: Int, parent: Int): Int
  • 根据 keyparent 查找 Group。
  • 如果找到,返回 Group 的索引;否则返回 -1
5. SlotTable 的重组机制

SlotTable 通过以下机制支持高效的重组:

  • 位置记忆(Positional Memoization)
    • 每个 Composable 函数在 Slot Table 中都有一个固定的位置。
    • 在重组时,Compose 根据位置快速定位和更新组件。
  • Gap Buffer
    • SlotTable 使用 Gap Buffer 来高效地插入和删除数据。
    • Gap Buffer 将数组分为两部分,中间留出一个“间隙”,使得插入和删除操作的时间复杂度为 O(1)。
  • 状态变化检测
    • 当状态发生变化时,SlotTable 会标记受影响的 Group 为“脏数据”,并在重组时重新执行相应的 Composable 函数。
6. 源码分析示例

以下是一个简单的 Composable 函数及其在 SlotTable 中的表示:

Composable 函数
@Composable
fun MyComponent() {val count = remember { mutableStateOf(0) }Text("Count: ${count.value}")
}
SlotTable 表示
GroupSlots
MyComponentcount (state)
Text“Count: 0”

groupsslots 数组中的具体表示:

  • groups 数组
    • MyComponent[key=1, parent=-1, firstChild=2, nextSibling=-1, dataStart=0, dataEnd=1]
    • Text[key=2, parent=1, firstChild=-1, nextSibling=-1, dataStart=1, dataEnd=2]
  • slots 数组
    • [count, "Count: 0"]

3.3 Recomposition

Compose 的重组逻辑位于 androidx.compose.runtime 包中,主要实现包括:

  • Recomposer.kt:管理重组过程,标记和调度受影响的组件。
  • Composer.kt:负责组件的创建、更新和销毁。
1. Recomposer 的作用

Recomposer 的主要职责包括:

  • 调度 Composable 函数的执行:协调 Composable 函数的初始执行和重组。
  • 管理状态变化:监听状态的变化,并触发受影响的 Composable 函数进行重组。
  • 协调帧更新:确保 UI 更新与 Android 的帧率(如 60Hz)同步。
  • 支持并发重组:在多个线程中高效地执行 Composable 函数。
2. Recomposer 的核心机制

Recomposer 的实现依赖于以下核心机制:

状态快照系统(State Snapshot System)
  • Compose 使用状态快照来跟踪状态的变化。
  • 当状态发生变化时,Recomposer 会查找所有依赖该状态的 Composable 函数,并触发它们重组。
Slot Table
  • Recomposer 使用 Slot Table 来存储 Composable 函数的状态和组件结构。
  • Slot Table 是 Compose 实现高效重组的基础。
帧调度
  • Recomposer 与 Android 的 Choreographer 集成,确保 UI 更新与屏幕刷新率同步。
  • 它会在每一帧开始前执行待处理的重组任务。
并发重组
  • Recomposer 支持在多个线程中执行 Composable 函数,以提高性能。
  • 它使用线程池来管理并发任务。
3. Recomposer 的源码分析

以下是 Recomposer 的核心源码片段及其功能解析:

初始化

Recomposer 在初始化时会创建一个线程池,并注册到 Android 的 Choreographer。

class Recomposer {private val choreographer = Choreographer.getInstance()private val executor = Executors.newSingleThreadExecutor()init {choreographer.postFrameCallback(frameCallback)}
}
帧回调

Recomposer 使用 Choreographer.FrameCallback 来协调帧更新。

private val frameCallback = object : Choreographer.FrameCallback {override fun doFrame(frameTimeNanos: Long) {// 执行待处理的重组任务executePendingRecompositions()// 注册下一帧的回调choreographer.postFrameCallback(this)}
}
状态监听

Recomposer 监听状态的变化,并标记受影响的 Composable 函数为“脏数据”。

fun onStateChanged(state: State<*>) {// 查找依赖该状态的 Composable 函数val affected = findAffectedComposables(state)// 标记为需要重组markDirty(affected)
}
重组调度

Recomposer 使用线程池执行重组任务。

private fun executePendingRecompositions() {executor.execute {// 获取待处理的重组任务val tasks = getPendingRecompositionTasks()// 执行任务tasks.forEach { it.run() }}
}
并发重组

Recomposer 支持在多个线程中执行 Composable 函数。

fun recompose(composable: @Composable () -> Unit) {executor.execute {// 在后台线程中执行 Composable 函数composable()}
}
4. Recomposer 的工作流程
  1. 初始化
    • 创建线程池,并注册到 Choreographer。
  2. 监听状态变化
    • 当状态发生变化时,标记受影响的 Composable 函数为“脏数据”。
  3. 调度重组
    • 在下一帧开始前,执行待处理的重组任务。
  4. 执行 Composable 函数
    • 在后台线程中执行 Composable 函数,并更新 Slot Table。
  5. 帧更新
    • 将更新后的 UI 提交给渲染系统。

3.4 Layout 和 Drawing

Compose 的布局和绘制逻辑位于 androidx.compose.ui 包中,主要实现包括:

  • Layout.kt:定义布局组件,如 ColumnRow 等。
  • Draw.kt:实现绘制逻辑,支持自定义绘制。
1. Layout 的作用

Layout 的主要功能包括:

  • 自定义布局:允许开发者完全控制子组件的测量(Measure)和布局(Placement)过程。
  • 支持复杂的布局逻辑:例如自定义排列、拖拽布局、流式布局等。
  • 与 Compose 布局系统集成:与 MeasurePolicyLayoutModifier 等组件无缝协作。
2. Layout 的基本用法

Layout 的典型用法如下:

@Composable
fun MyCustomLayout(modifier: Modifier = Modifier,content: @Composable () -> Unit
) {Layout(modifier = modifier,content = content) { measurables, constraints ->// 测量子组件val placeables = measurables.map { measurable ->measurable.measure(constraints)}// 计算布局尺寸val layoutWidth = placeables.maxOf { it.width }val layoutHeight = placeables.maxOf { it.height }// 布局子组件layout(layoutWidth, layoutHeight) {placeables.forEach { placeable ->placeable.placeRelative(x = 0, y = 0) // 自定义位置}}}
}
3. Layout 的源码分析

Layout 是 Compose 中的一个 Composable 函数,其源码位于 androidx.compose.ui.Layout.kt 中。以下是其核心实现:

Layout 函数签名
@Composable
fun Layout(modifier: Modifier = Modifier,content: @Composable () -> Unit,measurePolicy: MeasurePolicy
)
  • modifier:用于调整布局的行为和外观。
  • content:子组件的 Composable 内容。
  • measurePolicy:定义测量和布局的逻辑。
MeasurePolicy

MeasurePolicy 是一个接口,定义了如何测量和布局子组件。它的核心方法包括:

interface MeasurePolicy {fun MeasureScope.measure(measurables: List<Measurable>,constraints: Constraints): MeasureResultfun IntrinsicMeasureScope.minIntrinsicWidth(measurables: List<IntrinsicMeasurable>,height: Int): Intfun IntrinsicMeasureScope.minIntrinsicHeight(measurables: List<IntrinsicMeasurable>,width: Int): Intfun IntrinsicMeasureScope.maxIntrinsicWidth(measurables: List<IntrinsicMeasurable>,height: Int): Intfun IntrinsicMeasureScope.maxIntrinsicHeight(measurables: List<IntrinsicMeasurable>,width: Int): Int
}

最重要的方法是 measure,它负责测量子组件并返回布局结果。

Layout 的实现

Layout 的实现如下:

@Composable
fun Layout(modifier: Modifier = Modifier,content: @Composable () -> Unit,measurePolicy: MeasurePolicy
) {val density = LocalDensity.currentval layoutDirection = LocalLayoutDirection.currentval viewConfiguration = LocalViewConfiguration.currentReusableComposeNode<ComposeUiNode, Applier<Any>>(factory = ComposeUiNode.Constructor,update = {set(measurePolicy, ComposeUiNode.SetMeasurePolicy)set(modifier, ComposeUiNode.SetModifier)},content = content)
}
  • ReusableComposeNode:创建一个可重用的布局节点。
  • measurePolicy:传递测量和布局的逻辑。
  • modifier:传递布局的修饰符。
4. Layout 的工作流程

Layout 的工作流程如下:

  1. 测量子组件
    • 调用 measurePolicy.measure 方法,为每个子组件分配空间。
  2. 计算布局尺寸
    • 根据子组件的测量结果,确定布局的总尺寸。
  3. 布局子组件
    • 调用 layout 方法,将子组件放置在指定的位置。
5. 自定义布局的示例

以下是一个自定义布局的示例,将子组件垂直排列:

@Composable
fun VerticalLayout(modifier: Modifier = Modifier,content: @Composable () -> Unit
) {Layout(modifier = modifier,content = content) { measurables, constraints ->// 测量子组件val placeables = measurables.map { measurable ->measurable.measure(constraints)}// 计算布局尺寸val layoutWidth = placeables.maxOf { it.width }val layoutHeight = placeables.sumOf { it.height }// 布局子组件layout(layoutWidth, layoutHeight) {var y = 0placeables.forEach { placeable ->placeable.placeRelative(x = 0, y = y)y += placeable.height}}}
}
1. Draw 模块的核心类
1.1 Canvas
  • 作用:Canvas 是 Compose 中的绘制画布,封装了 Android 的 Canvas 类。
  • 源码位置:androidx.compose.ui.graphics.Canvas
  • 关键方法:
    • drawRect:绘制矩形。
    • drawCircle:绘制圆形。
    • drawPath:绘制路径。
    • drawImage:绘制图片。
1.2 Paint
  • 作用:Paint 是 Compose 中的绘制工具,封装了 Android 的 Paint 类。
  • 源码位置:androidx.compose.ui.graphics.Paint
  • 关键属性:
    • color:设置绘制颜色。
    • style:设置绘制样式(填充或描边)。
    • strokeWidth:设置描边宽度。
1.3 DrawScope
  • 作用:DrawScope 是 Compose 中用于定义绘制作用域的接口,提供了绘制的上下文信息(如尺寸、密度等)。
  • 源码位置:androidx.compose.ui.graphics.drawscope.DrawScope
  • 关键属性:
    • size:当前绘制区域的大小。
    • density:当前设备的屏幕密度。
  • 关键方法:
    • drawRectdrawCircledrawPath 等,与 Canvas 的方法类似。
1.4 DrawModifier
  • 作用:DrawModifier 是一个 Modifier,用于在 UI 组件上添加自定义绘制逻辑。
  • 源码位置:androidx.compose.ui.draw.DrawModifier
  • 关键实现:DrawModifier 通过 draw 方法将自定义绘制逻辑应用到组件上。
2. Draw 模块的工作原理
2.1 绘制流程
  1. 创建 Canvas:Compose 在渲染 UI 时,会为每个组件创建一个 Canvas
  2. 调用 DrawScope:通过 DrawScope 提供绘制上下文(如尺寸、密度等)。
  3. 执行绘制逻辑:在 DrawScope 中调用 drawRectdrawCircle 等方法完成绘制。
  4. 应用 DrawModifier:如果组件使用了 DrawModifier,会调用其 draw 方法应用自定义绘制逻辑。
2.2 重组与绘制
  • 当组件的状态发生变化时,Compose 会触发重组。
  • 在重组过程中,DrawScope 会重新调用绘制逻辑,更新 UI。
3. Draw 模块的关键源码解析
3.1 Canvas 的实现

Canvas 是对 Android Canvas 的封装,源码位于 androidx.compose.ui.graphics.Canvas。以下是关键方法:

fun drawRect(rect: Rect, paint: Paint) {nativeCanvas.drawRect(rect.toAndroidRect(), paint.asFrameworkPaint())
}
  • nativeCanvas:底层的 Android Canvas
  • paint.asFrameworkPaint():将 Compose 的 Paint 转换为 Android 的 Paint
3.2 DrawScope 的实现

DrawScope 是绘制作用域的接口,源码位于 androidx.compose.ui.graphics.drawscope.DrawScope。以下是关键方法:

fun drawCircle(color: Color,radius: Float,center: Offset,style: DrawStyle = Fill,alpha: Float = 1.0f,colorFilter: ColorFilter? = null,blendMode: BlendMode = DefaultBlendMode
) {// 调用 Canvas 的绘制方法canvas.drawCircle(center, radius, paint)
}
  • canvas:当前的 Canvas
  • paint:当前的 Paint
3.3 DrawModifier 的实现

DrawModifier 是用于添加自定义绘制逻辑的 Modifier,源码位于 androidx.compose.ui.draw.DrawModifier。以下是关键实现:

class DrawModifier(private val onDraw: DrawScope.() -> Unit
) : Modifier.Element, DrawModifier {override fun ContentDrawScope.draw() {onDraw()}
}
  • onDraw:自定义绘制逻辑。
  • ContentDrawScope:继承自 DrawScope,提供了绘制上下文。
4. Draw 模块的使用示例
4.1 自定义绘制

以下是一个简单的自定义绘制示例:

@Composable
fun CustomDrawComponent() {Canvas(modifier = Modifier.fillMaxSize()) {drawRect(color = Color.Red, topLeft = Offset(100f, 100f), size = Size(200f, 200f))drawCircle(color = Color.Blue, radius = 100f, center = Offset(300f, 300f))}
}
4.2 使用 DrawModifier

以下是一个使用 DrawModifier 的示例:

@Composable
fun CustomDrawModifier() {Box(modifier = Modifier.size(200.dp).drawWithContent {drawRect(color = Color.Red, size = size)drawContent()}) {Text("Hello, Compose!")}
}
5. Draw 模块的优化
5.1 硬件加速

Compose 的绘制过程默认使用硬件加速,性能高效。

5.2 最小化绘制区域

Compose 会自动优化绘制区域,只绘制需要更新的部分。

5.3 缓存绘制结果

对于复杂的绘制逻辑,可以使用 RenderNodeBitmap 缓存绘制结果,减少重复计算。

4. Compose 的优化技术

4.1 静态分析

Compose 编译器插件通过静态分析优化代码,减少不必要的重组。例如:

  • stableunstable 参数:标记参数的稳定性,避免不必要的重组。
  • rememberkey:通过缓存和唯一标识优化重组。

4.2 增量更新

Compose 通过比较新旧组件树,只更新变化的部分。例如:

  • DiffUtil:比较组件树的变化,生成最小更新集。
  • LazyColumnLazyRow:通过懒加载优化列表性能。

4.3 GPU 加速

Compose 的绘制过程使用 GPU 加速,提升渲染性能。例如:

  • Canvas:支持自定义绘制,性能高效。
  • Modifier:通过链式调用优化 UI 组件的属性设置。

写在后面

总的看来,很像ArkTs的更新方式,有一种莫名的熟悉感哈哈~

If you like this article, it is written by Johnny Deng.
If not, I don’t know who wrote it.

相关文章:

Android Jetpack Compose介绍

Android Jetpack Compose Android Jetpack Compose 是 Google 推出的现代 UI 工具包&#xff0c;用于以声明式的方式构建 Android 应用的 UI。它摒弃了传统的 XML 布局方式&#xff0c;完全基于 Kotlin 编写&#xff0c;提供了更简洁、更强大的 UI 开发体验。以下是 Compose 的…...

文档搜索引擎项目测试

目录 设计测试用例 功能测试 文档搜索功能 具体的逻辑 文档显示搜索结果功能 自动化测试 功能类介绍 最终测试套件的测试结果 性能测试 查看聚合报告 每秒处理事务数/吞吐量(TPS) 响应时间 生成测试报告 界面测试 初始页面 搜索页面 总结 后续改进: 设计测试…...

Vue3项目技术点记录

vue3项目技术点笔记 1 环境变量 - 不同环境用不同的配置 环境变量命名:.env.produciton .env.development Vite.config.ts 配置 envDir:‘’ 链接: VUE3+Vite 环境变量配置 2 axios的封装 http请求拦截,响应拦截。 src下建立公共文件夹Utils下建立request.ts import axios…...

一和零 (leetcode 474

leetcode系列 文章目录 一、核心操作二、外层配合操作三、核心模式代码总结 本题是一个01背包问题&#xff0c;只是背包是一个二维数组的背包&#xff0c;分别为0的个数不能超过m&#xff0c;1的个数不能超过n&#xff0c;而物品就是题目中的字符串&#xff0c;其容量为0和1的…...

Linux vim mode | raw / cooked

注&#xff1a;机翻&#xff0c;未校。 vim terminal “raw” mode Vim 终端 “raw” 模式 1. 原始模式与已处理模式的区别 We know vim puts the terminal in “raw” mode where it receives keystrokes as they are typed, opposed to “cooked” mode where the command…...

利用Linux的I2C子系统和i2c-tools工具集写出的对I2C设备AP3216C读写的应用程序

前言 由于NXP官方提供的BSP里已经包含了其片上I2C控制器的驱动并接入到了Linux的I2C子系统&#xff0c;所以我们可以直接去写与I2C有关的应用程序了。 在本篇博文中我们用两种方式对I2C设备AP3216C进行读写操作。 第一种&#xff1a;直接利用Linux的I2C子系统对I2C设备AP3216…...

springCloud集成tdengine(原生和mapper方式) 其二 原生篇

mapper篇请看另一篇文章 一、引入pom文件 <!-- TDengine 连接器--><dependency><groupId>com.taosdata.jdbc</groupId><artifactId>taos-jdbcdriver</artifactId><version>3.5.3</version></dependency>二、在nacos中填…...

gralloc usage flags

下面这些示例主要说明了 gralloc usage flags 在图像处理和多媒体应用中如何影响性能和正确性。让我们逐个详细分析每个问题的 根因 和 修复方案&#xff0c;并深入解析 gralloc 标志对 缓存管理 和 数据流 的影响。 ✅ Example 1: 长曝光快照耗时异常 &#x1f4cc; 问题描述…...

RIP路由欺骗攻击与防御实验详解

一、基础网络配置 1. 路由器R1配置 interface GigabitEthernet0/0/0ip address 192.1.2.254 255.255.255.0 ! interface GigabitEthernet0/0/1ip address 192.1.3.254 255.255.255.0 ! router rip 1version 2network 192.1.2.0network 192.1.3.0 2. 路由器R2配置 interface…...

在 Linux下使用 Python 3.11 和 FastAPI 搭建带免费证书的 HTTPS 服务器

在当今数字化时代&#xff0c;保障网站数据传输的安全性至关重要。HTTPS 协议通过使用 SSL/TLS 加密技术&#xff0c;能够有效防止数据在传输过程中被窃取或篡改。本教程将详细介绍如何在 Ubuntu 22.04 系统上&#xff0c;使用 Python 3.11 和 FastAPI 框架搭建一个带有免费 SS…...

[QMT量化交易小白入门]-三十五、如何将聚宽策略信号转为QMT实盘交易

本专栏主要是介绍QMT的基础用法,常见函数,写策略的方法,也会分享一些量化交易的思路,大概会写100篇左右。 QMT的相关资料较少,在使用过程中不断的摸索,遇到了一些问题,记录下来和大家一起沟通,共同进步。 文章目录 相关阅读一、聚宽模拟运行:构建交易策略的基础在聚宽…...

国思RDIF低代码快速开发框架 v6.2版本发布

1、平台介绍 国思RDIF企业级低代码开发平台&#xff0c;给用户和开发者最佳的框架平台方案&#xff0c;为企业快速构建跨平台、企业级的应用提供强大支持。致力于解决企业信息化项目交付难、实施效率低、开发成本高的问题。能帮助企业快速构建美观易用、架构专业、安全可控的企…...

学习笔记 ASP.NET Core Web API 8.0部署到iis

一.修改配置文件 修改Program.cs配置文件将 if (app.Environment.IsDevelopment()) {app.UseSwagger();app.UseSwaggerUI(); }修改为 app.UseSwagger(); app.UseSwaggerUI(); 二.安装ASP.NET Core Runtime 8.0.14 文件位置https://dotnet.microsoft.com/en-us/download/do…...

【Linux】信号:产生信号

&#x1f525;个人主页&#xff1a;Quitecoder &#x1f525;专栏&#xff1a;linux笔记仓 目录 01.信号信号处理简单理解信号的发送和保存由软件产生的信号由硬件&#xff08;异常&#xff09;产生的信号 01.信号 进程信号是操作系统提供的一种异步通知机制&#xff0c;用于…...

单片机自学总结

自从工作以来&#xff0c;一直努力耕耘单片机&#xff0c;至今&#xff0c;颇有收获。从51单片机&#xff0c;PIC单片机&#xff0c;直到STM32&#xff0c;以及RTOS和Linux&#xff0c;几乎天天在搞:51单片机&#xff0c;STM8S207单片机&#xff0c;PY32F003单片机&#xff0c;…...

Idea集成docker通过ca加密实现镜像打包

​ Idea集成docker实现镜像打包_ideadocker镜像打包-CSDN博客 ​ 之前通过这种方式虽然可以实现idea通过maven打jar包的同时把docker镜像也进行打包&#xff0c;但是这种方式存在很大漏洞&#xff0c;就是服务器的2375端口大开&#xff0c;任何人拿着idea通过这种方式都可以连…...

【Prometheus】prometheus标签替换label_replace,动态修改生成标签,增强查询的灵活性和表达能力

✨✨ 欢迎大家来到景天科技苑✨✨ 🎈🎈 养成好习惯,先赞后看哦~🎈🎈 🏆 作者简介:景天科技苑 🏆《头衔》:大厂架构师,华为云开发者社区专家博主,阿里云开发者社区专家博主,CSDN全栈领域优质创作者,掘金优秀博主,51CTO博客专家等。 🏆《博客》:Python全…...

蓝桥杯 之 暴力回溯

文章目录 数字接龙小u的最大连续移动次数问题 在蓝桥杯中&#xff0c;十分喜欢考察对于网格的回溯的问题&#xff0c;对于这类的问题&#xff0c;常常会使用到这个DFS和BFS进行考察&#xff0c;不过无论怎么考察&#xff0c;都只是会在最基础的模本的基础上&#xff0c;根据这个…...

在K8S中挂载 Secret 到 Pod

在 Kubernetes 里&#xff0c;把 Secret 挂载到 Pod 中有两种主要方式&#xff1a;作为卷挂载和作为环境变量挂载。下面为你提供相应的代码示例。 作为卷挂载 Secret 将 Secret 作为卷挂载到 Pod 时&#xff0c;Secret 的每个键会成为挂载目录下的一个文件&#xff0c;文件内…...

LINUX网络编程API原型详细解析

1. 网络体系 1.1. 简介 网络采用分而治之的方法设计&#xff0c;将网络的功能划分为不同的模块&#xff0c;以分层的形式有机组合在一起。 每层实现不同的功能&#xff0c;其内部实现方法对外部其他层次来说是透明的。每层向上层提供服务&#xff0c;同时使用下层提供…...

Android 13 Launcher3最近任务列表“全部清除“按钮位置优化实战

一、问题背景与实现难点 在Android 13横屏设备开发中&#xff0c;系统默认将最近任务列表的"全部清除"按钮布局在屏幕左侧&#xff0c;这与用户习惯的底部布局存在明显差异。相较于Android 8.1时代SystemUI模块的实现&#xff0c;Android 13将相关逻辑迁移至Launche…...

docker最新源,及遇到问题+处理

目前国内可用Docker镜像源汇总&#xff08;截至2025年3月&#xff09; - CoderJia 遇到问题&#xff1a; Error response from daemon: Get "https://registry-1.docker.io/v2/": dial tcp: lookup registry-1.docker.io on [::1]:53: read udp [::1]:13287->[:…...

Python简单爬虫实践案例

学习目标 能够知道Web开发流程 能够掌握FastAPI实现访问多个指定网页 知道通过requests模块爬取图片 知道通过requests模块爬取GDP数据 能够用pyecharts实现饼图 能够知道logging日志的使用 一、基于FastAPI之Web站点开发 1、基于FastAPI搭建Web服务器 # 导入FastAPI模…...

统信UOS中使用Vscode编程

写在前面&#xff1a;统信UOS其实就是套壳的Linux系统&#xff0c;所以有问题如果搜不到解决方法&#xff0c;可以参考Linux下的解决方法。 1.环境配置 Vscode : 1.85.0 Vscode就直接下载安装就行&#xff0c;然后安装插件&#xff1a;Volar、中文汉化包 node&#xff1a;18…...

C#从入门到精通(1)

目录 第一章 C#与VS介绍 第二章 第一个C#程序 &#xff08;1&#xff09;C#程序基本组成 1.命名空间 2.类 3.Main方法 4.注释 5.语句 6.标识符及关键字 &#xff08;2&#xff09;程序编写规范 1.代码编写规则 2.程序命名方法 3.元素命名规范 第三章 变量 &…...

openharmony中hilog实证记录说明(3.1和5.0版本)

每次用这个工具hilog都有一些小用法记不清&#xff0c;需要花一些时间去查去分析使用方法&#xff0c;为了给丰富多彩的生活留出更多的时间&#xff0c;所以汇总整理共享来了&#xff0c;它来了它来了~~~~~~~~~ 开始是想通过3.1来汇总的&#xff0c;但实际测试发现openharmony…...

飞书 设计智能字段:通过“字段类型”添加AI功能列

在飞书多维表格中&#xff0c;通过“字段类型”添加AI功能列的核心逻辑是将AI模型能力与结构化数据结合&#xff0c;实现自动化内容生成与信息处理。以下是具体操作步骤及关键要点&#xff0c;结合实际应用场景说明&#xff1a; 一、基础操作步骤 创建多维表格 登录飞书&#x…...

Cannot find module @rollup/rollup-win32-x64-msvc

方法1 在package.json中添加postinstall: "scripts": {"postinstall": "node -e \"const { platform } process; if (platform win32) { require(child_process).execSync(npm install rollup/rollup-win32-x64-msvc, { stdio: inherit });…...

Docker和Dify学习笔记

文章目录 1 docker学习1.1 基本命令使用1.1.1 docker ps查看当前正在运行的镜像1.1.2 docker stop停止容器1.1.3 docker compose容器编排1.1.4 docker网络[1] 进入到容器里面敲命令[2] docker network ls[3] brige网络模式下容器访问宿主机的方式 2 Dify的安装和基础使用2.1 下…...

【AIGC】Win10系统极速部署Docker+Ragflow+Dify

【AIGC】WIN10仅3步部署DockerRagflowDify 一、 Docker快速部署1.F2进入bios界面&#xff0c;按F7设置开启VMX虚拟化技术。保存并退出。2.打开控制面板配置开启服务3.到官网下载docker安装包&#xff0c;一键安装&#xff08;全部默认勾选&#xff09; 二、 RagFlow快速部署1.确…...

Oracle ASM 磁盘组冗余策略

Oracle ASM 磁盘组冗余策略 1. 外部冗余&#xff08;External Redundancy&#xff09;2. 普通冗余&#xff08;Normal Redundancy&#xff09;3. 高冗余&#xff08;High Redundancy&#xff09;关键注意事项如何选择合适的策略&#xff1f; Oracle ASM&#xff08;Automatic S…...

C++ 数据结构

C++ 数据结构 概述 C++作为一种强大的编程语言,在软件开发领域有着广泛的应用。数据结构作为C++编程中不可或缺的一部分,它决定了程序的性能和效率。本文将详细介绍C++中的常见数据结构,包括其定义、特点以及在实际应用中的使用方法。 常见数据结构 1. 数组 数组是一种…...

Unity NodeCanvas AI使用笔记

扩展: 1. 输入输出参数限制&#xff0c;增加描述&#xff0c;根据接口判断类型限制 2.选择节点&#xff0c;遍历节点&#xff0c;行为节点 3.行为节点 行为执行的时候有互斥关系&#xff0c;加入一个queue&#xff0c;最后执行 4.NodeCanvas的参数传参可以由上个节点传到下个节…...

(* IOB = “FORCE“ *) 的使用分享

在Xilinx FPGA设计中&#xff0c;IOBFORCE是一个与输入输出块&#xff08;IOB&#xff09;相关的属性设置。这个设置主要用于控制逻辑是否被推入到IOB&#xff08;Input/Output Block&#xff09;中&#xff0c;即FPGA芯片边缘的I/O引脚附近的专用硬件资源。使用IOB属性可以帮助…...

【大语言模型_7】利用ragas框架评测rag系统指标

一、介绍 ragas是一个用来评估RAG系统的框架&#xff0c;允许不在依赖人工注释的情况下&#xff0c;通过一套指标评估检索模块和生成模块的性能及其质量。 二、准备 数据准备&#xff1a;需要准备评估数据集&#xff0c;数据集格式如下 [{"question": "安全智…...

adb常用的命令

1. 查看adb版本 adb version 2. 将apk安装包安装到手机/模拟器上 adb install apk路径 3. 获取apk包名和界面名 包名&#xff08;package&#xff09;&#xff1a;决定程序的唯一性 界面名&#xff08;activity&#xff09;&#xff1a;一个界面界面名&#xff0c;对应一个界面…...

手动集成sqlite的方法

注意到sqlite有backup方法&#xff08;https://www.sqlite.org/backup.html&#xff09;。 也注意到android中sysroot下&#xff0c;没有sqlite3的库&#xff0c;也没有相关头文件。 如果要使用 sqlite 的backup&#xff0c;那么就需要手动集成sqlite代码到项目中。可以如下操…...

自然语言处理(NLP)技术

人工智能&#xff08;Artificial Intelligence&#xff0c;AI&#xff09;是一种模拟人类智能思维过程的技术&#xff0c;它在现代科技中的应用非常广泛&#xff0c;涉及诸多领域&#xff0c;如自然语言处理、计算机视觉、机器学习、数据分析等。以下是人工智能在现代科技中的应…...

【GPT入门】第25课 掌握 LangChain:链式调用的奥秘、特性与使用示例

【GPT入门】第25课 掌握 LangChain&#xff1a;链式调用的奥秘、特性与使用示例 语法解释各部分性质链式调用的性质调用方式注意事项 语法解释 你给出的代码 is_duplicated_chain (check_duplicated | model | parser) 运用了 LangChain 里的链式调用语法。在 LangChain 中&a…...

机器学习之DBSCAN算法详解

文章目录 引言1. DBSCAN算法概述2.DBSCAN算法的基本概念2.1 ε-邻域2.2 核心点&#xff08;Core Point&#xff09;2.3 边界点&#xff08;Border Point&#xff09;2.4 噪声点&#xff08;Noise Point&#xff09;2.5 直接密度可达&#xff08;Directly Density-Reachable&…...

借助vite来优化前端性能

Vite 是一个现代化的前端构建工具&#xff0c;凭借其基于原生 ES 模块的开发服务器和高效的构建能力&#xff0c;可以显著优化前端性能。 一、开发环境优化 1.快速启动与热更新 Vite 利用浏览器对 ES 模块的原生支持&#xff0c;在开发环境中无需打包&#xff0c;直接按需加载…...

[工控机安全] 使用DriverView快速排查不可信第三方驱动(附详细图文教程)

导语&#xff1a; 在工业控制领域&#xff0c;设备驱动程序的安全性至关重要。第三方驱动可能存在兼容性问题、安全漏洞甚至恶意代码&#xff0c;威胁设备稳定运行。本文将手把手教你使用 DriverView工具&#xff0c;高效完成工控机驱动安全检查&#xff0c;精准识别可疑驱动&a…...

Execution failed for task ‘:path_provider_android:compileDebugJavaWithJavac‘.

What went wrong: Execution failed for task ‘:path_provider_android:compileDebugJavaWithJavac’. Could not resolve all files for configuration ‘:path_provider_android:androidJdkImage’. Failed to transform core-for-system-modules.jar to match attributes {…...

基于SpringBoot的社区/物业管理系统

项目介绍 平台采用B/S结构&#xff0c;后端采用主流的SpringBoot语言进行开发&#xff0c;前端采用主流的Vue.js进行开发。是一个综合的社区/物业管理系统。 整个平台包括前台和后台两个部分。 - 前台功能包括&#xff1a;小区信息、社区论坛、社区公告、社区留言板、个人中心。…...

vmware下linux无法上网解决方法

首先&#xff0c;打开打开"编辑" “虚拟网络编辑器”,并将"桥接"方式的网卡选择为主机上网的网卡。 虚拟机中&#xff0c;设置IP地址为主机网卡同样子网下的ip地址&#xff1a; 并且要选择桥接模式&#xff01;注意如下图&#xff0c;"复制物理连接状…...

【数据库备份】docker中数据库备份脚本——MySql备份脚本

docker中数据库备份脚本——MySql备份脚本 #!/bin/bash# MySQL数据库信息 DB_USER"root" DB_PASSWORD"你的密码"# 备份保存主目录 BACKUP_ROOT"/data/data_backup/mysql"# 最多保留的备份日期文件夹数 MAX_DATE_FOLDERS15# 数组包含要备份的数据…...

SpringBoot 第二课(Ⅰ) 整合springmvc(详解)

目录 一、SpringBoot对静态资源的映射规则 1. WebJars 资源访问 2. 静态资源访问 3. 欢迎页配置 二、SpringBoot整合springmvc 概述 Spring MVC组件的自动配置 中央转发器&#xff08;DispatcherServlet&#xff09; 控制器&#xff08;Controller&#xff09; 视图解…...

centos家用笔记

改用阿里云yum源 因CentOS7已经停止维护&#xff0c;原有的yum源也无法使用&#xff0c;在国内&#xff0c;改用阿里云yum源是个方便的选择。 cd /etc/yum.repos.d/ mkdir backup mv Cent* backup wget http://mirrors.aliyun.com/repo/Centos-7.repo mv Centos-7.repo Cen…...

数据可视化(matplotlib)-------辅助图标的设置

目录 一、认识图表常用的辅助元素 坐标轴 二、设置坐标轴的标签、刻度范围和刻度标签 &#xff08;一&#xff09;、设置坐标轴的标签 1、xlabel()------设置x轴标签 2、ylabel()------设置y轴标签 &#xff08;二) 、设置刻度范围和刻度标签 1、xlim()和ylim()函数分别可…...

15-双链表-双链表基本操作

题目 来源 827. 双链表 - AcWing题库 思路 此题我只想说&#xff0c;千万千万别漏了头结点和尾结点&#xff0c;不然根本查不出来是哪里出了问题&#xff0c;因为传入的k会有问题&#xff1b;最左边插入&#xff0c;相当于是在头结点的右边插入&#xff08;也就是0号节点的右…...