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

如何修改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,自动计算列表差异,自动刷新变化的条目:

  1. 定义差异计算类:
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
}
  1. 适配器继承ListAdapter:
class HostAdapter : ListAdapter<Host, HostViewHolder>(HostDiffCallback())
  1. 每次更新全量列表时调用adapter.submitList(newList)即可,DiffUtil会自动对比仅刷新变化的条目,不用手动调用任何notify方法。

针对你最后问题的回复

推荐实现两个独立的更新函数:全量初始化和单条更新,不需要在同一个updateData里混着调用两种notify方法,职责更清晰,性能也最优。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.25 06:45:03