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
相关产品推荐
相关产品推荐

