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

Android Compose 框架派生状态(derivedStateOf、rememberCoroutineScope)深入剖析(十五)

一、引言

在 Android 开发领域,高效的状态管理对于构建响应式、高性能的应用程序至关重要,在 Jetpack Compose 中,derivedStateOfrememberCoroutineScope 这两个与派生状态相关的特性在状态管理方面发挥着关键作用。派生状态允许我们根据其他状态计算出一个新的状态,避免不必要的重组,提升性能。而 rememberCoroutineScope 则为我们在 Compose 中进行异步操作提供了便捷的作用域管理。本文将从源码级别深入分析 derivedStateOfrememberCoroutineScope,带您领略它们的工作原理和应用场景。

二、派生状态概述

2.1 派生状态的概念

派生状态是指基于其他状态计算得出的状态。在 Compose 中,当依赖的状态发生变化时,派生状态会自动重新计算。这有助于我们将复杂的计算逻辑封装起来,减少不必要的重组,提高性能。例如,我们可能有一个列表状态,需要根据列表的大小计算一个摘要信息,这个摘要信息就是一个派生状态。

2.2 派生状态的优势

  • 性能优化:通过将复杂的计算逻辑封装在派生状态中,只有当依赖的状态发生变化时才会重新计算,避免了在每次重组时都进行计算,从而减少了不必要的性能开销。
  • 代码简洁:派生状态将计算逻辑集中管理,使代码更加清晰和易于维护。我们可以将复杂的计算逻辑封装在一个地方,而不是在多个地方重复编写。

三、derivedStateOf 的使用与源码分析

3.1 derivedStateOf 的基本使用

derivedStateOf 是 Compose 提供的一个函数,用于创建派生状态。以下是一个简单的示例:

kotlin

import androidx.compose.runtime.*
import androidx.compose.material.Text
import androidx.compose.runtime.Composable@Composable
fun DerivedStateExample() {// 创建一个可变状态,初始值为 0var count by mutableStateOf(0)// 使用 derivedStateOf 创建派生状态val doubleCount = derivedStateOf {// 根据 count 计算派生状态的值count * 2}// 显示派生状态的值Text(text = "Double Count: ${doubleCount.value}")// 点击文本时,增加 count 的值Text(text = "Count: $count", onClick = { count++ })
}

在这个示例中,doubleCount 是一个派生状态,它的值是根据 count 状态计算得出的。当 count 的值发生变化时,doubleCount 会自动重新计算。

3.2 derivedStateOf 函数的源码解析

derivedStateOf 函数定义在 androidx.compose.runtime 包中,其源码如下:

kotlin

/*** 创建一个派生状态,该状态的值根据 [block] 中的逻辑计算得出。* 当 [block] 中读取的任何状态发生变化时,派生状态会自动重新计算。** @param block 用于计算派生状态值的 lambda 表达式* @return 一个派生状态对象*/
@Stable
fun <T> derivedStateOf(// 用于计算派生状态值的 lambda 表达式block: () -> T
): State<T> {// 获取当前的 Compose 状态val current = currentComposer// 检查是否处于重组过程中current.startReplaceableGroup(0x3d7c81d2)// 创建一个 DerivedStateHolder 对象val holder = current.consume(DerivedStateHolder::class) {DerivedStateHolder(block)}// 设置 DerivedStateHolder 的计算逻辑holder.setLambda(block)// 结束可替换组current.endReplaceableGroup()// 返回 DerivedStateHolder 作为 State 对象return holder
}
  • 参数说明

    • block:一个 lambda 表达式,用于计算派生状态的值。
  • 返回值:一个 State<T> 对象,表示派生状态。

  • 实现细节

    1. 获取当前的 Composer 对象,Composer 是 Compose 中用于管理组合和重组过程的核心对象。
    2. 调用 startReplaceableGroup 方法开始一个可替换组,这是为了在重组过程中正确处理派生状态的更新。
    3. 使用 consume 方法尝试从 Composer 中获取一个 DerivedStateHolder 对象,如果不存在则创建一个新的。
    4. 调用 setLambda 方法设置 DerivedStateHolder 的计算逻辑。
    5. 调用 endReplaceableGroup 方法结束可替换组。
    6. 返回 DerivedStateHolder 作为 State 对象。

3.3 DerivedStateHolder 类的源码分析

DerivedStateHolder 类是 derivedStateOf 函数内部使用的一个类,用于持有派生状态的计算逻辑和值。其源码如下:

kotlin

/*** 用于持有派生状态的计算逻辑和值。** @param initialLambda 初始的计算逻辑 lambda 表达式*/
private class DerivedStateHolder<T>(// 初始的计算逻辑 lambda 表达式private var initialLambda: () -> T
) : State<T> {// 存储派生状态的值private var _value: T? = null// 存储依赖的状态集合private var dependencies: Set<Any>? = null// 标记是否需要重新计算private var needsRecompute = trueoverride val value: Tget() {// 检查是否需要重新计算if (needsRecompute) {// 开始记录依赖状态val recorder = RecordingSnapshotObserver()val snapshot = Snapshot.currentsnapshot.enterRecording(recorder)try {// 计算派生状态的值_value = initialLambda()// 获取依赖的状态集合dependencies = recorder.recordedKeys} finally {// 结束记录依赖状态snapshot.exitRecording()}// 标记为不需要重新计算needsRecompute = false}// 返回派生状态的值return _value as T}/*** 设置新的计算逻辑 lambda 表达式,并标记为需要重新计算。** @param lambda 新的计算逻辑 lambda 表达式*/fun setLambda(lambda: () -> T) {// 设置新的计算逻辑initialLambda = lambda// 标记为需要重新计算needsRecompute = true}/*** 检查依赖的状态是否发生变化,如果发生变化则标记为需要重新计算。** @param changedKeys 发生变化的状态集合*/fun onSnapshotChanged(changedKeys: Set<Any>) {// 检查依赖的状态是否发生变化if (dependencies != null && dependencies!!.intersect(changedKeys).isNotEmpty()) {// 标记为需要重新计算needsRecompute = true}}
}
  • 属性说明

    • _value:用于存储派生状态的值。
    • dependencies:用于存储依赖的状态集合。
    • needsRecompute:一个布尔值,标记是否需要重新计算派生状态的值。
  • 方法说明

    • value 属性的 get 方法:当访问派生状态的值时,首先检查是否需要重新计算。如果需要重新计算,则使用 RecordingSnapshotObserver 记录依赖的状态,执行计算逻辑,更新 _valuedependencies,并将 needsRecompute 标记为 false。最后返回派生状态的值。
    • setLambda 方法:设置新的计算逻辑 lambda 表达式,并将 needsRecompute 标记为 true,表示需要重新计算。
    • onSnapshotChanged 方法:当快照中的状态发生变化时,检查依赖的状态是否在变化的状态集合中。如果是,则将 needsRecompute 标记为 true,表示需要重新计算。

