You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Jetpack Compose LazyColumn滚动丢帧性能问题求助

LazyColumn Release版本滚动丢帧问题排查与解决思路

问题描述

使用LazyColumn编写示例页面时,Release构建版本中快速滚动列表出现丢帧现象。所有Model与Composable均为稳定类型,且已完成基线配置文件创建、Release构建测试、LazyColumn设置key参数等官方优化步骤,但问题仍未解决。

相关代码

ViewModel代码

@HiltViewModel
class ExampleViewModel @Inject constructor(
    private val repo: ExampleRepository,
) : ViewModel() {

    private val _viewState = MutableStateFlow(ItemsViewState())
    val viewState = _viewState.asStateFlow()

    init {
        fetch()
    }

    private fun fetch() = viewModelScope.launch {
        repo.getItems()
            .onStart { _viewState.value = _viewState.value.copy(state = PageState.Loading) }
            .onCompletion { _viewState.value = _viewState.value.copy(state = PageState.Content) }
            .collect { _viewState.value = _viewState.value.copy(items = it.toImmutableList()) }
    }
}

ViewState与Model代码

data class ItemsViewState(
    val items: ImmutableList<Item> = persistentListOf(),
    val state: PageState = PageState.Loading,
)

data class Item(
    val id: Int,
    val imageUrl: String,
    val name: String,
    val rating: Double,
    val campaignText: String,
    val isChecked: Boolean = false,
)

data class ItemViewState(val item: Item) {

    fun isRatingVisible(): Boolean = item.rating > 7.0
}

sealed class PageState {

    object Content : PageState()

    object Loading : PageState()

    object Error : PageState()
}

Composable函数代码

@Composable
fun ExampleScreen(
    viewModel: ExampleViewModel = hiltViewModel(),
) {
    val viewState by viewModel.viewState.collectAsState()

    when (viewState.state) {
        PageState.Content -> {
            ExampleList(viewState = viewState)
        }
        PageState.Loading -> LoadingScreen()
        PageState.Error -> {}
    }
}

@Composable
private fun ExampleList(
    viewState: ItemsViewState,
) {
    LazyColumn(
        state = rememberLazyListState(),
        contentPadding = PaddingValues(16.dp),
        verticalArrangement = Arrangement.spacedBy(8.dp),
        modifier = Modifier.fillMaxSize()
    ) {
        items(viewState.items, key = { it.id }) { item ->
            ExampleListItem(item = item)
        }
    }
}

@Composable
private fun ExampleListItem(item: Item) {
    val viewState = ItemViewState(item)

    Card(
        shape = RoundedCornerShape(8.dp),
        backgroundColor = MaterialTheme.colors.background
    ) {
        Row(
            modifier = Modifier
                .padding(8.dp)
                .fillMaxWidth()
                .wrapContentHeight()
        ) {
            AsyncImage(
                model = item.imageUrl,
                contentDescription = viewState.item.name,
                contentScale = ContentScale.FillHeight,
                modifier = Modifier
                    .clip(RoundedCornerShape(10.dp))
                    .width(120.dp)
                    .height(120.dp),
            )
            Spacer(modifier = Modifier.width(8.dp))
            Column(verticalArrangement = Arrangement.SpaceBetween) {
                Row(horizontalArrangement = Arrangement.SpaceEvenly) {
                    Text(
                        text = viewState.item.name,
                        overflow = TextOverflow.Ellipsis,
                        maxLines = 1,
                        modifier = Modifier.weight(1f),
                    )
                    Icon(imageVector = Icons.Default.List, contentDescription = null)
                }
                Spacer(modifier = Modifier.height(2.dp))
                Row {
                    if (viewState.isRatingVisible()) {
                        Spacer(modifier = Modifier.width(4.dp))
                        Text(
                            text = viewState.item.rating.toString(),
                            overflow = TextOverflow.Ellipsis,
                            maxLines = 1,
                        )
                    }
                }
                Spacer(modifier = Modifier.height(2.dp))
                CampaignRow(campaignText = viewState.item.campaignText)
            }
        }
    }
}

@Composable
private fun CampaignRow(
    modifier: Modifier = Modifier,
    campaignText: String,
) = Row(modifier = modifier) {
    Image(
        painter = painterResource(androidx.ui.livedata.R.drawable.abc_ic_star_black_16dp),
        contentDescription = "",
        modifier = Modifier
            .wrapContentSize()
            .align(Alignment.CenterVertically)
            .padding(end = 4.dp)
    )
    Text(
        text = campaignText,
        overflow = TextOverflow.Ellipsis,
        maxLines = 1,
    )
}

解决思路

1. 优化AsyncImage图片加载

滚动时图片重复解码、加载是常见卡顿原因,可从以下两点优化:

  • 指定图片加载尺寸:给AsyncImage的model添加目标尺寸参数,让图片加载时直接适配120dp×120dp的控件大小,避免解码过大图片:
    AsyncImage(
        model = ImageRequest.Builder(LocalContext.current)
            .data(item.imageUrl)
            .size(Size.RESIZE, 120.dp.toPx().toInt(), 120.dp.toPx().toInt())
            .build(),
        // 其他参数不变
    )
    
  • 配置缓存与占位图:启用Coil的内存+磁盘缓存,添加占位/错误图减少滚动时的空白绘制:
    AsyncImage(
        model = item.imageUrl,
        placeholder = painterResource(R.drawable.placeholder_image),
        error = painterResource(R.drawable.error_image),
        // 其他参数不变
    )
    

2. 缓存ItemViewState避免重复创建

当前ExampleListItem每次重组都会新建ItemViewState,用remember缓存该对象,减少不必要的对象创建开销:

@Composable
private fun ExampleListItem(item: Item) {
    val viewState = remember(item) { ItemViewState(item) }
    // 后续代码不变
}

3. 简化布局层级减少绘制开销

检查布局嵌套,合并不必要的容器:

  • 移除Rating模块中无意义的Row,直接用条件渲染组件;
  • 合并相邻的Spacer与父容器,减少布局节点数量。

4. 优化状态收集逻辑

使用collectAsStateWithLifecycle()替代collectAsState(),确保组件在后台时停止状态收集,避免无效重组(需依赖androidx.lifecycle:lifecycle-runtime-compose):

val viewState by viewModel.viewState.collectAsStateWithLifecycle()

5. 借助性能工具定位瓶颈

用Android Studio的Profile工具在Release模式下分析:

  • 跟踪CPU占用,查看滚动时的耗时函数(如图片解码、布局测量);
  • 用Compose Recomposition Tracker检查是否存在不必要的Composable重组;
  • 用Layout Inspector查看布局绘制的Overdraw情况,优化绘制层级。

内容的提问来源于stack exchange,提问作者pcparticle

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.07 13:15:32