天天看点

compose 编程思想

前言

compose已经熟悉了几周了,跟着google sample进行理解时,对可组合函数参数列表如何声明,事件向上,数据向下传递理解还是有偏差。重新理解了下compose编程思想

1.早期基于View 微件数:

更新界面:多个需要同时更新View,会遗漏。复杂的View微件更新会更新前面已经移除过微件的状态

2.声明性范式:
tips:mvvm 为数据驱动界面ui更新。 界面(Fragment,Activity)订阅ViewModel中的LiveData,数据发生变化时通知界面进行刷新。而lifecycle系列避免发生订阅内存泄露而引入的辅助管理类。

compose:一个声明性界面框架,【完全重新生成一个屏幕】

声明性范式转变

View微件数:在许多面向对象的命令式界面工具包中,您可以通过实例化微件树来初始化界面。您通常通过Inflate XML 布局文件来实现此目的。每个微件都维护自己的内部状态,并且提供 getter 和 setter 方法,允许应用逻辑与微件进行交互。

Compose 的声明性方法中:微件相对无状态,并且不提供 setter 或 getter 函数,微件不会以对象形式提供,您可以通过调用带有不同参数的同一可组合函数来更新界面

界面描述:自上而下

compose 编程思想
应用逻辑为顶级可组合函数提供数据,该函数(顶级可组合函数)通过调用其他可组合函数来使用这些数据描述界面,将适当的数据传递给这些可组合函数,并沿层次结构向下传递数据

事件响应:自下而上

compose 编程思想

例如:触发onclick事件,这些事件应通知应用逻辑,应用逻辑随后可以改变应用的状态。当状态发生变化时,系统会使用新数据再次调用可组合函数。这会导致重新绘制界面元素,此过程称为“重组”。

单选按钮点击,应用逻辑将单选按钮状态由check变为checked。状态变化导致compose函数 重组

可组合函数内不建议缓存临时变量,包含匿名函数,除非特别简单。建议将状态和数据提取到可组合函数外面使可组合函数幂等

重组

1.切勿依赖于执行可组合函数所产生的附带效应,因为可能会跳过函数的重组

附带效应:对应用的其余部分可见的任何更改

写入共享对象的属性
更新 ViewModel 中的可观察项
更新共享偏好设置      

可组合函数可能会像每一帧一样频繁地重新执行,如果您需要执行成本高昂的操作(例如从共享偏好设置读取数据),请在[后台协程]中执行,并将值结果作为参数传递给可组合函数。

本文档讨论了您在 Compose 中编程时需要注意的事项:

可组合函数可以按任何顺序执行:

可组合函数均应该是幂等的,不应该依赖声明函数顺序,臆想程序执行顺序是序列化

可组合函数可以并行执行。

如果您看一下可组合函数的代码,可能会认为这些代码按其出现的顺序运行。但其实未必是这样。如果某个可组合函数包含对其他可组合函数的调用,这些函数可以按任何顺序运行。Compose 可以选择识别出某些界面元素的优先级高于其他界面元素,因而首先绘制这些元素。

例如,假设您有如下代码,用于在标签页布局中绘制三个屏幕:

@Composable
fun ButtonRow() {
    MyFancyNavigation {
        StartScreen()
        MiddleScreen()
        EndScreen()
    }
}      

对 StartScreen、MiddleScreen 和 EndScreen 的调用可以按任何顺序进行。这意味着,举例来说,您不能让 StartScreen() 设置某个全局变量(附带效应)并让 MiddleScreen() 利用这项更改。相反,其中每个函数都需要保持独立。

可组合函数可以并行运行

Compose 可以通过并行运行可组合函数来优化重组。这样一来,Compose 就可以利用多个核心,并以较低的优先级运行可组合函数(不在屏幕上)。

这种优化意味着,可组合函数可能会在后台线程池中执行。如果某个可组合函数对 ViewModel 调用一个函数,则 Compose 可能会同时从多个线程调用该函数。

为了确保应用正常运行,所有可组合函数都不应有附带效应,而应通过始终在界面线程上执行的 onClick 等回调触发附带效应。

调用某个可组合函数时,调用可能发生在与调用方不同的线程上。这意味着,应避免使用修改可组合 lambda 中的变量的代码,既因为此类代码并非线程安全代码,又因为它是可组合 lambda 不允许的附带效应。

以下示例展示了一个可组合项,它显示一个列表及其项数:

@Composable
fun ListComposable(myList: List<String>) {
    Row(horizontalArrangement = Arrangement.SpaceBetween) {
        Column {
            for (item in myList) {
                Text("Item: $item")
            }
        }
        Text("Count: ${myList.size}")
    }
}      

此代码没有附带效应,它会将输入列表转换为界面。此代码非常适合显示小列表。不过,如果函数写入局部变量,则这并非线程安全或正确的代码:

@Composable
@Deprecated("Example with bug")
fun ListWithBug(myList: List<String>) {
    var items = 0

    Row(horizontalArrangement = Arrangement.SpaceBetween) {
        Column {
            for (item in myList) {
                Text("Item: $item")
                items++ // Avoid! Side-effect of the column recomposing.
            }
        }
        Text("Count: $items")
    }
}      

在本例中,每次重组时,都会修改 items。这可以是动画的每一帧,或是在列表更新时。但不管怎样,界面都会显示错误的项数。因此,Compose 不支持这样的写入操作;通过禁止此类写入操作,我们允许框架更改线程以执行可组合 lambda。

重组会跳过尽可能多的可组合函数和 lambda。

重组是乐观的操作,可能会被取消:

只要 Compose 认为某个可组合项的参数可能已更改,就会开始重组。重组是乐观的操作,也就是说,Compose 预计会在参数再次更改之前完成重组。如果某个参数在重组完成之前发生更改,Compose 可能会取消重组,并使用新参数重新开始。

取消重组后,Compose 会从重组中舍弃界面树。如有任何附带效应依赖于显示的界面,则即使取消了组成操作,也会应用该附带效应。这可能会导致应用状态不一致。

可组合函数可能会像动画的每一帧一样非常频繁地运行
最佳做法都是使可组合函数保持快速、幂等且没有附带效应。