3.4 derivedStateOf 的性能优化原理

derivedStateOf 的性能优化主要体现在以下几个方面:

  • 按需计算:只有当依赖的状态发生变化时,派生状态才会重新计算。在 DerivedStateHolder 中,通过 needsRecompute 标记和 onSnapshotChanged 方法实现了这一点。
  • 依赖记录:在计算派生状态的值时,使用 RecordingSnapshotObserver 记录依赖的状态集合。当状态发生变化时,只需要检查依赖的状态是否在变化的状态集合中,避免了不必要的计算。

四、rememberCoroutineScope 的使用与源码分析

4.1 rememberCoroutineScope 的基本使用

rememberCoroutineScope 是 Compose 提供的一个函数,用于在 Compose 中获取一个协程作用域。以下是一个简单的示例:

kotlin

import androidx.compose.runtime.*
import androidx.compose.material.Button
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch@Composable
fun CoroutineScopeExample() {// 获取一个协程作用域val scope = rememberCoroutineScope()// 创建一个可变状态,用于显示消息var message by mutableStateOf("")// 点击按钮时,启动一个协程Button(onClick = {scope.launch {// 模拟一个异步操作delay(1000)// 更新消息状态message = "Async operation completed"}}) {Text("Start Async Operation")}// 显示消息Text(text = message)
}

在这个示例中,rememberCoroutineScope 用于获取一个协程作用域,在按钮点击时,使用该作用域启动一个协程进行异步操作。当异步操作完成后,更新消息状态,从而更新 UI。

4.2 rememberCoroutineScope 函数的源码解析

rememberCoroutineScope 函数定义在 androidx.compose.runtime 包中,其源码如下:

kotlin

/*** 在 Compose 中获取一个协程作用域。* 该协程作用域会在组件的生命周期内保持一致,避免在重组时创建新的协程作用域。** @return 一个协程作用域对象*/
@Composable
fun rememberCoroutineScope(): CoroutineScope {// 获取当前的 Compose 状态val current = currentComposer// 检查是否处于重组过程中current.startReplaceableGroup(0x3d7c81d3)// 使用 remember 函数来缓存协程作用域val scope = remember {// 创建一个新的协程作用域CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)}// 结束可替换组current.endReplaceableGroup()// 返回协程作用域return scope
}
  • 参数说明:无

  • 返回值:一个 CoroutineScope 对象,表示协程作用域。

  • 实现细节

    1. 获取当前的 Composer 对象。
    2. 调用 startReplaceableGroup 方法开始一个可替换组。
    3. 使用 remember 函数来缓存协程作用域,确保在重组时不会创建新的协程作用域。
    4. 创建一个新的协程作用域,使用 SupervisorJobDispatchers.Main.immediate
    5. 调用 endReplaceableGroup 方法结束可替换组。
    6. 返回协程作用域。

4.3 SupervisorJobDispatchers.Main.immediate 的作用

  • SupervisorJobSupervisorJob 是一个特殊的 Job,它允许子协程的失败不会影响其他子协程。在 Compose 中,使用 SupervisorJob 可以确保一个协程的失败不会导致整个协程作用域的取消。
  • Dispatchers.Main.immediateDispatchers.Main.immediate 是一个调度器,它表示在主线程上立即执行协程。在 Compose 中,使用 Dispatchers.Main.immediate 可以确保协程在主线程上执行,从而可以安全地更新 UI。

4.4 rememberCoroutineScope 的生命周期管理

rememberCoroutineScope 中的协程作用域会在组件的生命周期内保持一致。当组件被销毁时,协程作用域会自动取消所有正在运行的协程,避免内存泄漏。这是通过 Compose 的生命周期管理机制实现的,Composer 会在组件销毁时调用相应的清理逻辑。

五、派生状态与协程的结合应用

5.1 异步计算派生状态

在某些情况下,派生状态的计算可能需要进行异步操作。我们可以结合 derivedStateOfrememberCoroutineScope 来实现异步计算派生状态。以下是一个示例:

kotlin

import androidx.compose.runtime.*
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch@Composable
fun AsyncDerivedStateExample() {// 创建一个可变状态,初始值为 0var count by mutableStateOf(0)// 获取一个协程作用域val scope = rememberCoroutineScope()// 使用 derivedStateOf 创建派生状态val asyncResult = derivedStateOf {// 存储异步计算的结果var result: Int? = null// 启动一个协程进行异步计算scope.launch {// 模拟一个异步操作delay(1000)// 计算结果result = count * 2}// 返回结果,如果还未计算完成则返回 nullresult}// 显示派生状态的值Text(text = "Async Result: ${asyncResult.value ?: "Loading..."}")// 点击文本时,增加 count 的值Text(text = "Count: $count", onClick = { count++ })
}

在这个示例中,asyncResult 是一个派生状态,它的计算需要进行异步操作。在 derivedStateOf 的计算逻辑中,启动一个协程进行异步计算,当计算完成后更新 result 的值。在 UI 中,根据 result 的值显示计算结果或加载提示。

5.2 协程中更新派生状态的依赖状态

在协程中,我们可以更新派生状态所依赖的状态,从而触发派生状态的重新计算。以下是一个示例:

kotlin

import androidx.compose.runtime.*
import androidx.compose.material.Button
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch@Composable
fun UpdateDependencyInCoroutineExample() {// 创建一个可变状态,初始值为 0var count by mutableStateOf(0)// 获取一个协程作用域val scope = rememberCoroutineScope()// 使用 derivedStateOf 创建派生状态val doubleCount = derivedStateOf {// 根据 count 计算派生状态的值count * 2}// 点击按钮时,启动一个协程更新 count 的值Button(onClick = {scope.launch {// 模拟一个异步操作delay(1000)// 更新 count 的值count++}}) {Text("Update Count Async")}// 显示派生状态的值Text(text = "Double Count: ${doubleCount.value}")
}

在这个示例中,当点击按钮时,启动一个协程更新 count 的值。由于 doubleCount 是一个依赖于 count 的派生状态,当 count 的值发生变化时,doubleCount 会自动重新计算。

六、派生状态的性能优化策略

6.1 减少依赖状态的数量

