Jetpack Compose `@ReadOnlyComposable` 的“魔法”

什么是 @ReadOnlyComposable

在 Compose 中,普通 @Composable 函数不仅负责绘制 UI,还会参与 Composition 的状态管理。每次调用通常都会在内部 Slot Table 中创建一个新的 Group(组合节点),用于:

  • 跟踪函数调用位置
  • 管理状态
  • 支持 Recomposition
  • 实现 Skip(跳过未变化部分)

@ReadOnlyComposable 本质上是一种特殊优化,它向 Compose 编译器做出承诺:

"我只是读取上下文数据,不生成 UI,不创建新的组合节点,也不会产生副作用。"

常见场景:

kotlin 复制代码
@Composable
@ReadOnlyComposable
fun currentDensity(): Density {
    return LocalDensity.current
}

它只是读取 CompositionLocal 的值。

普通 @Composable 会发生什么

例如:

kotlin 复制代码
@Composable
fun UserThemeColor(): Color {
    return MaterialTheme.colorScheme.primary
}

编译器在背后大致会变成:

kotlin 复制代码
fun UserThemeColor(
    composer: Composer,
    changed: Int
): Color {

    composer.startReplaceableGroup(...)
    
    val color = MaterialTheme.colorScheme.primary

    composer.endReplaceableGroup()

    return color
}

这里:

  • startReplaceableGroup()
  • endReplaceableGroup()

会创建 Composition Group。这样 Compose 才能记录:

  • 当前函数位置
  • 状态依赖
  • 是否可以跳过重组
@ReadOnlyComposable 的变化

加上:

kotlin 复制代码
@Composable
@ReadOnlyComposable
fun UserThemeColor(): Color {
    return MaterialTheme.colorScheme.primary
}

编译器会省略 Group 创建:

kotlin 复制代码
fun UserThemeColor(
    composer: Composer,
    changed: Int
): Color {

    return MaterialTheme.colorScheme.primary
}
差异

普通 Composable:

kotlin 复制代码
startGroup()
读取状态
endGroup()

ReadOnlyComposable:

kotlin 复制代码
读取状态

少了:

  • Group 分配
  • Slot Table 写入
  • 重组跟踪成本

这就是它的性能收益来源

为什么 Compose 官方会使用它

在 Material3 源码里能看到类似:

kotlikn 复制代码
val ColorScheme.primary: Color
    @Composable
    @ReadOnlyComposable
    get() = LocalColorScheme.current.primary

原因:主题颜色可能被频繁访问:

kotlin 复制代码
Text(
    color = MaterialTheme.colorScheme.primary
)

Button(
    colors = ButtonDefaults.buttonColors(
        containerColor = MaterialTheme.colorScheme.primary
    )
)

如果每次都创建新的 Group:

  • Slot Table 会变大
  • 重组跟踪更多节点
  • 性能增加额外开销

@ReadOnlyComposable 避免了这些成本。

什么时候不能使用

下面这种是错误示例:

kotlin 复制代码
@Composable
@ReadOnlyComposable
fun Wrong() {
    Text("Hello")
}

原因:会创建 UI 节点。

scss 复制代码
Text()

再比如:

kotlin 复制代码
@Composable
@ReadOnlyComposable
fun Wrong() {
    val state = remember {
        mutableStateOf(0)
    }
}

原因:

remember

  • 写入 Slot Table
  • 创建状态节点

这违反了"只读"的承诺。

还有:

kotlin 复制代码
LaunchedEffect()
SideEffect()
DisposableEffect()

都不能放进去。

可以把它理解成什么

可以把:@Composable理解为:"我可能创建 UI,也可能管理状态。" 而@ReadOnlyComposable则是: "我只是读取 Compose 环境变量。" 它更像一个"零成本访问器(accessor)"。

实际开发建议

大部分业务代码几乎不需要自己写 @ReadOnlyComposable

适合的场景非常少:

  • 读取 CompositionLocal
  • 读取主题数据
  • 读取 Density
  • 读取 LayoutDirection
  • 读取 Context

例如:

less 复制代码
@Composable
@ReadOnlyComposable
fun screenWidth(): Dp {
    return LocalConfiguration.current.screenWidthDp.dp
}

不适合:

  • UI 组件
  • remember
  • Effect API
  • 状态修改
  • 动画逻辑

Compose 官方也主要把它用于框架内部的微优化,而不是日常业务开发

相关推荐
AFinalStone20 小时前
Android12 U盘插拔链路源码全解析(八)实战调试与案例分析
android·frameworks
我命由我1234520 小时前
Android 开发问题:View 的 getWidth、getHeight 方法返回的值都为 0
android·java·java-ee·android studio·android jetpack·android-studio·android runtime
JohnnyDeng941 天前
【Android】Hilt 依赖注入:原理与最佳实践
android·kotlin·mvvm·hilt
星间都市山脉1 天前
Android STS(Security Test Suite)完整介绍与测试流程
android·java·linux·windows·ubuntu·android studio·androidx
Yeyu1 天前
你真的了解AIDL吗? 附:AIDL 与 Binder 交互全解析
android
dualven_in_csdn1 天前
一键起飞调用示例
android·java·javascript
故渊at1 天前
第十板块:Android 系统稳定性与调试 | 第二十五篇:Watchdog 与 ANR 的系统级监控
android·watchdog·系统稳定性·anr·超时监控
故渊at2 天前
第十板块:Android 系统稳定性与调试 | 第二十六篇:Systrace 与 Perfetto 的系统级性能分析
android·perfetto·性能分析·systrace·系统稳定性
吕工-老船长19982 天前
20260610----S905Y5(Android14)-----连接网络自动更新时间,时间设置为24小时
android