Jetpack Compose中如何用ViewModel替代CursorLoader加载相册及图片数据
实现方案(Jetpack Compose + ViewModel 加载相册数据)
1. 前置权限配置
首先在AndroidManifest.xml中添加对应存储权限:
<!-- Android 13及以上版本 --> <uses-permission android:name="android.permission.READ_MEDIA_IMAGES" /> <!-- Android 12及以下版本 --> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="32" />
2. 数据结构定义
你原有代码中的Image、Album类可直接复用,补充字段参考如下:
data class Image( val id: String, val name: String, val uri: String, val width: Float, val height: Float, val addTime: Long, val folderName: String // 用于归类相册 ) data class Album( val folderName: String, val coverUri: String, val imageCount: Int, val imageList: List<Image> )
3. ViewModel逻辑实现
所有查询逻辑放在ViewModel中,用协程执行后台查询,避免阻塞主线程,同时自动管理生命周期避免内存泄漏:
import android.app.Application import android.provider.MediaStore import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.viewModelScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import java.util.UUID class AlbumViewModel(application: Application) : AndroidViewModel(application) { private val _albumList = MutableStateFlow<List<Album>>(emptyList()) val albumList: StateFlow<List<Album>> = _albumList // 权限申请通过后调用该方法触发加载 fun loadAlbumImages() { viewModelScope.launch { val albums = queryImagesFromMediaStore() _albumList.value = albums } } private suspend fun queryImagesFromMediaStore(): List<Album> = withContext(Dispatchers.IO) { val imageList = mutableListOf<Image>() val projection = arrayOf( MediaStore.Images.Media._ID, MediaStore.Images.Media.DATA, MediaStore.Images.Media.WIDTH, MediaStore.Images.Media.HEIGHT, MediaStore.Images.Media.DISPLAY_NAME, MediaStore.Images.Media.DATE_ADDED, MediaStore.Images.Media.BUCKET_DISPLAY_NAME // 系统直接返回所属相册名,无需自行拆分路径 ) // use方法自动关闭Cursor,避免内存泄漏 getApplication<Application>().contentResolver.query( MediaStore.Images.Media.EXTERNAL_CONTENT_URI, projection, null, null, "${MediaStore.Images.Media.DATE_ADDED} DESC" )?.use { cursor -> if (cursor.count > 0) { cursor.moveToFirst() do { val name = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DISPLAY_NAME)) val path = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA)) val width = cursor.getFloat(cursor.getColumnIndexOrThrow(MediaStore.Images.Media.WIDTH)) val height = cursor.getFloat(cursor.getColumnIndexOrThrow(MediaStore.Images.Media.HEIGHT)) val time = cursor.getLong(cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATE_ADDED)) val folderName = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Images.Media.BUCKET_DISPLAY_NAME)) ?: "其他" val image = Image( id = UUID.randomUUID().toString(), name = name, uri = "file:///$path", width = width, height = height, addTime = time, folderName = folderName ) imageList.add(image) } while (cursor.moveToNext()) } } return@withContext splitFolder(imageList) } // 按文件夹分组生成相册列表,可直接复用你原有拆分逻辑 private fun splitFolder(imageList: List<Image>): List<Album> { val albumMap = imageList.groupBy { it.folderName } return albumMap.map { entry -> Album( folderName = entry.key, coverUri = entry.value.first().uri, imageCount = entry.value.size, imageList = entry.value ) }.toList() } }
4. Compose 侧调用
在Compose页面中先申请存储权限,再监听数据变化渲染UI:
import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.result.contract.ActivityResultContracts import androidx.compose.foundation.lazy.grid.GridCells import androidx.compose.foundation.lazy.grid.LazyVerticalGrid import androidx.compose.foundation.lazy.grid.items import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsStateWithLifecycle import androidx.compose.ui.platform.LocalContext import androidx.lifecycle.viewmodel.compose.viewModel @Composable fun AlbumPickerScreen( albumViewModel: AlbumViewModel = viewModel(LocalContext.current as androidx.activity.ComponentActivity) ) { val albumList = albumViewModel.albumList.collectAsStateWithLifecycle().value // 权限申请器 val permissionLauncher = rememberLauncherForActivityResult( contract = ActivityResultContracts.RequestPermission(), onResult = { granted -> if (granted) albumViewModel.loadAlbumImages() } ) // 进入页面自动申请权限 LaunchedEffect(key1 = Unit) { val permission = if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.TIRAMISU) { android.Manifest.permission.READ_MEDIA_IMAGES } else { android.Manifest.permission.READ_EXTERNAL_STORAGE } permissionLauncher.launch(permission) } // 自定义渲染相册/图片列表,实现选图逻辑即可 LazyVerticalGrid(columns = GridCells.Fixed(3)) { items(albumList.flatMap { it.imageList }) { image -> // 可配合Coil/Glide加载图片 Text(text = image.name) } } }
内容的提问来源于stack exchange,提问作者gaohomway
相关产品推荐
相关产品推荐

