能否不调用notify方法更新RecyclerView?列表项进度条自更新方案咨询
嘿,这个思路完全靠谱!用观察者模式绑定ViewHolder和DownloadManager的进度变化,确实是比定时刷新优雅太多的方案,既能避免不必要的UI线程阻塞,又能减少冗余的RecyclerView刷新操作。我来给你捋捋具体怎么落地:
核心实现方案拆解
本质就是让每个ViewHolder成为对应下载任务进度的订阅者,当DownloadManager里的任务进度更新时,直接通知对应的ViewHolder去更新ProgressBar,全程不用麻烦Adapter调用notifyItemChanged()。
1. 给DownloadManager添加进度发布能力
首先得让DownloadManager能对外发送进度变化的事件。这里用Android自带的LiveData举例(不用额外依赖第三方库,还能自动适配线程),你也可以换成RxJava的Observable或者自己实现简单的观察者模式:
class DownloadManager { // 用Map存储每个下载任务ID对应的进度LiveData private val progressLiveDataMap = mutableMapOf<String, MutableLiveData<Int>>() // 获取指定任务的进度LiveData,不存在则创建 fun getProgressLiveData(taskId: String): LiveData<Int> { return progressLiveDataMap.getOrPut(taskId) { MutableLiveData() } } // 下载进度更新时,同步更新对应LiveData(postValue确保后台线程调用也能安全更新UI) fun updateTaskProgress(taskId: String, progress: Int) { progressLiveDataMap[taskId]?.postValue(progress) } // 任务完成/取消后,移除对应LiveData,避免内存占用 fun clearTaskProgress(taskId: String) { progressLiveDataMap.remove(taskId) } }
2. 让ViewHolder订阅对应任务的进度
在ViewHolder内部,绑定列表项的下载任务时,订阅该任务的进度LiveData,进度变化时直接更新ProgressBar:
class DownloadViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { private val progressBar: ProgressBar = itemView.findViewById(R.id.download_progress_bar) private var currentTaskId: String? = null private var progressObserver: Observer<Int>? = null fun bind(downloadTask: DownloadTask, downloadManager: DownloadManager) { // 先取消之前任务的订阅,避免错误更新和内存泄漏 currentTaskId?.let { oldTaskId -> downloadManager.getProgressLiveData(oldTaskId).removeObserver(progressObserver!!) } currentTaskId = downloadTask.id // 创建新的进度观察者 progressObserver = Observer { progress -> progressBar.progress = progress // 进度到100时可以做收尾操作,比如隐藏ProgressBar if (progress == 100) { progressBar.visibility = View.GONE } } // 订阅进度变化 downloadManager.getProgressLiveData(downloadTask.id).observeForever(progressObserver!!) // 初始化当前进度 progressBar.progress = downloadTask.currentProgress } // ViewHolder被回收时,必须取消订阅,防止内存泄漏 fun onRecycled() { currentTaskId?.let { taskId -> downloadManager.getProgressLiveData(taskId).removeObserver(progressObserver!!) } currentTaskId = null progressObserver = null } }
3. 让Adapter配合ViewHolder的回收逻辑
Adapter需要重写onViewRecycled方法,调用ViewHolder的回收处理,确保废弃的ViewHolder不会一直持有LiveData引用:
class DownloadAdapter(private val downloadManager: DownloadManager) : RecyclerView.Adapter<DownloadViewHolder>() { private val downloadTasks = mutableListOf<DownloadTask>() override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): DownloadViewHolder { val itemView = LayoutInflater.from(parent.context) .inflate(R.layout.item_download, parent, false) return DownloadViewHolder(itemView) } override fun onBindViewHolder(holder: DownloadViewHolder, position: Int) { holder.bind(downloadTasks[position], downloadManager) } override fun onViewRecycled(holder: DownloadViewHolder) { super.onViewRecycled(holder) holder.onRecycled() } // 只有列表结构变化时才调用这个方法更新 fun refreshTasks(newTasks: List<DownloadTask>) { downloadTasks.clear() downloadTasks.addAll(newTasks) notifyDataSetChanged() } override fun getItemCount(): Int = downloadTasks.size }
4. 关键注意事项
- 内存泄漏防范:ViewHolder被回收时一定要取消订阅,这是重中之重!用
observeForever必须手动移除观察者,如果你的场景能绑定Activity/Fragment的生命周期,也可以用observe(lifecycleOwner, observer),但前者的灵活性更高。 - 线程安全:DownloadManager更新进度时一定要用
postValue,因为下载操作肯定在后台线程,postValue会自动把更新切换到主线程,避免UI线程阻塞。 - 任务清理:下载任务完成或取消后,记得调用
clearTaskProgress移除对应的LiveData,避免无用的内存占用。
这样实现后,每个ProgressBar只会在自己对应的任务进度变化时才更新,完全不用定时触发刷新,高效又流畅!
内容的提问来源于stack exchange,提问作者Marty Miller
相关产品推荐
相关产品推荐

