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

离开Activity后如何获取正确的RecyclerView ViewHolder

解决FirebaseRecyclerAdapter下载进度在Activity返回后无法正确更新视图的问题

这个问题的核心原因其实很好理解:RecyclerView的ViewHolder是复用机制,当你离开Activity再返回时,原来绑定下载任务的ViewHolder可能已经被回收,转而绑定了其他列表项;同时异步的下载回调还拿着旧的ViewHolder引用,自然会更新错误的视图。要解决这个问题,我们需要把下载状态和进度与数据模型绑定,而不是依赖ViewHolder,同时用ViewModel来管理跨生命周期的下载任务。下面是具体的实现方案:

1. 给数据模型添加下载状态字段

首先,把下载相关的状态(是否正在下载、当前进度)直接附加到你的列表数据类中,这样不管ViewHolder怎么复用,数据本身的状态是准确的。比如:

data class FileModel(
    val storageRef: StorageReference? = null,
    val fileName: String? = null,
    // 新增本地状态字段,不需要同步到Firebase数据库
    var isDownloading: Boolean = false,
    var downloadProgress: Int = 0
)

2. 用ViewModel管理下载任务与状态

ViewModel的生命周期和Activity/Fragment绑定,不会因为页面离开、重建而丢失数据。我们可以用它来保存正在进行的下载任务,以及维护数据模型的状态:

class DownloadViewModel : ViewModel() {
    // 用文件路径作为键,保存正在进行的下载任务
    val activeDownloads = mutableMapOf<String, DownloadTask>()
    // 如果你的数据源是本地维护的列表,也可以放在这里;如果是Firebase实时数据库,直接关联Adapter的数据源即可
}

3. 在Adapter中根据数据状态更新视图

修改FirebaseRecyclerAdapter的onBindViewHolder方法,每次绑定数据时,根据FileModel的状态来更新UI,而不是依赖ViewHolder的缓存引用:

override fun onBindViewHolder(holder: FileViewHolder, position: Int, model: FileModel) {
    // 绑定基础数据(比如文件名)
    holder.fileNameTv.text = model.fileName

    // 根据下载状态切换视图
    if (model.isDownloading) {
        holder.downloadBtn.visibility = View.GONE
        holder.progressBar.visibility = View.VISIBLE
        holder.progressBar.progress = model.downloadProgress
    } else {
        holder.downloadBtn.visibility = View.VISIBLE
        holder.progressBar.visibility = View.GONE
    }

    // 下载按钮点击事件
    holder.downloadBtn.setOnClickListener {
        startDownload(model, holder.adapterPosition)
    }
}

4. 启动下载时更新数据状态并绑定回调

启动下载任务时,更新对应数据模型的状态,同时在进度回调里更新数据并通知Adapter刷新指定位置:

private fun startDownload(model: FileModel, position: Int) {
    val storageRef = model.storageRef ?: return
    val localFile = File(getExternalFilesDir(null), model.fileName)

    // 更新数据状态
    model.isDownloading = true
    notifyItemChanged(position)

    // 启动下载任务并保存到ViewModel
    val downloadTask = storageRef.getFile(localFile)
    viewModel.activeDownloads[storageRef.path] = downloadTask

    // 进度回调:更新数据模型并刷新UI
    downloadTask.addOnProgressListener { snapshot ->
        val progress = (100.0 * snapshot.bytesTransferred / snapshot.totalByteCount).toInt()
        model.downloadProgress = progress
        notifyItemChanged(position)
    }.addOnCompleteListener { task ->
        model.isDownloading = false
        notifyItemChanged(position)
        viewModel.activeDownloads.remove(storageRef.path)
        
        if (task.isSuccessful) {
            // 下载完成的逻辑(比如提示用户)
        } else {
            // 下载失败的逻辑
        }
    }
}

5. 处理Activity返回后的状态恢复

在Activity的onResume方法中,让ViewModel重新绑定所有活跃下载任务的回调,确保返回后进度能继续更新到正确的视图:

override fun onResume() {
    super.onResume()
    viewModel.activeDownloads.forEach { (path, task) ->
        // 找到对应的FileModel
        val model = adapter.currentList.find { it.storageRef?.path == path } ?: return@forEach
        // 重新绑定进度回调
        task.addOnProgressListener { snapshot ->
            val progress = (100.0 * snapshot.bytesTransferred / snapshot.totalByteCount).toInt()
            model.downloadProgress = progress
            // 找到当前模型在列表中的位置并刷新
            val position = adapter.currentList.indexOf(model)
            if (position != -1) {
                adapter.notifyItemChanged(position)
            }
        }.addOnCompleteListener {
            model.isDownloading = false
            val position = adapter.currentList.indexOf(model)
            if (position != -1) {
                adapter.notifyItemChanged(position)
            }
            viewModel.activeDownloads.remove(path)
        }
    }
}

关键注意事项

  • 永远不要直接持有ViewHolder的引用:ViewHolder会被RecyclerView复用,持有引用会导致更新错误的视图,甚至内存泄漏。
  • 避免将本地下载进度同步到Firebase数据库:除非你需要所有客户端都看到这个进度,否则本地维护状态即可。
  • 记得取消无用的下载任务:当列表项被滑出屏幕或者Activity销毁时,取消对应的下载任务,避免浪费资源。

内容的提问来源于stack exchange,提问作者Jean-Pascal MEWENEMESSE

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.25 07:04:36