第一个就是列表中如果有加载图片,那么大图一定要压缩!一定要重采样!不要几M几十M的图直接放到app里面加载!那样会卡爆!会内存OOM!
我们可以使用三方的图片加载库,如coil,因为图片加载库一般会缓存,同一张图片不会重复加载第二次,还可以根据尺寸压缩等。
关于 coil 图片加载库的使用,请参考 Jetpack Compose中的Accompanist 一文中有介绍
第二个,为 LazyColumn
的 items()
方法显示的指定一个 key
,可以优化列表的加载性能,避免多余的重组。尤其是对于长列表而言性能更佳。
LazyColumn(
modifier = Modifier.fillMaxSize(),
contentPadding = PaddingValues(16.dp),
verticalArrangement = Arrangement.spacedBy(16.dp)
) {
items(items = images, key = { it.id }) { image ->
ImageDetails(image)
}
}
这里的 key
需要指定一个唯一的id
,如果列表的业务数据没有提供一个唯一的id
,可以使用UUID
生成一个:
data class MyImage(
val resId: Int,
val title: String,
val tags: List<String>,
val id: String = UUID.randomUUID().toString() // 自己生成一个唯一id
)
第三个,可以为数据类添加@Immutable
以提高性能,这样编译器会认为它是稳定的类型,从而可以实施智能重组、跳过重组等特性。
@Immutable
data class MyImage(
val resId: Int,
val title: String,
val tags: List<String>,
val id: String = UUID.randomUUID().toString()
)
上面代码如果不加@Immutable
,我们在 Layout Inspector 中观察列表快速滑动的时候,会发现列表出现红色闪烁,这说明发生了重组。因为其中的 val tags: List
不是一个稳定的类型,因为编译器不知道它会如何实现。@Immutable
向编译器承诺:我是一个稳定的类型。加了它之后,在 Layout Inspector 中就可以看到重组次数为0,跳过重组的次数有很多(本该重组的次数)。
来看一个简单示例:
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())
}
}
}
}
上面代码中同样是由于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>
)
}
这时如果我们再次在 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())
}
}
}
}
注意使用 ImmutableList
需要单独添加一个依赖:
implementation("org.jetbrains.kotlinx:kotlinx-collections-immutable:0.3.6")
如果你对本文提到的 List
是一个不稳定类型这一点存有疑惑,请参考我的另一篇文章:Jetpack Compose 深入探索系列二:Compose 编译器,在这篇文章中的类的稳定性推断部分,详细的介绍了稳定类型需要满足的准则,以及什么样的类型是稳定类型或者不稳定类型。
如果你需要更加详细全面的了解 Jetpack Compose 中的性能优化,请参考我的另一篇文章:
Jetpack Compose 中的重组作用域和性能优化。