• Jetpack Compose 性能优化的几条建议


    关于 LazyColumn 优化性能的几条建议

    第一个就是列表中如果有加载图片,那么大图一定要压缩!一定要重采样!不要几M几十M的图直接放到app里面加载!那样会卡爆!会内存OOM!

    我们可以使用三方的图片加载库,如coil,因为图片加载库一般会缓存,同一张图片不会重复加载第二次,还可以根据尺寸压缩等。

    关于 coil 图片加载库的使用,请参考 Jetpack Compose中的Accompanist 一文中有介绍

    第二个,为 LazyColumnitems() 方法显示的指定一个 key ,可以优化列表的加载性能,避免多余的重组。尤其是对于长列表而言性能更佳。

    LazyColumn(
        modifier = Modifier.fillMaxSize(),
        contentPadding = PaddingValues(16.dp),
        verticalArrangement = Arrangement.spacedBy(16.dp)
    ) {
    
        items(items = images, key = { it.id }) { image ->
            ImageDetails(image)
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    这里的 key 需要指定一个唯一的id,如果列表的业务数据没有提供一个唯一的id,可以使用UUID生成一个:

    data class MyImage(
        val resId: Int, 
        val title: String, 
        val tags: List<String>,
        val id: String = UUID.randomUUID().toString() // 自己生成一个唯一id
    )
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    第三个,可以为数据类添加@Immutable以提高性能,这样编译器会认为它是稳定的类型,从而可以实施智能重组、跳过重组等特性。

    @Immutable
    data class MyImage(
        val resId: Int,
        val title: String,
        val tags: List<String>,
        val id: String = UUID.randomUUID().toString()
    )
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    上面代码如果不加@Immutable,我们在 Layout Inspector 中观察列表快速滑动的时候,会发现列表出现红色闪烁,这说明发生了重组。因为其中的 val tags: List不是一个稳定的类型,因为编译器不知道它会如何实现。@Immutable向编译器承诺:我是一个稳定的类型。加了它之后,在 Layout Inspector 中就可以看到重组次数为0,跳过重组的次数有很多(本该重组的次数)。

    使用 @Stable 优化重组

    来看一个简单示例:

    class MyActivity: ComponentActivity() { 
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContent {
                MyComposeApplicationTheme {
                    var selected by remember { mutableStateOf(false)}
                    Column {
                        Checkbox(
                            checked = selected,
                            onCheckedChange = { selected = it }
                        )
                        ContactsList(
                            isLoading = false,
                            names = listOf("Peter")
                        )
                    }
    
                }
            }
        }
    
        @Composable
        fun ContactsList(
            isLoading: Boolean,
            names: List<String> // 问题在这里
        ) {
            Box(contentAlignment= Alignment.Center) {
                if (isLoading) {
                    CircularProgressIndicator()
                } else {
                    Text(names.toString())
                }
            }
        }
    }
    
    • 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

    上面代码中同样是由于names: List不是一个稳定的类型,会导致重组。当我们点击 Checkbox 时,不仅 Checkbox 自身会重组,而且不读取selected 状态值的 ContactsList 也会重组。如果我们在 Layout Inspector 中观察,可以明显看到这个效果:

    在这里插入图片描述

    解决办法是将不稳定类型包装进一个数据类,并为其添加@Stable注解,代码修改后如下:

    class MyActivity: ComponentActivity() {  
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContent {
                MyComposeApplicationTheme {
                    var selected by remember { mutableStateOf(false)}
                    Column {
                        Checkbox(
                            checked = selected,
                            onCheckedChange = { selected = it }
                        )
                        ContactsList(
                            state = ContactListState(
                                isLoading = false,
                                names = listOf("Peter")
                            )
                        )
                    }
    
                }
            }
        }
    
        @Composable
        fun ContactsList(
            state: ContactListState
        ) {
            Box(contentAlignment= Alignment.Center) {
                if (state.isLoading) {
                    CircularProgressIndicator()
                } else {
                    Text(state.names.toString())
                }
            }
        }
    
        @Stable
        data class ContactListState(
            val isLoading: Boolean,
            val names: List<String>
        )
    }
    
    • 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

    这时如果我们再次在 Layout Inspector 中观察,就可以看到与之前不同的效果:

    在这里插入图片描述

    这次可以明显的看到,当我们点击 Checkbox 时,只有 Checkbox 自身会重组,ContactsList 不会再重组了,并且我们可以看到 Checkbox的重组次数是 5,而 ContactsList 跳过重组的次数是 5

    当然这里你也可以使用 @Immutable 来注解数据类,目前它跟@Stable没有太大的区别。

    另一个解决方法是使用 ImmutableList,像下面这样:

    class MyActivity: ComponentActivity() { 
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContent {
                MyComposeApplicationTheme {
                    var selected by remember { mutableStateOf(false)}
                    Column {
                        Checkbox(
                            checked = selected,
                            onCheckedChange = { selected = it }
                        )
                        ContactsList(
                            isLoading = false,
                            names = persistentListOf("Peter")
                        )
                    }
    
                }
            }
        }
    
        @Composable
        fun ContactsList(
            isLoading: Boolean,
            names: ImmutableList<String> // 使用不可变List
        ) {
            Box(contentAlignment= Alignment.Center) {
                if (isLoading) {
                    CircularProgressIndicator()
                } else {
                    Text(names.toString())
                }
            }
        } 
    }
    
    • 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

    注意使用 ImmutableList需要单独添加一个依赖:

    implementation("org.jetbrains.kotlinx:kotlinx-collections-immutable:0.3.6") 
    
    • 1

    如果你对本文提到的 List 是一个不稳定类型这一点存有疑惑,请参考我的另一篇文章:Jetpack Compose 深入探索系列二:Compose 编译器,在这篇文章中的类的稳定性推断部分,详细的介绍了稳定类型需要满足的准则,以及什么样的类型是稳定类型或者不稳定类型。

    如果你需要更加详细全面的了解 Jetpack Compose 中的性能优化,请参考我的另一篇文章:
    Jetpack Compose 中的重组作用域和性能优化

  • 相关阅读:
    CSP 2021 入门级第一轮
    【python】奥数题
    DSP2335的LED工程笔记
    第二章 JAVA基础语法
    InnoDB和MyISAM的区别
    有线热电偶温度验证系统
    JS中call(),apply()是什么,call(),apply()的原理是什么?如何手写一个call(),apply()?Symbol是什么,怎么用Symbol调优?含详细解析
    JAVA:实现RabinKarpAlgorithm拉宾卡普算法(附完整源码)
    删除链表结点类问题
    二、 Error与Exception的区别
  • 原文地址:https://blog.csdn.net/lyabc123456/article/details/136321967