Android Glide如何等待/重新加载其他线程中正在保存的图片?
解决Glide加载写入中图片导致显示不完整的问题
这个问题我做相册类应用时也碰到过,核心原因就是Glide读取文件的时候,后台协程还在写入,拿到的是不完整的文件流,自然显示异常。给你几个针对性的解决方案,按实现复杂度从易到难排序:
方案一:写入完成后主动通知刷新(最推荐)
既然是协程在写文件,那我们可以在文件写入完成的瞬间,通知相册Fragment刷新对应的RecyclerView Item,让Glide重新加载完整的图片。
具体步骤:
在写入图片的协程里添加完成回调
假设你是在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) { // 处理写入异常 } }在相册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) } }你的Binding Adapter可以保持不变
因为刷新时文件已经完全写入,Glide会加载到完整的PNG文件,自然显示正常。
方案二:用临时文件避免加载不完整内容
另一种思路是先写临时文件,写完再重命名为目标文件名,这样Glide加载的目标文件只有在写入完成后才会存在,从根源上避免读取不完整的文件。
具体修改:
修改图片写入逻辑
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) }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
相关产品推荐
相关产品推荐