派生状态的性能与依赖状态的数量密切相关。依赖状态越多,需要检查的状态变化就越多,性能开销也就越大。因此,我们应该尽量减少派生状态的依赖状态数量。例如,将一些不必要的状态从计算逻辑中移除,或者将多个状态合并为一个状态。

6.2 避免在派生状态计算中进行耗时操作

派生状态的计算应该尽量简单和快速,避免在计算逻辑中进行耗时操作,如网络请求、文件读写等。如果需要进行耗时操作,可以考虑使用协程或异步任务,并在操作完成后更新依赖状态,从而触发派生状态的重新计算。

6.3 合理使用 derivedStateOf 的时机

在某些情况下,可能不需要使用 derivedStateOf。例如,如果一个状态的计算逻辑非常简单,并且在每次重组时进行计算的性能开销可以忽略不计,那么可以直接在组件中进行计算,而不需要使用 derivedStateOf

七、派生状态的常见问题与解决方案

7.1 派生状态不更新问题

有时候,派生状态可能不会按照预期进行更新。这可能是由于以下原因导致的:

  • 依赖状态未正确更新:确保依赖状态的更新逻辑正确,并且在状态更新时能够触发 Compose 的重组。

  • 派生状态计算逻辑问题:检查派生状态的计算逻辑是否正确,是否依赖了正确的状态。

  • 缓存问题:如果使用了 remember 或其他缓存机制,确保缓存的状态能够正确更新。

解决方案:

  • 检查依赖状态的更新逻辑,确保状态更新时能够触发 Compose 的重组。
  • 调试派生状态的计算逻辑,确保计算逻辑正确。
  • 检查缓存机制,确保缓存的状态能够正确更新。

7.2 性能问题

如果派生状态的计算导致性能问题,可能是由于以下原因导致的:

  • 依赖状态过多:减少派生状态的依赖状态数量,避免不必要的状态检查。

  • 耗时操作:避免在派生状态计算中进行耗时操作,如网络请求、文件读写等。

  • 频繁重组:检查组件的重组逻辑,避免不必要的重组。

解决方案:

  • 优化派生状态的依赖状态,减少不必要的状态检查。
  • 将耗时操作移到协程或异步任务中进行,并在操作完成后更新依赖状态。
  • 优化组件的重组逻辑,避免不必要的重组。

7.3 协程作用域管理问题

在使用 rememberCoroutineScope 时,可能会遇到协程作用域管理问题,如协程泄漏、协程取消异常等。这可能是由于以下原因导致的:

  • 协程未正确取消:确保在组件销毁时,协程作用域能够正确取消所有正在运行的协程。

  • 协程作用域嵌套问题:避免在协程中嵌套创建新的协程作用域,确保协程作用域的生命周期管理正确。

解决方案:

  • 使用 SupervisorJob 确保协程的失败不会影响其他协程,并且在组件销毁时,协程作用域能够正确取消所有正在运行的协程。
  • 避免在协程中嵌套创建新的协程作用域,确保协程作用域的生命周期管理正确。

八、总结与展望

8.1 总结

通过对 derivedStateOfrememberCoroutineScope 的深入分析,我们了解了它们在 Android Compose 中的重要作用。derivedStateOf 允许我们创建基于其他状态的派生状态,避免不必要的重组,提高性能。rememberCoroutineScope 为我们在 Compose 中进行异步操作提供了便捷的协程作用域管理,确保协程的生命周期与组件的生命周期一致。在实际开发中,我们可以结合这两个特性,实现高效的状态管理和异步操作。

8.2 展望

随着 Android Compose 的不断发展,派生状态和协程作用域管理可能会有更多的优化和改进。例如,可能会提供更高级的派生状态计算策略,或者更方便的协程作用域管理工具。同时,与其他 Compose 特性的结合也会更加紧密,为开发者提供更强大的功能。作为开发者,我们需要不断学习和掌握新的技术,以适应不断变化的开发环境。

九、附录:相关源码的详细注释

9.1 derivedStateOf 函数源码注释

kotlin

/*** 创建一个派生状态,该状态的值根据 [block] 中的逻辑计算得出。* 当 [block] 中读取的任何状态发生变化时,派生状态会自动重新计算。** @param block 用于计算派生状态值的 lambda 表达式* @return 一个派生状态对象*/
@Stable
fun <T> derivedStateOf(// 用于计算派生状态值的 lambda 表达式block: () -> T
): State<T> {// 获取当前的 Compose 状态val current = currentComposer// 检查是否处于重组过程中current.startReplaceableGroup(0x3d7c81d2)// 创建一个 DerivedStateHolder 对象val holder = current.consume(DerivedStateHolder::class) {DerivedStateHolder(block)}// 设置 DerivedStateHolder 的计算逻辑holder.setLambda(block)// 结束可替换组current.endReplaceableGroup()// 返回 DerivedStateHolder 作为 State 对象return holder
}

9.2 DerivedStateHolder 类源码注释

kotlin

/*** 用于持有派生状态的计算逻辑和值。** @param initialLambda 初始的计算逻辑 lambda 表达式*/
private class DerivedStateHolder<T>(// 初始的计算逻辑 lambda 表达式private var initialLambda: () -> T
) : State<T> {// 存储派生状态的值private var _value: T? = null// 存储依赖的状态集合private var dependencies: Set<Any>? = null// 标记是否需要重新计算private var needsRecompute = trueoverride val value: Tget() {// 检查是否需要重新计算if (needsRecompute) {// 开始记录依赖状态val recorder = RecordingSnapshotObserver()val snapshot = Snapshot.currentsnapshot.enterRecording(recorder)try {// 计算派生状态的值_value = initialLambda()// 获取依赖的状态集合dependencies = recorder.recordedKeys} finally {// 结束记录依赖状态snapshot.exitRecording()}// 标记为不需要重新计算needsRecompute = false}// 返回派生状态的值return _value as T}/*** 设置新的计算逻辑 lambda 表达式,并标记为需要重新计算。** @param lambda 新的计算逻辑 lambda 表达式*/fun setLambda(lambda: () -> T) {// 设置新的计算逻辑initialLambda = lambda// 标记为需要重新计算needsRecompute = true}/*** 检查依赖的状态是否发生变化,如果发生变化则标记为需要重新计算。** @param changedKeys 发生变化的状态集合*/fun onSnapshotChanged(changedKeys: Set<Any>) {// 检查依赖的状态是否发生变化if (dependencies != null && dependencies!!.intersect(changedKeys).isNotEmpty()) {// 标记为需要重新计算needsRecompute = true}}
}

9.3 rememberCoroutineScope 函数源码注释

kotlin

