Compose 学习界面架构 - 生命周期

部署运行你感兴趣的模型镜像

        小白参考 Android Compose 官方文档的一些学习笔记,如有错误恳请指正。

        官方文档链接:Compose 官方文档

可组合项的生命周期

一、可组合项是什么

        可组合项 Compose 中用于描述界面元素的基础单元,本质是被 @Composable 注解标记的函数。核心作用是 “描述界面应该是什么样子”,而非像 xml 中使用 findViewById 操作 UI 组件。

二、可组合项的生命周期

        可组合项的生命周期只有三个关键阶段:

  1. 进入组合(Enter the Composition):当 Compose 首次执行这个可组合项时,它会把这个组件加入到 “组合树” 里,相当于组件 “出生”,开始在界面树中占据位置。
  2. 执行 0 次或多次重组(Recompose):当应用的状态(比如按钮点击、数据更新)发生变化时,Compose 会判断这个可组合项是否需要重新执行来更新界面,这就是 “重组”。如果组件不依赖变化的状态,可能一次重组都不会执行;如果依赖,就会多次更新,相当于组件在 “工作中调整自己”。
  3. 退出组合(Leave the Composition):当组件不再需要(比如页面关闭、条件判断不显示该组件),就会从组合树中移除,相当于组件 “消失”,生命周期结束。

        所以,在 Compose 中如果一个组件一旦定义,想要修改只能通过重组更新,没有其它方式可以修改。而重组通常是由 State<T> 类型的状态变化触发的,比如 mutableStateOf 包裹的值变了,Compose 就会触发所有依赖这个状态的可组合项的重组。

@Composable
fun MyComposable() {
    Column {
        Text("Hello")
        Text("World")
    }
}

        另外,如果一个可组合项被多次调用,那么每个调用都会在组合树里生成一个独立的实例,每个实例都有自己的生命周期,互不干扰。比如在 Column 里放了两个 Text,它们就是两个独立的可组合项实例,一个的状态变化不会影响另一个的生命周期。

三、组合树中的身份标识:调用点与实例

        在上面例子中,一个可组合项被多次调用,Compose 是怎么区分不同可组合项实例的?如果某一个可组合项实例发生更改的话,是如何精准定位到该发生更改的实例使其重组?这里的核心就是:调用点 (调用可组合项的源代码位置)。

@Composable
fun LoginScreen(showError: Boolean) {
    if (showError) {
        LoginError()
    }
    LoginInput() 
}

@Composable
fun LoginInput() { /* ... */ }

@Composable
fun LoginError() { /* ... */ }

        例如,在 LoginScreen 里有个条件判断:当 showError 为 true 时显示 LoginError,而 LoginInput 始终显示。当 showError 从 false 变成 true 时,LoginError 会 “进入组合”,但 LoginInput 的调用点没变,所以它的实例会被保留不会重新创建,如果 LoginInput 的任何参数都没有变化,Compose 将避免重组这些可组合项,即跳过 LoginInput 的重组。

        如果从同一个调用点多次调用某个可组合项,Compose 就无法唯一标识对该可组合项的每次调用,因此除了调用点之外,还会使用执行顺序来区分实例,即:“调用点 + 执行顺序” 来区分实例。

@Composable
fun MoviesScreen(movies: List<Movie>) {
    Column {
        for (movie in movies) {
            MovieOverview(movie)
        }
    }
}

同一个调用点多次调用某个可组合项会出现一个问题:

        执行顺序的局限性

        如果在列表顶部或中间新增内容,移除项目或对项目进行重新排序而导致 movies 列表发生改变,将导致输入参数在列表中的位置已更改的所有 MovieOverview 调用发生重组。比如在列表顶部新增一个电影导致 movies 列表发生改变,原来第 1 位的 MovieOverview 会变成第 2 位,执行顺序变了,Compose 会认为这是一个 “新实例”,原来的实例会被销毁再重建。如果 MovieOverview 里有耗时的操作,例如加载图片等,就会导致加载被取消、重新开始。

        但是在列表底部新增了一个 movie 就不会出现这样的问题,Compose 可以重复使用组合中既有的实例,因为这些实例在列表中的位置没有发生变化,因此这些实例的 movie 输入是相同的。

        为了使 movies 列表即使发生了变化顺序被改变,Compose 也可以复用组合中已有的实例,不要全部销毁再重建的解决办法:用 key 给实例 “唯一身份证”

