compositionLocalOf和staticCompositionLocalOf,你都用对了吗

1. 差别

特性 compositionLocalOf staticCompositionLocalOf
定义方式 普通的函数调用定义 val myCompositionLocal = compositionLocalOf { /*初始值提供者*/ } 伴生对象的静态方法定义 object MyStaticCompositionLocal { val key = staticCompositionLocalOf { /*初始值提供者*/ } }
内存管理 每次调用 compositionLocalOf 都会创建一个新的 CompositionLocal 实例 无论调用多少次 staticCompositionLocalOf,只会创建一个静态的 CompositionLocal 实例
访问方式 直接使用定义的 CompositionLocal 实例访问 通过伴生对象访问,如 MyStaticCompositionLocal.key

2. 使用场景

  • compositionLocalOf

    • 当你需要在不同的地方创建多个相似但独立的 CompositionLocal 时使用。例如,在不同的组件树分支中,可能需要不同的配置信息,每个分支可以创建自己的 compositionLocalOf 实例。
    • 用于局部范围的特定数据传递,这些数据可能在不同的部分有不同的值。
  • staticCompositionLocalOf

    • 当你有一个全局唯一的 CompositionLocal,在整个应用中都使用同一个实例时使用。例如,应用级别的配置信息,如全局的用户设置、应用主题等。
    • 适用于在多个不同的组件层次结构中共享相同的数据,且数据不需要在不同部分有不同值的情况。

3. 性能

  • compositionLocalOf:由于每次调用都会创建新实例,在频繁创建和销毁的场景下,可能会有一定的性能开销,因为涉及到对象的创建和内存分配。
  • staticCompositionLocalOf :因为只有一个静态实例,不存在频繁创建和销毁的问题,在性能上相对更优,特别是在应用的整个生命周期内都需要使用该 CompositionLocal 的场景。

4. 实现原理

  • compositionLocalOf

    • compositionLocalOf 是一个普通的函数,它接受一个 lambda 表达式作为初始值提供者。
    • 当在 Composition 中使用 CompositionLocal 时,Compose 运行时会根据当前的 Composition 上下文来查找对应的 CompositionLocal 的值。
    • 它通过 CompositionLocalProvider 来提供值,在 CompositionLocalProvider 的作用域内,所有使用该 CompositionLocal 的地方都会获取到指定的值。
  • staticCompositionLocalOf

    • staticCompositionLocalOf 是通过伴生对象的静态方法实现的。
    • 它同样依赖于 CompositionLocalProvider 来提供值,但由于是静态实例,在整个应用中只有一个,所以在不同的 Composition 上下文中,只要是通过相同的伴生对象访问,获取的都是同一个 CompositionLocal 实例。

5. 对比总结

compositionLocalOf 更灵活,适合创建多个独立的局部数据传递实例;而 staticCompositionLocalOf 更适合全局唯一实例的场景,在性能和使用方式上有一定优势。在实际开发中,需要根据具体的需求来选择合适的方式来定义和使用 CompositionLocal


相关话题


