如何修改T的属性后自动触发LiveData<MutableList<T>>更新并刷新UI
优化方案
你现在的性能问题核心是两点:
- 每次修改单个属性后都提交全量列表到LiveData
- 适配器收到列表后调用
notifyDataSetChanged()全量刷新所有条目,效率极低
推荐实现方式(性能最优)
拆分初始化和单条更新逻辑,分别处理:
步骤1:给适配器新增两个更新方法
不用在同一个updateData里混着两种逻辑,拆分后职责更清晰:
// 仅用于第一次初始化全量列表 fun updateFullList(freshHostList: List<Host>) { hostList.clear() hostList.addAll(freshHostList) notifyDataSetChanged() } // 仅用于更新单个条目的ping值 fun updatePingByPosition(position: Int, newPing: Long) { hostList[position].averagePing = newPing // 仅刷新对应位置的条目,性能提升明显 notifyItemChanged(position) }
如果要实现局部刷新(只更新ping对应的TextView,不重绘整个条目),可以加payload参数:
// 调用时传入payload标记 notifyItemChanged(position, "UPDATE_PING") // 重写onBindViewHolder接收payload override fun onBindViewHolder(holder: ViewHolder, position: Int, payloads: MutableList<Any>) { if (payloads.firstOrNull() == "UPDATE_PING") { // 只更新ping值的TextView holder.pingTv.text = "${hostList[position].averagePing}ms" } else { super.onBindViewHolder(holder, position, payloads) } }
步骤2:修改ViewModel的事件分发逻辑
不用每次把全量列表塞给LiveData,改用密封类分发不同的UI事件:
// 定义UI事件密封类 sealed class HostUiEvent { data class InitFullList(val list: List<Host>) : HostUiEvent() data class UpdateSinglePing(val url: String, val pingValue: Long) : HostUiEvent() } // 替换原有hostListLiveData的类型 val uiEventLiveData = MutableLiveData<HostUiEvent>()
修改数据更新逻辑:
fun loadHosts() { viewModelScope.launch(Dispatchers.IO) { val hostList = repo.getHosts() hostList.forEach { host -> host.url = "https://${host.url}" host.averagePing = -1 } // 分发初始化事件 uiEventLiveData.postValue(HostUiEvent.InitFullList(hostList)) hostList.forEach { host -> async { pingHostAndUpdate(host.url) } } } } suspend fun pingHostAndUpdate(url: String) { try { val before = System.currentTimeMillis() val connection = URL(url).openConnection() as HttpURLConnection connection.connectTimeout = 5000 connection.connect() val diff = System.currentTimeMillis() - before connection.disconnect() // 直接分发单条更新事件 uiEventLiveData.postValue(HostUiEvent.UpdateSinglePing(url, diff)) } catch (e: Exception) { e.printStackTrace() } }
步骤3:界面层观察事件分别处理
viewModel.uiEventLiveData.observe(this) { event -> when(event) { is HostUiEvent.InitFullList -> adapter.updateFullList(event.list) is HostUiEvent.UpdateSinglePing -> { val position = adapter.hostList.indexOfFirst { it.url == event.url } if (position >= 0) { adapter.updatePingByPosition(position, event.pingValue) } } } }
替代方案(代码改动最小)
如果你不想改现有LiveData的结构,可以给适配器接入DiffUtil,自动计算列表差异,自动刷新变化的条目:
- 定义差异计算类:
class HostDiffCallback : DiffUtil.ItemCallback<Host>() { override fun areItemsTheSame(oldItem: Host, newItem: Host) = oldItem.url == newItem.url override fun areContentsTheSame(oldItem: Host, newItem: Host) = oldItem.averagePing == newItem.averagePing }
- 适配器继承
ListAdapter:
class HostAdapter : ListAdapter<Host, HostViewHolder>(HostDiffCallback())
- 每次更新全量列表时调用
adapter.submitList(newList)即可,DiffUtil会自动对比仅刷新变化的条目,不用手动调用任何notify方法。
针对你最后问题的回复
推荐实现两个独立的更新函数:全量初始化和单条更新,不需要在同一个updateData里混着调用两种notify方法,职责更清晰,性能也最优。
内容的提问来源于stack exchange,提问作者AleksejB
相关产品推荐
相关产品推荐

