• Android Jetpack Compose之UI的重组和自动刷新


    1.概述

    我们都知道,在传统的View中,若要改变UI,需要我们修改View的私有属性,比如要修改一个TextView的文字,我们需要通过它的setText(“xxx”)方法去修改。而Compose 则是通过重组来刷新UI。在之前的状态管理的文章中也提到过重组的概念。本章主要就是介绍Compose的重组和刷新相关的内容

    2.Compose智能重组

    compose的重组是很智能的,当重组发生的时候,只有状态发生改变的Composable函数才会参与重组,没有变化的Composable则会跳过本次重组。例如在计数器的demo中:

    class ComposeCounterAct : ComponentActivity() {
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContent {
                MyComposeTheme {
                    // A surface container using the 'background' color from the theme
                    Surface(
                        modifier = Modifier.fillMaxSize(),
                        color = MaterialTheme.colorScheme.background
                    ) {
                        CounterComponent()
    
                    }
                }
            }
        }
    
        @Composable
        fun CounterComponent() {
            Column(modifier = Modifier.padding(16.dp)) {
                var counter by remember { mutableStateOf(0) }
                Text(
                    "$counter",
                    Modifier.fillMaxWidth(),
                    textAlign = TextAlign.Center
                )
    
                Row {
                    Button(
                        onClick = { counter-- },
                        modifier = Modifier.weight(1f)
                    ) {
                        Text("-")
                    }
    
                    Spacer(Modifier.width(16.dp))
                    Button(
                        onClick = { counter++ },
                        modifier = Modifier.weight(1f)
                    ) {
                        Text("+")
                    }
                }
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46

    当点击Button按钮的时候,counter的状态变化会触发整个Coloum范围的重组。重组过程中显示计数器值的文字组件会被赋予新的counter值,以显示更新后的数字。但是如果此时有另外一个不依赖counter状态的文字组件,则它是不参与重组的,因为Compose编译器会在编译期间插入相关的比较代码,这些比较代码会让没有依赖变更的状态的组件,在对应状态更新时,不参与重组。

    有读者可能会疑惑,在Button的onclick方法中也依赖了counter,那它会发生重组吗?答案是不会,因为重组只会在Composable函数中进行,而onClick并非是一个Composable函数,所以和重组无关。此外,Button控件也没有依赖counter状态,所以也不会参与重组

    3.避免掉入重组的“坑”

    在前面的文章中我们提到过,Composable在编译期间代码会发生变化,所以代码的实际运行情况可能并不如我们预期的那样。所以我们需要了解下Composable在重组执行时的一些特性,避免掉入重组的"坑”。所以我们需要理解掌握下面的内容:

    3.1 Composable函数的执行顺序不固定

    当我们的代码中出现多个Composable函数时,他们并不一定会按照代码中出现的顺序执行,比如在Navigation中处于Stack最上方的UI会优先被绘制,在一个Box布局中处于前景的UI会具有较高的优先级,因此Composable函数会根据优先级执行。例如:

       @Composable
        fun ButtonRow(){
            NavigationBar { 
                StartScreen()
                MiddleScreen()
                EndScreen()
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    如上面的代码所示,在代码中ButtonRow函数一次调用了 StartScreen()、MiddleScreen()、 EndScreen()三个方法,,我们不能预设这三个方法一定是按照顺序执行的。也不要试图去在StartScreen中添加一个全局变量,然后在MiddleScreen()中获取到这个变化,这种关联被称作副作用,在Web前端开发的Vue中也有这个概念。副作用会让我们的UI状态混乱,而且不易维护,所以我们编写Compose时应该尽量避免副作用。

    3.2 Composable 会并发执行

    Composable在进行重组时不一定执行在主线程中,他们可能在后台线程池中并行执行,这样有利于发挥多核处理器的性能优势,但是由于多个composable在同一时间执行在不同线程,此时必须要注意考虑线程安全的问题。如下面例子所示来自于《Jetpack Compose 实战》一书:

     @Composable
        fun EventsFeed(localEvents:List,nationalEvent:List){
            var totalEvents = 0
            Row{
                Column { // 注释1
                    localEvents.forEach { 
                        event -> Text("Item: ${event.name}") 
                        totalEvents ++
                    }
                }
                Spacer(modifier = Modifier.height(10.dp))
                Column { // 注释2
                    nationalEvent.forEach { 
                        event -> Text("Item: ${event.name}") 
                        totalEvents ++
                    }
                }
                
                Text(
                    if(totalEvents == 0) "No Events." else 
                        "Total events $totalEvents"
                )
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    在上面的例子中想通过totalEvents记录events的数量并在Text上显示,但是注释1和注释2 处的Column代码有可能在不同的线程中并行执行,所以就导致了totalEvents的累加是非线程安全的,结果可能是不正确的。即使totalEvents的结果正确,但是由于Text可能运行在单独线程,所以也不一定能显示正确的结果,所以这里还是副作用带来的问题,需要我们避开。

    3.3 Composable会反复的执行

    除了重组会造成Composable的再次执行外,在动画等场景中每一帧的变化都可能引起Composable的执行。所以Composable在短时间内可能会反复的执行,而且我们无法准确的判断它的执行次数。因此我们必须考虑到:即使多次执行也不应该出现性能问题,更不能对外部产生额外有害的影响。同样。借用书中的例子:

     @Composable
        fun EventsFeed(netWorkService: EventsNetWorkService) {
            val events = netWorkService.loadAllEvents()
            LazyColumn {
                items(events) { event ->
                    {
                        Text(text = event.name)
                    }
                }
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    如上面代码所示,在方法EventsFeed中,loadAllEvents是一个IO操作,是一个耗时操作,执行的成本高如果在Composable中同步调用,那么在重组时会造成严重的卡顿问题。所以我们编写代码的时候需要注意Composable会反复的执行,编写UI的时候需要时刻注意这一点

    3.4.Composable的执行时“乐观”的

    所谓的“乐观”是指Composable最终总会依据最新的状态正确地完成重组。某些场景下,UI状态可能会连续的发生变化,这可能会导致中间态的重组在执行中被打断,新的重组会插入进来。对于被打断的重组,Composable不会将执行一般的重组结果反馈到视图树上,因为它 知道最后一次状态总归是正确的,所以中间的状态丢失了也不影响。

    4 总结

    本节介绍了Compose的智能重组和刷新,以及重组过程中可能会掉入的“坑”,这些坑我们需要去属性它,因为Compose框架要求Composable作为一个无副作用的纯函数运行,我们只要在开发的过程中遵循这一原则,那么重组中的“坑”就不会再是坑,而是我们提高程序执行性能的有效方法。


    为了帮助大家更好的熟知Jetpack Compose 这一套体系的知识点,这里记录比较全比较细致的《Jetpack 入门到精通》(内含Compose) 学习笔记!!! 对Jetpose Compose这块感兴趣的小伙伴可以参考学习下……

    Jetpack 全家桶(Compose)

    Jetpack 部分

    1. Jetpack之Lifecycle
    2. Jetpack之ViewModel
    3. Jetpack之DataBinding
    4. Jetpack之Navigation
    5. Jetpack之LiveData

    Compose 部分
    1.Jetpack Compose入门详解
    2.Compose学习笔记
    3.Compose 动画使用详解

  • 相关阅读:
    nginx源码安装
    超详细 | 实验室linux服务器非root账号 | 安装pip | 安装conda
    创建大量栅格文件并分别写入像元数据:C++ GDAL代码实现
    WiFi连接满格信号但是不能上网?
    火山引擎 DataTester 应用故事:一个A/B测试,将产品DAU提升了数十万
    概率论与数据统计学习:随机变量(一)——知识总结与C语言案例实现
    半导体CIM系统中的EAP:提升制造效率的关键
    WebFlux如何编写异步的代码
    网站服务器怎么部署
    【操作系统】读写锁实现&&读写锁原理(亲测可用)
  • 原文地址:https://blog.csdn.net/weixin_61845324/article/details/133173333