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

相关推荐
诸神黄昏EX2 小时前
Android Build系列专题【篇七:VINTF源码解析】
android
plainGeekDev2 小时前
Android Framework 面试题:Binder都说不清楚,简历别写精通了
android·java
萌新杰少2 小时前
安卓原生项目迁移KMP——核心迁移
android·kotlin·jetbrains
小孔龙2 小时前
AndroidManifest.xml 配置速查手册
android
七牛云行业应用2 小时前
OpenAI Codex手机版上线实战:iOS/Android 5步配置远程控制指南(2026)
android·ios·智能手机
背包客(wyq)3 小时前
YOLO手势检测识别模型Android端部署测试
android·yolo
peakmain93 小时前
基于 Hilt 实现 Android 网络库可插拔替换 Skill
android·架构·ai编程
黄林晴3 小时前
Google I/O 2026 Android开发者速览
android·android studio
DogDaoDao4 小时前
Android 播放器开发:从零构建全功能视频播放器
android·ffmpeg·音视频·播放器·mediacodec·编解码