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

在ViewModel中存储LazyListState引用是否会引发内存泄漏?

问题:Compose中ViewModel持有UI回调是否会引发内存泄漏?

我用Compose的Pull Refresh实现下拉刷新的LazyColumn,希望刷新后滚动到列表顶部。目前的做法是在ViewModel里设置一个onRefreshFinished回调,在loadList执行完后触发滚动到顶部的操作。

对应的Compose代码:

@Composable
fun pullRefreshList(
    contacts: List<String>,
    navController: NavController,
    modifier: Modifier = Modifier,
) {
    val viewModel = viewModel<MainViewModel>()
    val refreshing by viewModel.isRefreshing.collectAsState()
    val listState = rememberLazyListState()

    viewModel.onRefreshFinished = {
        // Scroll to the top when the callback is invoked
        listState.scrollToItem(0)
    }

    val pullRefreshState = rememberPullRefreshState(refreshing, viewModel::loadList)

    Box(Modifier.pullRefresh(pullRefreshState)) {
        LazyColumn(state = listState, modifier = modifier) {
            items(contacts) { contactName ->
                Row(
                    modifier = Modifier
                        .height(30.dp)
                        .fillMaxWidth()
                        .clickable { navController.navigate("screen2") },
                    horizontalArrangement = Arrangement.Center,
                ) {
                    Text(text = contactName, fontSize = 24.sp)
                }
            }
        }

        PullRefreshIndicator(refreshing, pullRefreshState, Modifier.align(Alignment.TopCenter))
    }
}

对应的ViewModel代码:

class MainViewModel: ViewModel() {
    private val _isRefreshing = MutableStateFlow(false)
    val isRefreshing = _isRefreshing.asStateFlow()
    private var nextInt = 0
    var onRefreshFinished: (suspend () -> Unit)? = null

    fun loadList() {
        viewModelScope.launch {
            _isRefreshing.value = true

            addToList()

            _isRefreshing.value = false
            onRefreshFinished?.invoke()
        }
    }
}

在基于XML的UI中,我们通常不会让ViewModel持有View引用,因为这会导致内存泄漏。想请教在Compose中,这种做法是否也会引发内存泄漏?


回答

这种做法确实存在内存泄漏风险,本质和XML时代的问题一致:

  • ViewModel的生命周期远长于Composable(ViewModel会在配置变更后存活,而Composable会随页面销毁、重组被回收)。如果ViewModel持有指向Composable内部状态(比如LazyListState)的回调,当Composable被销毁后,这个回调引用会阻止相关UI对象被GC回收,进而引发内存泄漏。

推荐的安全实现方式

我们可以在Composable侧监听刷新状态变化,当刷新完成时自动触发滚动,完全不需要让ViewModel绑定UI回调:

@Composable
fun pullRefreshList(
    contacts: List<String>,
    navController: NavController,
    modifier: Modifier = Modifier,
) {
    val viewModel = viewModel<MainViewModel>()
    val refreshing by viewModel.isRefreshing.collectAsState()
    val listState = rememberLazyListState()
    val pullRefreshState = rememberPullRefreshState(refreshing, viewModel::loadList)

    // 监听刷新状态,从true变为false时(刷新完成)滚动到顶部
    LaunchedEffect(refreshing) {
        if (!refreshing) {
            listState.scrollToItem(0)
        }
    }

    Box(Modifier.pullRefresh(pullRefreshState)) {
        LazyColumn(state = listState, modifier = modifier) {
            items(contacts) { contactName ->
                Row(
                    modifier = Modifier
                        .height(30.dp)
                        .fillMaxWidth()
                        .clickable { navController.navigate("screen2") },
                    horizontalArrangement = Arrangement.Center,
                ) {
                    Text(text = contactName, fontSize = 24.sp)
                }
            }
        }

        PullRefreshIndicator(refreshing, pullRefreshState, Modifier.align(Alignment.TopCenter))
    }
}

对应的ViewModel可以去掉回调,保持业务职责单一:

class MainViewModel: ViewModel() {
    private val _isRefreshing = MutableStateFlow(false)
    val isRefreshing = _isRefreshing.asStateFlow()
    private var nextInt = 0

    fun loadList() {
        viewModelScope.launch {
            _isRefreshing.value = true
            addToList()
            _isRefreshing.value = false
        }
    }
}

这种方式更安全的原因

  • LaunchedEffect会跟随Composable的生命周期自动管理:当Composable被销毁时,LaunchedEffect会自动取消,不会持有多余引用。
  • ViewModel只负责业务逻辑(加载数据、维护刷新状态),不涉及任何UI操作或引用,完全符合MVVM的职责分离原则。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.10 17:52:35