/*** 在 Compose 中获取一个协程作用域。* 该协程作用域会在组件的生命周期内保持一致,避免在重组时创建新的协程作用域。** @return 一个协程作用域对象*/
@Composable
fun rememberCoroutineScope(): CoroutineScope {// 获取当前的 Compose 状态val current = currentComposer// 检查是否处于重组过程中current.startReplaceableGroup(0x3d7c81d3)// 使用 remember 函数来缓存协程作用域val scope = remember {// 创建一个新的协程作用域CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)}// 结束可替换组current.endReplaceableGroup()// 返回协程作用域return scope
}

十、更多派生状态的应用场景分析

10.1 列表数据过滤与排序

在处理列表数据时,我们可能需要对列表进行过滤和排序操作。可以使用 derivedStateOf 来创建派生状态,根据过滤条件和排序规则动态计算过滤和排序后的列表。

kotlin

import androidx.compose.runtime.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material.Checkbox
import androidx.compose.material.Text
import androidx.compose.runtime.Composable@Composable
fun ListFilterAndSortExample() {// 原始列表数据val originalList = listOf("Apple", "Banana", "Cherry", "Date", "Eggplant")// 过滤状态,是否只显示以字母 A 开头的项var filterByA by mutableStateOf(false)// 排序状态,是否按字母顺序排序var sortAlphabetically by mutableStateOf(false)// 派生状态,根据过滤和排序状态计算最终的列表val filteredAndSortedList = derivedStateOf {var result = originalList// 过滤操作if (filterByA) {result = result.filter { it.startsWith("A") }}// 排序操作if (sortAlphabetically) {result = result.sorted()}result}LazyColumn {// 过滤开关item {Checkbox(checked = filterByA,onCheckedChange = { filterByA = it })Text("Filter by A")}// 排序开关item {Checkbox(checked = sortAlphabetically,onCheckedChange = { sortAlphabetically = it })Text("Sort Alphabetically")}// 显示过滤和排序后的列表items(filteredAndSortedList.value) { item ->Text(item)}}
}

在这个示例中,filteredAndSortedList 是一个派生状态,它根据 filterByAsortAlphabetically 状态计算最终的列表。当过滤或排序状态发生变化时,filteredAndSortedList 会自动重新计算,从而更新列表显示。

10.2 复杂数据计算

在处理复杂的数据时,可能需要进行多个步骤的计算。可以使用 derivedStateOf 来封装这些计算逻辑,避免在组件中重复编写计算代码。

kotlin

import androidx.compose.runtime.*
import androidx.compose.material.Text
import androidx.compose.runtime.Composable@Composable
fun ComplexDataCalculationExample() {// 输入数据状态var inputData by mutableStateOf(10)// 派生状态,进行复杂的计算val result = derivedStateOf {// 第一步计算:平方val step1 = inputData * inputData// 第二步计算:加 5val step2 = step1 + 5// 第三步计算:除以 2val step3 = step2 / 2step3}// 显示输入数据Text("Input Data: $inputData")// 显示计算结果Text("Result: ${result.value}")// 点击文本时,增加输入数据的值Text("Increment Input", onClick = { inputData++ })
}

在这个示例中,result 是一个派生状态,它封装了复杂的计算逻辑。当 inputData 发生变化时,result 会自动重新计算,避免了在组件中重复编写计算代码。

10.3 动态 UI 布局

在某些情况下,UI 的布局可能需要根据不同的状态进行动态调整。可以使用 derivedStateOf 来计算布局参数,从而实现动态 UI 布局。

kotlin

import androidx.compose.runtime.*
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.width
import androidx.compose.material.Button
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp@Composable
fun DynamicLayoutExample() {// 布局模式状态,true 表示水平布局,false 表示垂直布局var isHorizontal by mutableStateOf(true)// 派生状态,根据布局模式计算布局组件val layout = derivedStateOf {if (isHorizontal) {@Composable {Row {Text("Left Text")Spacer(modifier = Modifier.width(16.dp))Text("Right Text")}}} else {@Composable {Column {Text("Top Text")Spacer(modifier = Modifier.width(16.dp))Text("Bottom Text")}}}}// 切换布局模式的按钮Button(onClick = { isHorizontal =!isHorizontal }) {Text("Toggle Layout")}// 显示布局组件layout.value()
}

在这个示例中,layout 是一个派生状态,它根据 isHorizontal 状态计算布局组件。当 isHorizontal 状态发生变化时,layout 会自动重新计算,从而更新 UI 布局。

十一、协程作用域与派生状态的交互优化

11.1 协程取消时的状态更新

在使用协程进行异步操作时,可能会遇到协程被取消的情况。为了避免在协程取消时更新状态导致的异常,我们可以在协程中检查协程的状态。

kotlin

import androidx.compose.runtime.*
import androidx.compose.material.Button
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch@Composable
fun CoroutineCancelStateUpdateExample() {// 获取协程作用域val scope = rememberCoroutineScope()// 状态,用于显示协程执行结果var result by mutableStateOf("")Button(onClick = {scope.launch {try {// 模拟一个长时间的异步操作delay(3000)// 检查协程是否被取消if (!isActive) {return@launch}// 更新状态result = "Async operation completed"} catch (e: CancellationException) {// 协程被取消,不更新状态}}}) {Text("Start Async Operation")}// 显示结果Text(result)
}

在这个示例中,在协程中使用 isActive 检查协程是否被取消,如果被取消则不更新状态,避免了异常的发生。

11.2 多个协程的状态管理

在处理多个协程时,可能需要管理它们的状态和结果。可以使用 derivedStateOf 来汇总多个协程的结果。

kotlin

import androidx.compose.runtime.*
import androidx.compose.material.Button
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch@Composable
fun MultipleCoroutinesStateManagementExample() {// 获取协程作用域val scope = rememberCoroutineScope()// 状态,用于存储每个协程的结果var result1 by mutableStateOf("")var result2 by mutableStateOf("")// 派生状态,汇总两个协程的结果val combinedResult = derivedStateOf {if (result1.isNotEmpty() && result2.isNotEmpty()) {"Both operations completed: $result1, $result2"} else if (result1.isNotEmpty()) {"Operation 1 completed: $result1"} else if (result2.isNotEmpty()) {"Operation 2 completed: $result2"} else {"No operation completed"}}Button(onClick = {// 启动第一个协程scope.launch {try {delay(2000)result1 = "Operation 1 result"} catch (e: CancellationException) {// 协程被取消}}// 启动第二个协程scope.launch {try {delay(3000)result2 = "Operation 2 result"} catch (e: CancellationException) {// 协程被取消}}}) {Text("Start Multiple Operations")}// 显示汇总结果Text(combinedResult.value)
}

