Compose: 如何减少无效的重组

64 阅读3分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第11天,点击查看活动详情

recomposable.png

大家都知道Compose最主要的地方就是声明式的UI,使用状态管理UI。这个最主要的实现就是重组函数了,Compose里面自己实现何时调用重组函数,调用那个重组函数,但是在使用的时候如果我们使用不当,也会造成无用的重组。下面举几个例子:

  • Screen所有的组件写在一个compose函数里面

如果把所有的组件都写在一个组件里面,那边当其中一个组件需要重组的时候,那么就需要把当前重组函数所有的非重组的地方都会走一遍,这对于重组其实是一种浪费。例如如下的代码:

 Column {
   var count by remember {
       mutableStateOf(0)
   }
   var expanded by remember {
       mutableStateOf(false)
   }
   Text(text = "计数器:$count")
   Button(onClick = { count++ }) {
       Text(text = "累加计数器")
   }
   Box(modifier = Modifier.fillMaxSize()){
       Log.i("test","drop down widget")
      Text(text = "drop down", modifier = Modifier.clickable { expanded = !expanded })
       DropdownMenu(expanded = expanded, onDismissRequest = { /*TODO*/ }) {
           DropdownMenuItem(onClick = { /*TODO*/ }) {
               Text(text = "Item 1")
           }
           DropdownMenuItem(onClick = { /*TODO*/ }) {
               Text(text = "Item 2")
           }
       }
   }

}

在这段代码里面第一是点击计数的button,会累计count的值,同时text的文本也会发生变化。另外一个功能就是box里面点击text会展示dropdown list。这个其实是两个功能,但是这当点击累计加数器按钮的时候,其实box里面的“drop down widget”也会走,那么其实这个时候就重组了没有变化的部分,同理如何点击显示dropdown按钮的时候也会再把上面没有变化的部分走一遍。这个其实是一个浪费。对于这种情况就需要把这两个composable函数,这样两部分的重组互相不影响,减少不必要的重组。

  • List重组问题

先看看官方的说法:

  1. 当item添加到list尾部时:

Screen Shot 2022-12-05 at 9.23.46 PM.png

那么只会重组最后添加的一个,之前添加的元素不会变。

  1. 添加到头部时:

Screen Shot 2022-12-05 at 9.25.18 PM.png

之前所有的元素都进行了重组。 我们使用下面的实例代码进行一下验证:

var listdata = remember {
    mutableStateListOf<String>().apply {
        add("item 1")
        add("item 2")
        add("item 3")
    }
}
Column {
    listdata.forEach { itemData ->
        ListItem(text = itemData)
    }
    LaunchedEffect(key1 = Unit ){
        var count = 3
        while (true){
            delay(5000)
            Log.i("test","add to end")
            listdata.add("item $count")
            delay(5000)
            count++
            Log.i("test","add to head")
            listdata.add(0,"item $count")
        }
    }
}

@Composable
fun ListItem(text:String){
    Log.i("test","start display text:$text")
    Text(text = text)
}

这个代码初始有3个item,在LaunchedEffect里面等待5s之后在队尾插入一个元素,再过5s在对头添加一个元素,我看看log打印的结果:

2022-12-05 21:22:14.173 I/test: start display text:item 1
2022-12-05 21:22:14.177 I/test: start display text:item 2
2022-12-05 21:22:14.177 I/test: start display text:item 3
2022-12-05 21:22:19.233 I/test: add to end
2022-12-05 21:22:19.252 I/test: start display text:item 3
2022-12-05 21:22:24.235 I/test: add to head
2022-12-05 21:22:24.252 I/test: start display text:item 4
2022-12-05 21:22:24.255 I/test: start display text:item 1
2022-12-05 21:22:24.256 I/test: start display text:item 2
2022-12-05 21:22:24.258 I/test: start display text:item 3

从这个log可以看出,当添加到队尾的时候只有最后添加的元素才会重新打印,而当添加到对头的时候,所有的元素都打印了一遍,所有当插入队头或者中间的任何位置都会引起不必要的重组,那这个需要怎么处理呢,官网给的方法是添加一个key。key 的值不必是全局唯一的,只需要在调用点处调用可组合项的作用域内确保其唯一性即可。下面我们在上面的例子之上添加一个key:

listdata.forEach { itemData ->
    key(itemData.id){
        ListItem(text = itemData.text)
    }
}

数据结构做的调整,这里就不再贴代码了,现在看看打印结果:

2022-12-05 21:35:28.989  I/test: start display text:item 1
2022-12-05 21:35:28.993  I/test: start display text:item 2
2022-12-05 21:35:28.994  I/test: start display text:item 3
2022-12-05 21:35:34.073  I/test: add to end
2022-12-05 21:35:34.084  I/test: start display text:item 4
2022-12-05 21:35:39.076  I/test: add to head
2022-12-05 21:35:39.083  I/test: start display text:item 5

现在可以看出,当插入头的时候不会再去多次的不必要的重组了。今天就分享到这里, 今天还遇到一个奇怪的问题,当我给mutablestate里面添加ArrayList类型的时候,然后给ArrayList里面添加元素却不会触发重组,这个不知道为什么,有人遇到过这样的问题吗,其实state里面提供了一个木tablestatelist这个可以兼并list的功能,也很好用,大家可以尝试一下。