Android使用Retrofit2填充ArrayList时出现IndexOutOfBoundsException异常
问题根本原因
- 异步调用时序问题:你使用
retrofitData.enqueue()发起的是异步网络请求,代码不会等待请求返回再往下执行。FetchContent的init代码块中调用完getRetroFitData()后立刻循环调用createFetchItem()读取数据,此时网络请求还未完成,retroId等存储数据的数组完全是空的,自然会抛出下标越界异常。你调试时看到的数据是请求返回后才存入的,和崩溃发生的时机不重合。 - 实例不共享问题:你每次调用
fetchedData()都是新建一个类实例,请求成功后数据存在了临时实例的私有变量中,后续调用fetchedData().getID()拿到的是全新的空实例,之前存储的数据完全无法访问。
修复方案
第一步:调整数据存储类为单例,增加加载回调
将fetchedData改为单例保证全局共享同一个实例,同时增加数据加载完成的回调,解决异步时序问题:
// 类名调整为大驼峰,符合Kotlin命名规范 object FetchedData { private val retroId = arrayListOf<String>() private val retroListId = arrayListOf<String>() private val retroName = arrayListOf<String>() fun getRetroFitData(onLoaded: ()->Unit) { val retrofitBuilder = Retrofit.Builder() .addConverterFactory(GsonConverterFactory.create()) .baseUrl(PRIMARY_URL) .build() .create(interfaceAPI::class.java) val retrofitData = retrofitBuilder.getData() retrofitData.enqueue(object : Callback<List<FetchContent.FetchDataItem>?> { override fun onResponse( call: Call<List<FetchContent.FetchDataItem>?>, response: Response<List<FetchContent.FetchDataItem>?> ) { val responseBody = response.body() ?: return // 先清空旧数据避免重复添加 retroId.clear() retroListId.clear() retroName.clear() for (item in responseBody) { if (!item.name.isNullOrEmpty()) { retroId.add(item.id) retroListId.add(item.listId) retroName.add(item.name) } } // 数据加载完成后触发回调 onLoaded.invoke() } override fun onFailure(call: Call<List<FetchContent.FetchDataItem>?>, t: Throwable) { Log.d("FetchContent", "onFailure:"+t.message) } }) } // getter方法增加下标越界判断 fun getID(index: Int): String { return if (index >= retroId.size || retroId[index].isEmpty()) "null" else retroId[index] } fun getListID(index: Int): String { return if (index >= retroListId.size) "null" else retroListId[index] } fun getName(index: Int): String { return if (index >= retroName.size) "null" else retroName[index] } // 新增获取数据总条数的方法 fun getDataSize(): Int = retroId.size }
第二步:修改FetchContent逻辑,移除init块的硬编码循环
不要在init块中硬写固定长度的循环,等数据加载完成后再填充列表:
object FetchContent { val jsonITEMS: MutableList<FetchDataItem> = ArrayList() fun loadData(onLoaded: ()->Unit) { FetchedData.getRetroFitData { jsonITEMS.clear() // 根据实际返回的数据条数生成列表项 for (i in 0 until FetchedData.getDataSize()) { jsonITEMS.add(createFetchItem(i)) } onLoaded.invoke() } } private fun createFetchItem(position: Int): FetchDataItem { return FetchDataItem( FetchedData.getID(position), FetchedData.getListID(position), FetchedData.getName(position) ) } data class FetchDataItem(val id: String, val listId: String, val name: String) { override fun toString(): String = listId } }
第三步:修改ItemFragment,异步加载完成后更新适配器
先给RecyclerView设置空适配器,等数据加载完成后再更新数据:
override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? { val view = inflater.inflate(R.layout.fragment_item_list, container, false) if (view is RecyclerView) { with(view) { layoutManager = when { columnCount <= 1 -> LinearLayoutManager(context) else -> GridLayoutManager(context, columnCount) } // 先设置空适配器 adapter = MyItemRecyclerViewAdapter(emptyList()) // 异步加载数据 FetchContent.loadData { // 切回主线程更新UI activity?.runOnUiThread { adapter = MyItemRecyclerViewAdapter(FetchContent.jsonITEMS) // 也可以给适配器新增updateData方法更新数据,避免重复创建实例,性能更优 } } } } return view }
内容的提问来源于stack exchange,提问作者Richard Walker
相关产品推荐
相关产品推荐

