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

相关推荐
五彩小白20 小时前
自回归和上下文
android
事圆则缓1 天前
Android 多渠道打包实战:Product Flavors、签名、AAB 与发包校验
android
树下困觉眯1 天前
View绘制流程学习--获取WindowManager的三种方式
android·系统架构·view design
vilya1 天前
我怎么给手机 GUI Agent 做双通道感知:无障碍树为主,投屏像素兜底
android·人工智能
李子红了时1 天前
阿狸舞台APP(安卓端手机EncFS解密+压缩包解压+视频播放)
android·音视频
事圆则缓1 天前
Android 使用 Jenkins 实现 CI/CD,并用 SonarQube 建立质量门禁
android·ci/cd·jenkins
其实防守也摸鱼1 天前
DeepSeek Harness 开源贡献手记:从 Issue 到 Merge 的完整旅程
android·数据库·学习·ai·oracle·自动化
恋猫de小郭1 天前
Android CLI 支持 AI Agent 通过 Device Streaming 调试云真机
android·前端·flutter
素师良码2 天前
第5篇:显示驱动必备调试工具浅析
android
维克兜率天2 天前
【维克】配对交易的季节性:哪些品种适合长拿?
android·开发语言·笔记·python·算法·kotlin·量化