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

Android Glide如何等待/重新加载其他线程中正在保存的图片?

解决Glide加载写入中图片导致显示不完整的问题

这个问题我做相册类应用时也碰到过,核心原因就是Glide读取文件的时候,后台协程还在写入,拿到的是不完整的文件流,自然显示异常。给你几个针对性的解决方案,按实现复杂度从易到难排序:

方案一:写入完成后主动通知刷新(最推荐)

既然是协程在写文件,那我们可以在文件写入完成的瞬间,通知相册Fragment刷新对应的RecyclerView Item,让Glide重新加载完整的图片。

具体步骤:

  1. 在写入图片的协程里添加完成回调
    假设你是在ViewModel里处理图片写入,那可以用LiveData发送刷新事件:

    // ViewModel中定义LiveData
    private val _refreshThumbnail = MutableLiveData<String>() // 传入memo的uuid作为标识
    val refreshThumbnail: LiveData<String> = _refreshThumbnail
    
    // 写入图片的协程逻辑
    viewModelScope.launch(Dispatchers.IO) {
        try {
            // 执行图片写入操作(比如生成缩略图并保存)
            val targetFile = File("${getPath(memo.uuid)}$THUMB_NAME")
            writeThumbnailToFile(memo, targetFile)
            // 写入完成后,发送刷新事件
            _refreshThumbnail.postValue(memo.uuid)
        } catch (e: Exception) {
            // 处理写入异常
        }
    }
    
  2. 在相册Fragment中监听刷新事件
    拿到uuid后找到对应的Item位置,通知Adapter刷新:

    // 相册Fragment的onViewCreated里
    viewModel.refreshThumbnail.observe(viewLifecycleOwner) { memoUuid ->
        val targetPosition = memoList.indexOfFirst { it.uuid == memoUuid }
        if (targetPosition != -1) {
            recyclerView.adapter?.notifyItemChanged(targetPosition)
        }
    }
    
  3. 你的Binding Adapter可以保持不变
    因为刷新时文件已经完全写入,Glide会加载到完整的PNG文件,自然显示正常。

方案二:用临时文件避免加载不完整内容

另一种思路是先写临时文件,写完再重命名为目标文件名,这样Glide加载的目标文件只有在写入完成后才会存在,从根源上避免读取不完整的文件。

具体修改:

  1. 修改图片写入逻辑

    viewModelScope.launch(Dispatchers.IO) {
        val tempFile = File("${getPath(memo.uuid)}temp_$THUMB_NAME")
        // 先写入临时文件
        writeThumbnailToFile(memo, tempFile)
        // 写入完成后,重命名为最终的缩略图文件名
        val targetFile = File("${getPath(memo.uuid)}$THUMB_NAME")
        tempFile.renameTo(targetFile)
    }
    
  2. Binding Adapter中添加占位图优化体验
    如果目标文件还没生成(还在写入),Glide会加载失败,你可以加个占位图:

    @BindingAdapter("memoThumb")
    fun memoThumb(view: ImageView, memo: Memo) {
        val imageUrl = "${getPath(memo.uuid)}$THUMB_NAME"
        val targetFile = File(imageUrl)
        if (targetFile.exists()) { // 只有文件存在才加载
            Glide.with(view.context)
                .load(imageUrl)
                .apply(RequestOptions()
                    .signature(ObjectKey(targetFile.lastModified())) // 用文件修改时间作为签名,避免缓存旧文件
                    .diskCacheStrategy(DiskCacheStrategy.AUTOMATIC)
                )
                .transition(DrawableTransitionOptions.withCrossFade())
                .into(view)
        } else {
            // 文件还在写入,显示占位图
            view.setImageResource(R.drawable.ic_thumbnail_loading)
        }
    }
    

    等文件重命名完成后,你还是可以用方案一的方式通知刷新,让占位图替换成真实缩略图。

方案三:Glide监听加载失败并重试(应急方案)

如果前面两种方案都没法快速修改,你可以在Binding Adapter里给Glide加一个加载失败的监听,判断是不是因为文件不完整导致的失败,然后重试加载。

修改你的Binding Adapter:

@BindingAdapter("memoThumb")
fun memoThumb(view: ImageView, memo: Memo) {
    val imageUrl = "${getPath(memo.uuid)}$THUMB_NAME"
    if (imageUrl.isNotEmpty()) {
        var retryCount = 0
        val loadImage = {
            Glide.with(view.context)
                .load(imageUrl)
                .apply(RequestOptions()
                    .signature(ObjectKey(System.currentTimeMillis()))
                    .diskCacheStrategy(DiskCacheStrategy.AUTOMATIC)
                )
                .listener(object : RequestListener<Drawable> {
                    override fun onLoadFailed(e: GlideException?, model: Any?, target: Target<Drawable>?, isFirstResource: Boolean): Boolean {
                        // 判断是否是文件截断/不完整的异常
                        val isTruncatedFile = e?.rootCauses?.any {
                            it is IOException && it.message?.contains("truncated") == true
                        } == true
                        if (isTruncatedFile && retryCount < 3) { // 最多重试3次
                            retryCount++
                            view.postDelayed(loadImage, 200) // 延迟200ms重试
                            return true
                        }
                        return false
                    }

                    override fun onResourceReady(resource: Drawable?, model: Any?, target: Target<Drawable>?, dataSource: DataSource?, isFirstResource: Boolean): Boolean {
                        return false
                    }
                })
                .transition(DrawableTransitionOptions.withCrossFade())
                .into(view)
        }
        loadImage()
    }
}

这个方案的缺点是依赖异常判断,不同设备的异常信息可能有差异,重试次数和延迟时间也需要根据实际情况调整,不如前两种方案可靠。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.11 09:07:01