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 官方也主要把它用于框架内部的微优化,而不是日常业务开发

相关推荐
郑州光合科技余经理3 小时前
国际版外卖系统:税率字段怎么和订单主流程解耦
android·java·开发语言·前端·后端·php·ai编程
andyweike8 小时前
Android Automotive
android
又见情义9 小时前
记一次 RK3568 Android 13 蓝牙反复重启问题的排查与修复
android
hai_android9 小时前
Kotlin Flow combine 源码剖析:一个 Channel 如何优雅合并多条流
android·java·kotlin
敲代码的瓦龙10 小时前
Jetpack?ViewModel!!!
android·开发语言·kotlin·android-studio
Godikov11 小时前
Android 工控终端实战:从秒级卡顿到毫秒响应,SQLite/LitePal 性能优化全记录
android
星间都市山脉13 小时前
Android16 SystemService.onBootPhase 调用时机
android·java·linux·windows·ubuntu
swithun13 小时前
不用 WebView,我用 Kotlin + Compose Multiplatform 重写 Mermaid,并做了 2048 组对拍
android·开源·kotlin
AI吃大瓜13 小时前
人脸检测和行人检测4:Android实现YOLOv8 YOLO11 YOLO26人脸检测和人体检测(含源码,可实时检测)
android·yolo·人脸检测·人体检测·行人检测·yolo26
与海boy13 小时前
Prompt模版
android·prompt