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

Jetpack Compose中基于Cursor在LazyColumn展示大数据的最佳实践

最佳实践:Jetpack Compose LazyColumn 结合 Cursor 按需加载数十万条数据

核心思路

避免一次性将Cursor全量转为对象存入内存,模仿RecyclerView的按需加载逻辑:仅在LazyColumn需要渲染某条数据时,才从Cursor中读取对应位置的记录并转为对象。


步骤1:封装Cursor为按需访问的数据源

创建一个持有Cursor的数据源类,提供获取总条数和单条数据的方法,同时管理Cursor的生命周期:

class CursorDataSource<T>(
    private val cursor: Cursor,
    private val mapper: (Cursor) -> T
) : AutoCloseable {
    val itemCount: Int get() = cursor.count

    fun getItemAt(position: Int): T? {
        return if (position in 0 until itemCount) {
            cursor.moveToPosition(position)
            mapper(cursor)
        } else {
            null
        }
    }

    override fun close() {
        if (!cursor.isClosed) {
            cursor.close()
        }
    }
}

步骤2:ViewModel中用StateFlow暴露数据源

在ViewModel中,根据过滤条件异步查询Cursor,包装成CursorDataSource后通过StateFlow发射,同时注意在数据源更新时关闭旧Cursor,避免泄漏:

class OrgViewModel(private val pkmAndroid: PkmAndroid) : ViewModel() {
    private val _orgFilter = MutableStateFlow(OrgFilter()) // 自定义过滤条件类
    private val _orgDataSource = MutableStateFlow<CursorDataSource<Organisation>?>(null)
    val orgDataSource = _orgDataSource.asStateFlow()

    init {
        viewModelScope.launch {
            _orgFilter.collect { filter ->
                // 关闭旧数据源,释放Cursor
                _orgDataSource.value?.close()
                // IO线程执行数据库查询
                val cursor = withContext(Dispatchers.IO) {
                    pkmAndroid.queryOrgList(filter)
                }
                // 包装数据源并发射
                _orgDataSource.value = CursorDataSource(cursor) {
                    PkmAndroid.getOrganisationFromCursor(it)
                }
            }
        }
    }

    // 更新过滤条件的方法
    fun updateFilter(filter: OrgFilter) {
        _orgFilter.value = filter
    }

    override fun onCleared() {
        super.onCleared()
        // ViewModel销毁时关闭剩余Cursor
        _orgDataSource.value?.close()
    }
}

步骤3:Compose中LazyColumn按需加载数据

在Compose界面中,收集StateFlow,获取数据源后,使用items渲染,仅在需要时调用getItemAt获取数据:

@Composable
fun OrgListScreen(viewModel: OrgViewModel = viewModel()) {
    val orgDataSource by viewModel.orgDataSource.collectAsState()

    LazyColumn {
        orgDataSource?.let { dataSource ->
            items(count = dataSource.itemCount) { position ->
                dataSource.getItemAt(position)?.let { org ->
                    OrgItem(org = org) // 自定义的Item布局Composable
                } ?: run {
                    // 处理位置越界的占位布局
                    EmptyListItem()
                }
            }
        } ?: run {
            // 加载中或空状态布局
            LoadingPlaceholder()
        }
    }
}

关键注意事项

  • Cursor生命周期:必须在数据源更新、ViewModel销毁时关闭Cursor,避免数据库连接泄漏。
  • 线程安全:数据库查询必须在IO线程执行,ViewModel中用withContext(Dispatchers.IO)切换线程,避免阻塞主线程。
  • 异常处理:可在getItemAt中添加try-catch,处理Cursor已关闭、位置无效等异常情况。
  • 过滤更新:过滤条件变化时,自动重新查询Cursor并更新数据源,Compose会自动重组LazyColumn。

优势对比

相比全量转List的实现,这种方案:

  • 内存占用极低:仅转换当前可见的几个Item对象,数十万条数据也不会占用大量内存。
  • 响应速度更快:无需等待全量数据转换完成,查询到Cursor后即可立即显示列表。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.19 17:05:02