Jetpack Compose中LazyColumn嵌套LazyRow引发OOM内存溢出问题求助
问题原因
- 嵌套Lazy组件的独立缓存机制:
LazyColumn每一行的LazyRow都是独立的Lazy列表实例,每个实例默认会缓存4个离屏子项、持有独立的滚动状态、测量数据和视图缓存,30行就存在30个独立的缓存池,内存占用会指数级升高。 - 低版本Compose的内存泄漏BUG:Compose 1.4以下版本存在已知的嵌套Lazy列表状态泄漏问题,当
LazyColumn回收行条目时,内部LazyRow持有的滚动状态、子项缓存无法被GC正确回收,滚动次数越多内存堆积越严重。 - 未限制离屏缓存与测量优化:默认
LazyRow没有固定高度,滚动过程中会频繁触发子项测量,测量数据会被持久化持有,进一步加剧内存占用。 - 数据类重复生成列表:
TestDataBlock构造时每次都会生成21个新的TestDataItem实例,数据量大时会生成大量冗余对象。
解决办法
优先方案:替换为LazyVerticalGrid
网格类布局直接使用官方提供的LazyVerticalGrid实现,它是单一Lazy列表实例,统一管理所有子项的缓存与回收,完全避免嵌套Lazy组件的内存问题,示例修改如下:
@Composable fun List(dataItems: List<TestDataBlock> = listOf()) { // 每行固定展示21个元素,可根据需求调整单元格规则 LazyVerticalGrid( columns = GridCells.Fixed(21), modifier = Modifier.fillMaxSize() ) { items(dataItems.size * 21) { index -> val rowIndex = index / 21 val columnIndex = index % 21 val item = dataItems[rowIndex].testDataItems[columnIndex] Text(text = item.id, color = Color.Black, modifier = Modifier.width(100.dp)) } } }
TV端开发可以使用适配TV焦点的TvLazyVerticalGrid组件,效果一致。
兼容方案:必须保留嵌套滚动场景的优化
如果业务需要每一行独立横向滚动,无法替换为网格组件,可以通过以下方式优化:
- 升级Compose版本到1.4.0及以上,修复嵌套Lazy组件的内存泄漏底层BUG。
- 显式控制每个
LazyRow的缓存数量,禁用不必要的离屏缓存:
@Composable fun drawElement(rowItem: TestDataBlock) { Text(text = rowItem.id) // 绑定行id作为key,行回收时同步销毁状态,同时限制离屏缓存数量为0 val lazyRowState = remember(key1 = rowItem.id) { LazyListState(beyondBoundsItemCount = 0) } LazyRow( state = lazyRowState, // 固定行高,避免重复测量 modifier = Modifier.height(48.dp) ) { itemsIndexed( items = rowItem.testDataItems, key = { _, item -> item.id } ) { _, item -> Text(text = item.id, color = Color.Black, modifier = Modifier.width(100.dp)) } } }
- 优化数据实例,避免重复生成子项列表:将
TestDataBlock中的testDataItems抽为全局缓存或者伴生对象存储,不需要每次构造TestDataBlock都生成新的列表。
@Immutable data class TestDataBlock( val id: String, val data: Int, ) { companion object { // 公共缓存,所有实例共用一份子项列表 private val sharedTestItems = (0..20).map { TestDataItem(it.toString()) } } val testDataItems: List<TestDataItem> = sharedTestItems }
按照以上方案优化后,滚动20次的内存占用可以稳定在50MB以内,不会触发OOM问题。
内容的提问来源于stack exchange,提问作者Qalterra
相关产品推荐
相关产品推荐

