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

使用MVVM架构时,Retrofit获取数据填充RecyclerView频繁触发IndexOutOfBoundsException异常

排查RecyclerView填充时的IndexOutOfBoundsException问题

我来帮你拆解下这个问题——你遇到的索引越界异常,核心原因是异步请求的回调时机和RecyclerView初始化时机不匹配,再加上代码里缺少必要的非空判断,咱们一步步理清楚:

问题根源分析

  1. Retrofit异步调用的时机差
    Retrofit的enqueue是异步执行的,你在ViewModel发起请求后,可能还没等网络请求返回、回调里的集合(actorList/pictureList等)被填充数据,就已经调用了createRV方法去初始化RecyclerView。这时候集合还是空的,直接访问actorList[0]必然触发IndexOutOfBoundsException。

  2. 缺少集合非空校验
    就算请求成功返回数据,你在回调里直接用characterData[0],在createRV里直接取actorList[0],都没有先判断集合是否为空。万一API返回的Staff列表是空(虽然你说现在能打印数据,但极端场景要考虑),也会直接报错。

解决方案

1. 用LiveData实现数据驱动更新(符合MVVM架构)

MVVM的核心就是数据驱动UI,ViewModel应该把数据通过LiveData暴露给Activity,Activity观察LiveData的变化,只有当数据真正就绪时才更新RecyclerView:

ViewModel里的改造:

// 定义LiveData,用来承载返回的Staff数据
val staffLiveData = MutableLiveData<List<YourStaffModel>>() // 替换成你的实际数据模型

fun fetchStaffData() {
    val retrofit = Retrofit.Builder()
        .baseUrl("http://hp-api.herokuapp.com/")
        .addConverterFactory(GsonConverterFactory.create())
        .build()
    val service = retrofit.create(HarryPotterApi::class.java)
    
    service.staff().enqueue(object : Callback<Staff> {
        override fun onResponse(call: Call<Staff>, response: Response<Staff>) {
            if (response.isSuccessful) {
                response.body()?.let { staffList ->
                    // 数据就绪后,通过LiveData通知Activity
                    staffLiveData.postValue(staffList)
                }
            }
        }

        override fun onFailure(call: Call<Staff>, t: Throwable) {
            // 这里可以处理请求失败的逻辑,比如给LiveData发空值或者错误状态
        }
    })
}

Activity里的改造:

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.your_activity_layout)
    
    // 初始化ViewModel并观察LiveData
    val viewModel = ViewModelProvider(this)[YourViewModel::class.java]
    viewModel.staffLiveData.observe(this) { staffList ->
        // 只有当数据非空时才初始化RecyclerView
        if (!staffList.isNullOrEmpty()) {
            initRecyclerView(staffList)
        }
    }
    
    // 发起数据请求
    viewModel.fetchStaffData()
}

private fun initRecyclerView(staffList: List<YourStaffModel>) {
    val recyclerview = findViewById<RecyclerView>(R.id.recyclerview)
    recyclerview.layoutManager = LinearLayoutManager(this)
    
    // 把数据转换成RecyclerView需要的ItemsViewModel
    val data = ArrayList<ItemsViewModel>()
    val firstStaff = staffList[0]
    data.add(ItemsViewModel(R.drawable.ic_launcher_background, firstStaff.name, "fds", "gsd"))
    
    val adapter = RVAdapter(data)
    recyclerview.adapter = adapter
}

2. 临时修复:确保回调完成后再更新RecyclerView(如果暂时不想用LiveData)

如果你想先快速验证问题,可以在Retrofit的onResponse回调里,确认数据填充到集合后,再切回主线程调用createRV,同时加上非空判断:

override fun onResponse(call: Call<Staff>, response: Response<Staff>) {
    println("the response code is " +response.code())
    if (response.code() == 200) {
        val characterData = response.body()
        // 先判断数据非空且有内容
        characterData?.takeIf { it.isNotEmpty() }?.let { data ->
            pictureList.add(data[0].name)
            actorList.add(data[0].name)
            characterList.add(data[0].name)
            houseList.add(data[0].name)
            
            // 切回主线程更新UI
            runOnUiThread {
                createRV(pictureList, actorList, characterList, houseList)
            }
        }
    }
}

同时在createRV里加一层校验:

fun createRV( pictureList: ArrayList<String?>, actorList: ArrayList<String>, characterList: ArrayList<String?>, houseList: ArrayList<String?>){
    // 先判断集合是否为空,避免越界
    if (actorList.isEmpty()) return
    
    val recyclerview = findViewById<RecyclerView>(R.id.recyclerview)
    recyclerview.layoutManager = LinearLayoutManager(this)
    val data = ArrayList<ItemsViewModel>()
    data.add(ItemsViewModel(R.drawable.ic_launcher_background, actorList[0],"fds","gsd"))
    val adapter = RVAdapter(data)
    recyclerview.adapter = adapter
}

3. 优化数据结构(可选但推荐)

你现在用多个独立集合(pictureList/actorList等)来管理数据,很容易出现数据不同步的问题。建议把这些字段封装到一个数据类里,比如:

data class CharacterInfo(
    val picture: String?,
    val actorName: String,
    val characterName: String?,
    val house: String?
)

然后用一个List<CharacterInfo>来统一管理,这样既安全又符合代码规范。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.30 22:08:13