@Composable
fun MoviesScreenWithKey(movies: List<Movie>) {
    Column {
        for (movie in movies) {
            // 用电影的唯一 id 作为 key
            // key 的值不必是全局唯一,只需要在调用点处调用可组合项的作用域内确保其唯一性即可。
            key(movie.id) {
                MovieOverview(movie)
            }
        }
    }
}


// LazyColumn 这样的常用可组合项已经内置了 key 支持:
@Composable
fun MoviesScreenLazy(movies: List<Movie>) {
    LazyColumn {
        items(movies, key = { movie -> movie.id }) { movie ->
            MovieOverview(movie)
        }
    }
}

        这样给每个实例一个唯一标识 movie.id 不管列表怎么排序、增删,只要 key 不变,Compose 就认为是同一个实例,不会重新创建。

四、性能优化 - 跳过重组

        Compose 有个很智能的优化逻辑:如果可组合项的输入参数没变化,就会跳过它的重组,避免不必要的计算,提升性能。但不是所有可组合项都能被跳过,需要满足一定条件,也和输入参数的 “稳定性” 密切相关。

1. 可组合项能被跳过的条件

只有满足以下所有条件,Compose 才会在输入不变时跳过重组:

  • 可组合项的返回值类型是 Unit(大多数 UI 可组合项都是如此,比如 TextColumn);
  • 没有被 @NonRestartableComposable 或 @NonSkippableComposable 注解标记;
  • 所有必需参数的类型都是 “稳定类型” (val)。

2. 什么是 “稳定类型”?

稳定类型要符合三个协定,简单说就是 “可预测、能通知、全稳定”:

  • 相同的两个实例,调用 equals() 方法的结果永远一致(比如 Int 类型,1 和 1 永远相等);
  • 如果类型的公共属性变了,Compose 能收到通知(比如 MutableState,它的 value 变了会触发重组);
  • 该类型所有的公共属性类型也都是稳定类型。

Compose 会默认把以下这些类型当作稳定类型,不用我们额外处理:

  • 所有基本数据类型:BooleanIntLongFloatChar 等;
  • 字符串(String),因为它是不可变的;
  • 所有函数类型(lambda),同样是不可变的;
  • MutableState 类型,虽然它是可变的,但 value 变化时能通知 Compose,所以被视为稳定。

而像接口(比如自定义的 UiState 接口)、有可变公共属性的类(比如一个 User 类有 var name: String),Compose 默认会认为是 “不稳定类型”,即使输入没变化,也可能触发重组。

3. @Stable 注解

如果我们自定义的类型明明稳定协定,但 Compose 无法自动推断(比如接口),或者可以保证输入的数据是不变的,就可以用 @Stable 注解手动标记,告诉 Compose“这个类型是稳定的,放心用它来判断是否跳过重组”。比如下面的 UiState 接口,虽然是接口,但它的属性满足稳定条件,加了 @Stable 后,Compose 会把它当作稳定类型处理:

@Stable
interface UiState<T : Result<T>> {
    val value: T?
    val exception: Throwable?

    val hasError: Boolean
        get() = exception != null
}

        注意:加 @Stable 注解是 “承诺” 该类型符合稳定协定,所以一定要确保代码逻辑满足条件,否则可能导致界面更新异常。

五、可组合项生命周期总结

1. 生命周期核心

  • 可组合项生命周期:进入组合 → 0 次 / 多次重组 → 退出组合;
  • 重组触发源:State<T> 类型状态变化;
  • 多实例规则:同一可组合项可多次调用生成多个独立实例,各有自己的生命周期。

2. 组合树实例识别

  • 基础标识:调用点(源代码位置);
  • 重复调用问题:同一调用点多次调用时,默认用 “调用点 + 执行顺序” 标识,列表变化易导致实例重建;
  • 解决方案:用 key 可组合项(或 LazyColumn 内置 key),通过唯一标识(如 movie.id)稳定实例身份,避免不必要的重组和效应重启。

3. 重组优化(输入不变则跳过)

  • 可跳过条件:返回值 Unit、无特殊注解、参数类型稳定;
  • 稳定类型:默认包含基本类型、String、lambda、MutableState;自定义类型符合协定可加 @Stable 手动标记;
  • 优化逻辑:参数为稳定类型且值未变时,Compose 跳过重组,提升性能。

您可能感兴趣的与本文相关的镜像

Stable-Diffusion-3.5

Stable-Diffusion-3.5

图片生成
Stable-Diffusion

Stable Diffusion 3.5 (SD 3.5) 是由 Stability AI 推出的新一代文本到图像生成模型,相比 3.0 版本,它提升了图像质量、运行速度和硬件效率

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值