CompositionLocal 性能优化技巧

  • 合理选择定义方式

    • compositionLocalOf :适合值可能经常改变的场景,它会精准订阅,值改变时精准重组(Recompose),性能消耗主要在订阅阶段。例如在一个实时切换主题颜色的功能中,由于颜色值可能频繁变动,使用 compositionLocalOf 更合适 。
    • staticCompositionLocalOf :适用于值不会改变或几乎不会改变的场景,它不支持订阅,值改变时全量重组,性能消耗在更新阶段。如应用中全局的屏幕尺寸信息,基本不会变化,用 staticCompositionLocalOf 定义比较好。
  • 减少不必要的重组范围

    • 精准控制 CompositionLocalProvider 作用域 :仅在需要改变 CompositionLocal 属性值的组件及其子组件范围使用 CompositionLocalProvider。比如,只是某个特定的弹窗需要特殊的文本样式,那就将 CompositionLocalProvider 作用域限定在该弹窗组件内,避免影响其他无关组件的重组。
    • 避免深层嵌套导致的大范围重组 :如果有多层嵌套的组件结构使用 CompositionLocal,尽量将相关逻辑提取到更高层次的组件中,减少因底层组件变化导致的不必要向上传播重组。
  • 结合 remember 函数 :在使用 CompositionLocal 的组件中,如果有一些数据或状态是不依赖于 CompositionLocal 的变化而变化的,可以使用 remember 函数来缓存这些数据,防止因 CompositionLocal 变化引起的不必要重新计算。例如,某个组件根据 CompositionLocal 获取主题颜色来绘制背景,但同时有一个固定的图标显示,图标数据可以用 remember 缓存。

  • 使用稳定的数据类型作为 CompositionLocal 的值 :提供给 CompositionLocal 的数据类型尽量是稳定的(如 @Stable 注解的类型),这样当数据内容不变时,即使引用发生变化,Compose 也能识别出数据的稳定性,避免不必要的重组。


CompositionLocal 与状态管理

在 Jetpack Compose 中,CompositionLocal 与状态管理是构建灵活且高效 UI 的重要概念。

一、CompositionLocal

CompositionLocal 是通过组合隐式向下传递数据的工具,可在 Composable 视图树中共享数据。比如在处理公共 MaterialTheme 配置时,如果通过显示参数传递,当参数很多时,每个 Composable 的参数将很难维护,而使用 CompositionLocal 可轻松解决此问题。

(一)创建方式

  1. compositionLocalOf :用于创建一个 CompositionLocal 实例,允许对重组进行精细控制。当值发生变化时,只有读取该值的 UI 部分才会重组,适合频繁变化的数据,如动态主题或用户偏好。例如:val LocalName = compositionLocalOf { "默认值" }
  2. staticCompositionLocalOf :创建的实例在状态发生改变的时候,CompositionLocalProvider 中的 content 整体都会被重组,而不是限定在组合中读取内部 current 数值的 Composable。适用于很少变化的数据,如稳定的配置、在应用生命周期中保持不变的 API 端点、debug 标志或静态 UI 主题等。例如:val LocalAppConfig = staticCompositionLocalOf { AppConfig(apiBaseUrl = "https://api.dev.example.com", isAnalyticsEnabled = false) }

(二)使用步骤

  1. 创建 CompositionLocal 实例:按照上述两种方式创建。
  2. 为 CompositionLocal 提供值:通过 CompositionLocalProvider 可组合项将值绑定到给定层次结构的 CompositionLocal 实例。例如:
kotlin 复制代码
	CompositionLocalProvider(LocalName provides name) {

	    // 在此作用域内可使用 LocalName 的值

	    ShowMessage() 

	}
  1. 在需要的地方获取值 :在 Composable 函数中使用 CompositionLocalcurrent 属性获取值,如 val value = LocalName.current

二、状态管理

在 Compose 中,状态管理是构建交互性 UI 的关键。

(一)基本概念

应用中的状态是指可以随时间变化的任何值,如用户点击按钮发生的动画、Text 中的文字等。Compose 是声明式 UI,当 State 更新时,会发生重组。

(二)常用工具

  1. remember :Composable 可以使用 remember 来记住单个对象。系统会在初始化时将由 remember 计算的值存储在 Composable 中,并在重组的时候返回存储的值。例如:val rememberedValue = remember { "初始值" }

  2. mutableStateOf:创建可观察的 MutableState,value 有任何更改,系统会安排重组读取 value 的所有 Composable 函数。声明 MutableState 对象有三种方法:

    • val mutableState = remember { mutableStateOf("") }
    • var value by remember { mutableStateOf("") }
    • val (value, setValue) = remember { mutableStateOf("") }
  3. rememberSaveable:remember 虽然可以在重组后保持状态,但在应用配置更新(如屏幕旋转)时状态会重置,而 rememberSaveable 会帮助存储配置更改(重新创建 activity 或进程)时的状态。

