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 开发中数据传递与管理的效率和灵活性。

相关推荐
peakmain915 小时前
Jetpack Compose UI组件封装(一)
android jetpack
alexhilton3 天前
实战:探索Jetpack Compose中的SearchBar
android·kotlin·android jetpack
顾林海3 天前
Jetpack Pager 使用与原理解析
android·android jetpack
每次的天空5 天前
Android Jetpack学习总结(源码级理解)
android·学习·android jetpack
顾林海5 天前
Jetpack Room 使用与原理解析
android·android jetpack
ljt27249606617 天前
Compose笔记(十三)--事件总线
笔记·android jetpack
我命由我123457 天前
Android Gradle 插件问题:The option ‘android.useDeprecatedNdk‘ is deprecated.
android·java·java-ee·android studio·android jetpack·android-studio·android runtime
Mr_万能胶8 天前
要失业了!写在 Android “不再开源”之后
android·android studio·android jetpack
QING6189 天前
Android Jetpack Paging 使用指南
kotlin·app·android jetpack
顾林海9 天前
Jetpack DataBinding 使用与原理解析
android·android jetpack