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

Jetpack Compose:LaunchedEffect调用操作仍阻塞UI问题排查

问题分析与修复方案

核心问题:耗时操作阻塞主线程

你的代码中,所有文件读取、解密、Bitmap解码的耗时操作都在主线程执行,直接导致UI卡顿、按钮无法响应。具体原因如下:

  1. viewModelScope.launch默认使用Dispatchers.Main(主线程)执行任务,secretDataManager.getImages()的所有逻辑都跑在主线程。
  2. 虽然getImages是suspend函数,但它本身不会自动切换线程,必须显式指定后台调度器来处理IO/计算密集型操作。

修复步骤

1. 改造SecretDataManager:将耗时操作移至后台线程

把文件读取、解密、Bitmap解码逻辑包裹在withContext(Dispatchers.IO)中,强制切换到后台线程执行:

class SecretDataManager @Inject constructor(
    private val cryptoManager: CryptoManager,
    private val storageManager: StorageManager,
    private val repo: EncryptedVaultDataRepo,
    @ApplicationContext
    private val ctx: Context
) : SecretDataManagerService {

    override suspend fun getImages(): List<GalleryElement> = withContext(Dispatchers.IO) {
        val result = mutableListOf<GalleryElement>()
        repo.getAll().forEach { vaultItem ->
            // 读取加密文件(IO操作)
            val encryptedBytes = storageManager.readFile(File("${ctx.filesDir}/${vaultItem.name}").toUri()).use { inputStream ->
                inputStream?.readBytes() ?: return@forEach // 处理文件读取失败的情况
            }
            // 解密(计算/IO混合操作)
            val decryptedBytes = cryptoManager.decrypt(vaultItem.iv, encryptedBytes)
            // Bitmap解码(计算密集型操作)
            val bitmap = BitmapFactory.decodeByteArray(decryptedBytes, 0, decryptedBytes.size)
            result.add(GalleryElement(bitmap))
        }
        result
    }
}

2. 简化ViewModel逻辑

原getGalleryItems中的嵌套协程是多余的,直接调用suspend函数即可(LaunchedEffect已经在协程环境中):

@HiltViewModel
class WelcomeViewModel @Inject constructor(
    private val secretDataManager: SecretDataManager,
) : ViewModel() {

    private val _images = MutableLiveData<List<GalleryElement>>()
    val images: LiveData<List<GalleryElement>> = _images

    suspend fun getGalleryItems() {
        _images.value = secretDataManager.getImages()
    }
}

3. 优化Composable的状态观察

给observeAsState设置默认空列表,避免冗余的null判断:

@Composable
fun WelcomeView(
    viewModel: WelcomeViewModel = hiltViewModel()
) {
    LaunchedEffect(Unit) {
        viewModel.getGalleryItems()
    }
    // 设置默认值为emptyList(),简化后续逻辑
    val list = viewModel.images.observeAsState(emptyList())

    Column() {
        Button(onClick = {}){
            Text(text = "Click me while pictures are requested") 
        }
        LazyVerticalGrid(columns = GridCells.Adaptive(minSize = 128.dp)) {
            items(list.value) { item: GalleryElement ->
                GalleryItem(element = item)
            }
        }
    }
}

额外优化建议

如果图片数量较多,还可以进一步提升体验:

  • 分页加载:不要一次性加载所有图片,配合LazyVerticalGrid的分页机制逐步加载。
  • Bitmap采样压缩:使用BitmapFactory.Options对大图进行采样,避免内存溢出。
  • 添加加载状态:在数据加载过程中显示CircularProgressIndicator,告知用户当前状态。
  • 改用Flow:用StateFlow替代LiveData,获得更灵活的异步流处理能力。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.06 21:30:41