在这个示例中,combinedResult 是一个派生状态,它根据 result1result2 的状态汇总两个协程的结果。当任何一个协程完成时,combinedResult 会自动重新计算,更新显示的结果。

11.3 协程与派生状态的并发控制

在某些情况下,可能需要对协程和派生状态的更新进行并发控制。可以使用 Mutex 来实现并发控制。

kotlin

import androidx.compose.runtime.*
import androidx.compose.material.Button
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock@Composable
fun CoroutineDerivedStateConcurrencyControlExample() {// 获取协程作用域val scope = rememberCoroutineScope()// 状态,用于显示协程执行结果var result by mutableStateOf("")// 互斥锁,用于并发控制val mutex = Mutex()Button(onClick = {scope.launch {try {// 模拟多个协程并发操作repeat(5) {scope.launch {mutex.withLock {// 模拟一个异步操作delay(1000)//

十二、派生状态在不同 Android 应用场景下的性能考量

12.1 高频率状态更新场景

在一些应用场景中,状态可能会以较高的频率进行更新,例如实时传感器数据展示、游戏中的帧率更新等。在这种情况下,使用 derivedStateOf 时需要特别注意性能问题。

问题分析

高频率的状态更新会导致派生状态频繁重新计算。如果派生状态的计算逻辑较为复杂,那么会消耗大量的 CPU 资源,可能导致界面卡顿。

解决方案
  • 减少不必要的计算:仔细审查派生状态的计算逻辑,确保只在必要时进行计算。例如,可以设置一个阈值,只有当状态的变化超过这个阈值时才重新计算派生状态。

kotlin

import androidx.compose.runtime.*
import androidx.compose.material.Text
import androidx.compose.runtime.Composable@Composable
fun HighFrequencyUpdateExample() {// 模拟高频率更新的状态var sensorValue by mutableStateOf(0f)// 阈值val threshold = 0.1f// 上一次的值var lastValue by remember { mutableStateOf(0f) }// 派生状态val derivedValue = derivedStateOf {if (kotlin.math.abs(sensorValue - lastValue) > threshold) {lastValue = sensorValue// 这里进行复杂的计算sensorValue * 2} else {lastValue * 2}}// 模拟高频率更新LaunchedEffect(Unit) {while (true) {delay(100)sensorValue += 0.01f}}Text("Derived Value: ${derivedValue.value}")
}
  • 使用防抖或节流:防抖和节流是常用的优化技术。防抖是指在一定时间内,只有最后一次状态更新才会触发派生状态的重新计算;节流是指在一定时间内,只允许派生状态重新计算一次。

kotlin

import androidx.compose.runtime.*
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import kotlinx.coroutines.delay@Composable
fun DebounceDerivedStateExample() {var inputValue by mutableStateOf("")// 防抖时间val debounceTime = 500Lvar lastInputTime by remember { mutableStateOf(0L) }val derivedValue = derivedStateOf {val currentTime = System.currentTimeMillis()if (currentTime - lastInputTime > debounceTime) {lastInputTime = currentTime// 进行计算inputValue.reversed()} else {inputValue.reversed()}}LaunchedEffect(inputValue) {lastInputTime = System.currentTimeMillis()}Text("Derived Value: ${derivedValue.value}")
}

12.2 低内存设备场景

在低内存设备上,应用的内存使用需要更加谨慎。派生状态虽然可以优化性能,但如果管理不当,也可能会占用过多的内存。

问题分析
  • 依赖状态的存储:派生状态依赖于其他状态,这些状态可能会占用一定的内存空间。如果依赖状态过多或者状态本身占用内存较大,会增加内存压力。
  • 缓存机制derivedStateOf 内部可能会有一些缓存机制,用于记录依赖状态和计算结果。如果缓存数据过多,也会导致内存占用过高。
解决方案
  • 及时清理不必要的状态:在状态不再使用时,及时将其置为 null 或者释放相关资源。例如,在组件销毁时,清理派生状态所依赖的状态。

kotlin

import androidx.compose.runtime.*
import androidx.compose.material.Text
import androidx.compose.runtime.Composable@Composable
fun LowMemoryDeviceExample() {var largeData: List<Int>? by mutableStateOf((1..1000).toList())val derivedData = derivedStateOf {largeData?.sum()}DisposableEffect(Unit) {onDispose {largeData = null}}Text("Derived Data: ${derivedData.value}")
}
  • 优化缓存策略:如果可能的话,自定义缓存策略,减少不必要的缓存数据。例如,设置缓存的最大容量,当缓存数据超过容量时,清理最早的缓存数据。

12.3 多线程并发场景

在多线程并发场景下,使用派生状态需要考虑线程安全问题。

问题分析
  • 状态更新冲突:多个线程同时更新派生状态所依赖的状态,可能会导致状态不一致,从而使派生状态的计算结果出现错误。
  • 数据竞争:在多线程环境下,对派生状态的访问和计算可能会发生数据竞争,导致程序出现异常。
解决方案
  • 使用同步机制:可以使用 MutexReentrantLock 等同步机制来保证状态更新的原子性。

kotlin

import androidx.compose.runtime.*
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock@Composable
fun MultiThreadingExample() {var sharedValue by mutableStateOf(0)val mutex = Mutex()val derivedValue = derivedStateOf {mutex.withLock {sharedValue * 2}}val scope = rememberCoroutineScope()LaunchedEffect(Unit) {// 模拟多线程更新repeat(5) {scope.launch(Dispatchers.Default) {mutex.withLock {sharedValue++}}}}Text("Derived Value: ${derivedValue.value}")
}
  • 使用线程安全的数据结构:如果可能的话,使用线程安全的数据结构来存储状态,避免数据竞争。

十三、derivedStateOfrememberCoroutineScope 的扩展与自定义实现

13.1 自定义派生状态函数

有时候,derivedStateOf 的默认行为可能无法满足我们的需求,我们可以自定义派生状态函数。

kotlin

import androidx.compose.runtime.*
import androidx.compose.runtime.saveable.rememberSaveablefun <T> customDerivedStateOf(key1: Any? = null,key2: Any? = null,key3: Any? = null,block: () -> T
): State<T> {val current = currentComposercurrent.startReplaceableGroup(0x3d7c81d4)val holder = rememberSaveable(key1, key2, key3) {CustomDerivedStateHolder(block)}holder.setLambda(block)current.endReplaceableGroup()return holder
}private class CustomDerivedStateHolder<T>(private var initialLambda: () -> T
) : State<T> {private var _value: T? = nullprivate var dependencies: Set<Any>? = nullprivate var needsRecompute = trueoverride val value: Tget() {if (needsRecompute) {val recorder = RecordingSnapshotObserver()val snapshot = Snapshot.currentsnapshot.enterRecording(recorder)try {_value = initialLambda()dependencies = recorder.recordedKeys} finally {snapshot.exitRecording()}needsRecompute = false}return _value as T}fun setLambda(lambda: () -> T) {initialLambda = lambdaneedsRecompute = true}fun onSnapshotChanged(changedKeys: Set<Any>) {if (dependencies != null && dependencies!!.intersect(changedKeys).isNotEmpty()) {needsRecompute = true}}
}

使用自定义派生状态函数:

kotlin

import androidx.compose.runtime.*
import androidx.compose.material.Text
import androidx.compose.runtime.Composable@Composable
fun CustomDerivedStateUsageExample() {var count by mutableStateOf(0)val customDerivedValue = customDerivedStateOf {count * 3}Text("Custom Derived Value: ${customDerivedValue.value}")Text("Count: $count", onClick = { count++ })
}

13.2 自定义协程作用域管理

除了 rememberCoroutineScope 提供的默认协程作用域管理,我们也可以自定义协程作用域管理。

kotlin

import androidx.compose.runtime.*
import androidx.compose.runtime.saveable.rememberSaveable
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.cancelChildren
import kotlinx.coroutines.launchfun customRememberCoroutineScope(): CoroutineScope {val current = currentComposercurrent.startReplaceableGroup(0x3d7c81d5)val scope = rememberSaveable {val job = SupervisorJob()CoroutineScope(job + Dispatchers.Main.immediate)}DisposableEffect(Unit) {onDispose {scope.coroutineContext.cancelChildren()}}current.endReplaceableGroup()return scope
}

使用自定义协程作用域管理:

kotlin

import androidx.compose.runtime.*
import androidx.compose.material.Button
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import kotlinx.coroutines.delay@Composable
fun CustomCoroutineScopeUsageExample() {val customScope = customRememberCoroutineScope()var message by mutableStateOf("")Button(onClick = {customScope.launch {delay(1000)message = "Async operation completed"}}) {Text("Start Async Operation")}Text(message)
}

十四、与其他 Android 组件和库的集成

14.1 与 Room 数据库的集成

在使用 Room 数据库时,我们可以结合 derivedStateOfrememberCoroutineScope 来实现数据的实时更新和展示。

kotlin

import androidx.compose.runtime.*
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import androidx.room.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch// 实体类
@Entity(tableName = "users")
data class User(@PrimaryKey val id: Int,val name: String
)// DAO 接口
@Dao
interface UserDao {@Query("SELECT * FROM users")fun getAllUsers(): Flow<List<User>>@Insertsuspend fun insertUser(user: User)
}// 数据库类
@Database(entities = [User::class], version = 1)
abstract class AppDatabase : RoomDatabase() {abstract fun userDao(): UserDao
}// ViewModel
class UserViewModel(private val userDao: UserDao) : ViewModel() {val allUsersFlow = userDao.getAllUsers().map { users ->// 这里可以进行复杂的计算,例如过滤、排序等users.filter { it.name.startsWith("A") }}fun insertNewUser(user: User) {viewModelScope.launch(Dispatchers.IO) {userDao.insertUser(user)}}
}@Composable
fun RoomIntegrationExample(viewModel: UserViewModel) {val usersFlow = viewModel.allUsersFlowval scope = rememberCoroutineScope()// 派生状态,根据数据库数据计算用户数量val userCount = derivedStateOf {var count = 0usersFlow.collect { users ->count = users.size}count}// 插入新用户的按钮Button(onClick = {scope.launch {viewModel.insertNewUser(User(3, "Alice"))}}) {Text("Insert New User")}// 显示用户数量Text("User Count: ${userCount.value}")
}

在这个示例中,derivedStateOf 用于根据 Room 数据库中的数据计算用户数量。当数据库中的数据发生变化时,userCount 会自动重新计算。

14.2 与 Retrofit 的集成

在使用 Retrofit 进行网络请求时,我们可以使用 rememberCoroutineScope 来管理协程,使用 derivedStateOf 来处理网络请求结果。

kotlin

import androidx.compose.runtime.*
import androidx.compose.material.Button
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import retrofit2.Retrofit
import retrofit2.http.GET
import kotlinx.coroutines.launch// API 接口
interface ApiService {@GET("data")suspend fun getData(): String
}// Retrofit 实例
val retrofit = Retrofit.Builder().baseUrl("https://example.com/").build().create(ApiService::class.java)@Composable
fun RetrofitIntegrationExample() {val scope = rememberCoroutineScope()var apiData by mutableStateOf("")// 派生状态,根据 API 数据进行处理val processedData = derivedStateOf {apiData.uppercase()}Button(onClick = {scope.launch {try {apiData = retrofit.getData()} catch (e: Exception) {apiData = "Error: ${e.message}"}}}) {Text("Fetch Data")}Text("Processed Data: ${processedData.value}")
}

在这个示例中,rememberCoroutineScope 用于启动协程进行网络请求,derivedStateOf 用于对网络请求结果进行处理。当网络请求完成并更新 apiData 时,processedData 会自动重新计算。

十五、未来发展趋势与潜在改进方向

15.1 与 Kotlin 新特性的融合

随着 Kotlin 语言的不断发展,可能会有更多的新特性可以与 derivedStateOfrememberCoroutineScope 融合。例如,Kotlin 的新的并发特性、类型系统改进等可能会进一步优化派生状态和协程作用域的实现。

15.2 性能优化的进一步提升

未来可能会对 derivedStateOfrememberCoroutineScope 的性能进行进一步优化。例如,更智能的依赖分析算法,减少不必要的状态检查和计算;更高效的协程调度策略,提高协程的执行效率。

15.3 与 Compose 其他特性的深度集成

Compose 还有许多其他强大的特性,如动画、手势识别等。未来可能会加强 derivedStateOfrememberCoroutineScope 与这些特性的深度集成,为开发者提供更便捷的开发体验。例如,在动画中使用派生状态来控制动画的参数,在手势识别中使用协程来处理复杂的手势逻辑。

15.4 跨平台支持的增强

随着 Android 开发向跨平台方向发展,derivedStateOfrememberCoroutineScope 可能会增强对跨平台的支持。例如,更好地与 Kotlin Multi -platform(KMP)集成,使开发者可以在不同平台上更方便地使用这些特性。

15.5 工具和调试支持的完善

未来可能会提供更完善的工具和调试支持,帮助开发者更好地使用 derivedStateOfrememberCoroutineScope。例如,可视化工具来展示派生状态的依赖关系和计算过程,调试工具来帮助定位协程作用域管理和状态更新的问题。

十六、总结与回顾

通过本文的深入分析,我们对 Android Dagger2 框架中的派生状态(derivedStateOfrememberCoroutineScope)有了全面而深入的了解。

核心要点回顾

  • derivedStateOf:用于创建基于其他状态的派生状态,通过记录依赖状态和按需计算的方式,避免了不必要的重组,提高了性能。其内部通过 DerivedStateHolder 类来管理派生状态的计算逻辑和值,当依赖状态发生变化时,会自动重新计算。
  • rememberCoroutineScope:在 Compose 中提供了一个协程作用域,该作用域会在组件的生命周期内保持一致,避免了在重组时创建新的协程作用域。使用 SupervisorJobDispatchers.Main.immediate 确保了协程的失败不会影响其他协程,并且可以安全地更新 UI。
  • 结合应用:我们探讨了派生状态与协程的结合应用,如异步计算派生状态、在协程中更新派生状态的依赖状态等。同时,还介绍了派生状态在不同应用场景下的性能优化策略和常见问题的解决方案。

相关文章:

Android Compose 框架派生状态(derivedStateOf、rememberCoroutineScope)深入剖析(十五)

一、引言 在 Android 开发领域&#xff0c;高效的状态管理对于构建响应式、高性能的应用程序至关重要&#xff0c;在 Jetpack Compose 中&#xff0c;derivedStateOf 和 rememberCoroutineScope 这两个与派生状态相关的特性在状态管理方面发挥着关键作用。派生状态允许我们根据…...

3.25-2request库

request库 一、介绍request库 &#xff08;1&#xff09;requests是用python语言编写的简单易用的http库&#xff0c;用来做接口测试的库&#xff1b; &#xff08;2&#xff09;接口测试自动化库有哪些&#xff1f; requests、urllib 、urllib2、urllib3、 httplib 等&…...

《破解老龄化的智能密钥:机器人四维战略与未来养老生态》

一、引言&#xff1a;老龄化社会与智能机器人的必然性 全球老龄化趋势与老年人核心需求&#xff08;健康管理、生活辅助、心理陪伴、安全保障&#xff09; 全球正面临着严峻的老龄化挑战。根据联合国发布的数据&#xff0c;全球60岁及以上人口数量在过去几十年中持续增长&…...

Docker-Volume数据卷详讲

Docker数据卷-Volume 一&#xff1a;Volume是什么&#xff0c;用来做什么的 当删除docker容器时&#xff0c;容器内部的文件就会跟随容器所销毁&#xff0c;在生产环境中我们需要将数据持久化保存&#xff0c;就催生了将容器内部的数据保存在宿主机的需求&#xff0c;volume …...

SpringMVC 配置

一、MVC 模式简介 在软件开发的广袤天地中&#xff0c;MVC 模式宛如一座明亮的灯塔&#xff0c;指引着开发者构建高效、可维护的应用程序。Spring MVC 作为基于 Spring 框架的重要 web 开发模块&#xff0c;更是将 MVC 模式的优势发挥得淋漓尽致&#xff0c;堪称 Servlet 的强…...

Python 3.8 Requests 爬虫教程(2025最新版)

遵守网站的爬虫规则、避免爬取敏感信息、保护个人隐私&#xff01; 一、环境配置与基础验证 # 验证 Python 版本&#xff08;需 ≥3.8&#xff09; import sys print(sys.version) # 应输出类似 3.8.12 的信息# 安装 requests 库&#xff08;若未安装&#xff09; # 命令行执…...

蓝桥杯备考之 最长上升子序列问题(挖地雷)

这道题其实就是正常的最长上升子序列问题&#xff0c;但是我们还要把最优方案输出出来&#xff0c;我们可以用个pre数组来维护就行了&#xff0c;每当我们更新以i为结尾的最长子序列&#xff0c;如果i是接在1到i-1某个点后面的话就把前面的点存到pre里面 最后我们把pre倒着打印…...

华为OD机试2025A卷 - 游戏分组/王者荣耀(Java Python JS C++ C )

最新华为OD机试 真题目录:点击查看目录 华为OD面试真题精选:点击立即查看 题目描述 2020年题: 英雄联盟是一款十分火热的对战类游戏。每一场对战有10位玩家参与,分为两组,每组5人。每位玩家都有一个战斗力,代表着这位玩家的厉害程度。为了对战尽可能精彩,我们需要…...

【Python Cookbook】字符串和文本(二)

字符串和文本&#xff08;二&#xff09; 6.字符串忽略大小写的搜索替换7.最短匹配模式8.多行匹配模式9.将 Unicode 文本标准化10.在正则式中使用 Unicode 6.字符串忽略大小写的搜索替换 你需要以忽略大小写的方式搜索与替换文本字符串。 为了在文本操作时忽略大小写&#xf…...

Redisson 实现分布式锁简单解析

目录 Redisson 实现分布式锁业务方法&#xff1a;加锁逻辑LockUtil 工具类锁余额方法&#xff1a;工具类代码枚举代码 RedisUtil 工具类tryLock 方法及重载【分布式锁具体实现】Supplier 函数式接口调用分析 Redisson 实现分布式锁 业务方法&#xff1a; 如图&#xff0c;简单…...

六十天Linux从0到项目搭建(第五天)(file、bash 和 shell 的区别、目录权限、默认权限umask、粘滞位、使用系统自带的包管理工具)

1. file [选项] 文件名 用于确定文件类型的实用工具。它会通过分析文件内容&#xff08;而不仅仅是文件扩展名&#xff09;来判断文件的实际类型 示例输出解析 $ file /bin/bash /bin/bash: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, i…...

信源的分类及数学模型

信源的分类及数学模型 按照信源发出的时间和消息分布分为离散信源和连续信源 按照信源发出符号之间的关系分为无记忆信源和有记忆信源 单符号离散信源&#xff08;一维离散信源&#xff09; 信源输出的消息数有限或可数&#xff0c;且每次只输出符号集的一个消息 样本空间&…...

嵌入式硬件工程师从小白到入门-PCB绘制(二)

PCB绘制从小白到入门&#xff1a;知识点速通与面试指南 一、PCB设计核心流程 需求分析 明确电路功能&#xff08;如电源、信号处理、通信&#xff09;。确定关键参数&#xff08;电压、电流、频率、接口类型&#xff09;。 原理图设计 元器件选型&#xff1a;匹配封装、电压、…...

抽象工厂设计模式及应用案例

引言 在软件开发中&#xff0c;合理的设计模式可以有效地提高代码的可维护性、可扩展性和可重用性。抽象工厂模式&#xff08;Abstract Factory Pattern&#xff09;便是一个重要的创建型设计模式&#xff0c;它允许我们在不指定具体类的情况下&#xff0c;创建一系列相关或相…...

LVS NAT模式实现三台RS的轮询访问

节点规划: 配置RS&#xff1a; RS1-RS3的网关配置均为 192.168.163.8 配置RS1&#xff1a; [rootlocalhost ~]# hostnamectl hostname rs1 [rootlocalhost ~]# nmcli c modify ens160 ipv4.method manual ipv4.addresses 192.168.163.7/24 ipv4.gateway 192.168.163.8 conne…...

LSM-Tree(Log-Structured Merge-Tree)详解

1. 什么是 LSM-Tree? LSM-Tree(Log-Structured Merge-Tree)是一种 针对写优化的存储结构,广泛用于 NoSQL 数据库(如 LevelDB、RocksDB、HBase、Cassandra)等系统。 它的核心思想是: 写入时只追加写(Append-Only),将数据先写入内存缓冲区(MemTable)。内存数据满后…...

uni-app jyf-parser将字符串转化为html 和 rich-text

uni-app jyf-parser将字符串转化为html-CSDN博客 方法二&#xff1a; rich-text | uni-app...

Docker+Ollama+Xinference+RAGFlow+Dify部署及踩坑问题

目录 一、Xinference部署 &#xff08;一&#xff09;简介 &#xff08;二&#xff09;部署 &#xff08;三&#xff09;参数 &#xff08;四&#xff09;错误问题 &#xff08;五&#xff09;Xinference配置Text-embedding模型 &#xff08;六&#xff09;Xinference配…...

CentOS 7 搭建基于匿名用户的 FTP 服务

1. 安装 VSFTPD yum install vsftpd -y 2. 配置 VSFTPD 编辑主配置文 vi /etc/vsftpd/vsftpd.conf 以下配置项存在或修改为对应值 anonymous_enableYES # 启用匿名用户 local_enableNO # 禁用本地用户 write_enableYES # 允许写入&#xff08;若需要匿名上传&#xff0…...

【机器学习】什么是线性回归?

什么是线性回归&#xff1f; 线性回归是一种 监督学习算法&#xff0c;它通过拟合一个直线&#xff08;或平面&#xff0c;高维空间下是超平面&#xff09;来建立 输入特征 和 输出目标 之间的关系。简单来说&#xff0c;线性回归就是找出一个数学方程&#xff08;通常是线性方…...

零、ubuntu20.04 安装 anaconda

1.anaconda下载 地址&#xff1a;Index of /anaconda/archive/ | 清华大学开源软件镜像站 | Tsinghua Open Source Mirror 选择&#xff1a;Anaconda3-2023.07-2-Linux-x86_64.sh 2.anaconda安装 选择下载目录&#xff0c;选在在终端中打开&#xff0c;然后在终端输入安装命…...

Web纯前端实现在线打开编辑保存PPT幻灯片

很多项目中有时会需要在线打开PPT并编辑保存到服务器。猿大师办公助手可以完美调用本地office在线打开ppt文件&#xff0c;跟本地打开效果一样。还可以在线打开word、excel、pdf等文件&#xff0c;支持本机OFFICE完整嵌入模式&#xff0c;本机OFFICE所有功能基本都可以在网页上…...

LeetCode热题100精讲——Top3:最长连续序列【哈希】

你好&#xff0c;我是安然无虞。 文章目录 题目背景最长连续序列C解法Python解法 题目背景 如果大家对于 哈希 类型的概念并不熟悉, 可以先看我之前为此专门写的算法详解: 蓝桥杯算法竞赛系列第九章巧解哈希题&#xff0c;用这3种数据类型足矣 最长连续序列 题目链接&#x…...

vue2相关 基础命令

vue2 基础命令 vue简介&#xff0c;Vue 2 已于 2023 年 12 月 31 日停止维护。详见 Vue 2 终止支持 (EOL)。 安装完 Visual Studio Code后&#xff0c;打开项目目录&#xff0c; 在目录位置输入cmd&#xff0c;然后在命令行输入 code . 就可以在VScode打开项目。 公司的前后端…...

2025年渗透测试面试题总结-某 长亭(题目+回答)

网络安全领域各种资源&#xff0c;学习文档&#xff0c;以及工具分享、前沿信息分享、POC、EXP分享。不定期分享各种好玩的项目及好用的工具&#xff0c;欢迎关注。 目录 长亭 一、Spring SpEL表达式注入漏洞 1. 技术原理 2. 利用条件 3. 攻击方法 4. 防御策略 二、Jav…...

【web3】

检测钱包是否安装 方法一 // npm install metamask/detect-provider import detectEthereumProvider from metamask/detect-provider// 检测钱包是否安装 const isProvider await detectEthereumProvider() if(!isProvider) {proxy.$modal.msgError("请安装钱包")…...

Ubuntu部署Dufs文件服务器

安装dufs 安装cargo apt install cargo升级rust工具链 apt install rustup rustup update stable查看rust版本&#xff0c;需要>1.81 rustc --version安装dufs cargo install dufs将dufs加入环境变量 sudo vim ~/.bashrc export PATH"$HOME/.cargo/bin:$PATH" sou…...

速卖通API数据清洗实战:从原始JSON到结构化商品数据库

下面将详细介绍如何把速卖通 API 返回的原始 JSON 数据清洗并转换为结构化商品数据库。 1. 数据获取 首先要借助速卖通 API 获取商品数据&#xff0c;以 Python 为例&#xff0c;可使用requests库发送请求并得到 JSON 数据。 import requests# 替换为你的 API Key 和 Secret …...

前端模拟 websocket 请求小工具

背景&#xff1a; 后端写好websocket 接口后&#xff0c;用postman的某些版本无法直接模拟websocket请求&#xff0c;所以想着自制一个小工具。 使用方法&#xff1a; 直接复制以下代码到文本文件中&#xff0c;修改服务端端地址&#xff0c;保存为 .html的后缀名&#xff0c;…...

docker安装hyperf环境,连接本机redis问题处理

错误信息显示“Connection refused”&#xff0c;这通常说明 Docker 容器内的 Hyperf 项目无法连接到你本机的 Redis 服务。 1. 容器内的 127.0.0.1 指向问题 在 Docker 容器中&#xff0c;127.0.0.1 指的是容器本身&#xff0c;而不是宿主机&#xff08;你的 Mac&#xff09;…...