(三)状态提升

在编写可复用组件时,为保证 Composable 本身无状态,应将状态移到 Composable 组件的调用者,这种操作叫做状态提升。例如,若一个 Composable 内部创建了状态,在调用者不需要控制和管理状态时可以内部维护,但考虑复用性时,应将状态提升到调用者。

(四)响应式框架与状态转换

安卓常用的 RxJava、Livedata、Flow 等响应式开发框架,都支持转化为 State 对象。例如将 Flow 对象转化为一个 State:val favorites = MutableStateFlow<Set<String>>(setOf()) val state = favorites.collectAsState()

三、CompositionLocal 与状态管理的关系

CompositionLocal 本身并不直接管理状态,但它可以与状态管理工具结合使用。例如,通过 CompositionLocal 传递状态数据,使得多个 Composable 可以共享和响应这些状态变化。同时,根据状态的变化情况,可以选择合适的 CompositionLocal 创建方式(compositionLocalOf 或 staticCompositionLocalOf)来优化性能。如果状态频繁变化,使用 compositionLocalOf 可减少不必要的重组;如果状态很少变化,使用 staticCompositionLocalOf 可提高性能。


CompositionLocal 与依赖注入框架的集成

在软件开发中,依赖注入(Dependency Injection)是一种实现组件解耦的设计模式,能让对象的依赖关系由外部提供,而非在内部创建,从而提高代码的可测试性、可维护性和可扩展性。而 CompositionLocal 是 Jetpack Compose 中用于在组合函数之间隐式传递数据的机制。将 CompositionLocal 与依赖注入框架集成,能充分发挥两者优势,构建更灵活、可维护的应用架构。

集成的优势

  • 简化数据传递 :在复杂的组件层次结构中,CompositionLocal 可避免数据通过多层组件层层传递参数的繁琐过程,实现数据的隐式传递。依赖注入框架则负责管理依赖的创建和提供,两者结合能让数据在合适的地方被轻松获取和使用。
  • 提高组件可测试性 :依赖注入框架使组件依赖易于替换,方便在测试环境中提供模拟依赖。CompositionLocal 提供的数据也可在测试时轻松修改,确保组件在不同数据条件下的行为符合预期。
  • 增强代码可维护性和可扩展性 :通过依赖注入框架管理依赖,代码结构更清晰,依赖关系更明确。CompositionLocal 提供的局部作用域数据传递,使代码修改和扩展时对其他部分的影响更小。

CompositionLocal 在大型项目中的实践

在大型项目中,合理运用 CompositionLocal 能够显著提升代码的可维护性与可扩展性。以下是一些常见的实践场景:

  • 主题管理 :在大型项目中,主题相关的配置(如颜色、字体、形状等)可能会在众多组件中被广泛使用。使用 CompositionLocal 可以方便地管理和传递这些主题信息。

    kotlin 复制代码
    	// 定义主题相关的 CompositionLocal
    
    	internal val LocalAppTheme = staticCompositionLocalOf {
    
    	    AppTheme(
    
    	        colors = lightColors(),
    
    	        typography = Typography,
    
    	        shapes = Shapes
    
    	    )
    
    	}
    
    	 
    
    	@Composable
    
    	fun MyApp() {
    
    	    // 提供主题值
    
    	    CompositionLocalProvider(LocalAppTheme provides AppTheme()) {
    
    	        // 应用主题的组件树
    
    	        MainContent()
    
    	    }
    
    	}
    
    	 
    
    	@Composable
    
    	fun MainContent() {
    
    	    // 直接访问主题
    
    	    val theme = LocalAppTheme.current
    
    	    // 使用主题配置
    
    	    Text(text = "Hello", color = theme.colors.primary)
    
    	}
  • 用户偏好设置 :用户的偏好设置(如语言、字体大小、显示模式等)也适合通过 CompositionLocal 来传递。这样,当用户更改设置时,相关组件可以自动更新。

    kotlin 复制代码
    	// 定义用户偏好的 CompositionLocal
    
    	internal val LocalUserPreferences = compositionLocalOf {
    
    	    UserPreferences(
    
    	        language = "en",
    
    	        fontSize = 16.sp,
    
    	        displayMode = "light"
    
    	    )
    
    	}
    
    	 
    
    	@Composable
    
    	fun SettingsScreen() {
    
    	    // 修改用户偏好值
    
    	    CompositionLocalProvider(LocalUserPreferences provides UserPreferences(language = "zh")) {
    
    	        // 应用新偏好的组件
    
    	        UpdatedContent()
    
    	    }
    
    	}
    
    	 
    
    	@Composable
    
    	fun UpdatedContent() {
    
    	    val preferences = LocalUserPreferences.current
    
    	    Text(text = "Settings updated to ${preferences.language}")
    
    	}
  • 数据共享与依赖注入 :在复杂的组件层次结构中,某些数据可能需要在多个层级的组件间共享。CompositionLocal 可以实现数据的隐式传递,减少组件间的显式参数传递。

    kotlin 复制代码
    	// 定义共享数据的 CompositionLocal
    
    	internal val LocalSharedData = compositionLocalOf {
    
    	    SharedData(
    
    	        value1 = "data1",
    
    	        value2 = 42
    
    	    )
    
    	}
    
    	 
    
    	@Composable
    
    	fun ParentComponent() {
    
    	    // 提供共享数据
    
    	    CompositionLocalProvider(LocalSharedData provides SharedData()) {
    
    	        ChildComponent()
    
    	    }
    
    	}
    
    	 
    
    	@Composable
    
    	fun ChildComponent() {
    
    	    val data = LocalSharedData.current
    
    	    Text(text = "Shared data: ${data.value1}, ${data.value2}")
    
    	}

注意事项

  • 性能优化 :根据数据的变化频率选择合适的创建方式。如果数据很少变化,使用 staticCompositionLocalOf 可以避免不必要的重组;如果数据频繁变化,compositionLocalOf 能更精准地控制重组范围。
  • 作用域管理 :确保 CompositionLocal 的作用域清晰明确,避免意外的覆盖或数据不一致。特别是在多层嵌套的 CompositionLocalProvider 中,要注意 current 引用的是最近一层父函数中绑定的值。
  • 代码可读性 :虽然 CompositionLocal 减少了显式参数传递,但过多的隐式数据传递可能会降低代码的可读性。因此,在使用时应添加清晰的注释,说明数据的来源和用途。

总之,合理运用 CompositionLocal 的不同类型,能有效提升 Jetpack Compose 开发中数据传递与管理的效率和灵活性。

相关推荐
我命由我123452 天前
Android 对话框 - 对话框全屏显示(设置 Window 属性、使用自定义样式、继承 DialogFragment 实现、继承 Dialog 实现)
android·java·java-ee·android studio·android jetpack·android-studio·android runtime
Jeled2 天前
Android 本地存储方案深度解析:SharedPreferences、DataStore、MMKV 全面对比
android·前端·缓存·kotlin·android studio·android jetpack
我命由我123453 天前
Android 开发问题:getLeft、getRight、getTop、getBottom 方法返回的值都为 0
android·java·java-ee·android studio·android jetpack·android-studio·android runtime
alexhilton8 天前
Kotlin互斥锁(Mutex):协程的线程安全守护神
android·kotlin·android jetpack
是六一啊i9 天前
Compose 在Row、Column上使用focusRestorer修饰符失效原因
android jetpack
用户0609052552210 天前
Compose 主题 MaterialTheme
android jetpack
用户0609052552210 天前
Compose 简介和基础使用
android jetpack
用户0609052552210 天前
Compose 重组优化
android jetpack
行墨10 天前
Jetpack Compose 深入浅出(一)——预览 @Preview
android jetpack