Retrofit加载商品列表,点击第16+项触发数组越界异常求助
问题分析
从异常信息 java.lang.IndexOutOfBoundsException: Index 1 out of bounds for length 1 可以判断,崩溃发生在启动ProductDetailsActivity时,核心原因是访问了长度为1的数组/集合的索引1。结合前15项正常、16项及以后崩溃的现象,大概率是以下两种情况:
可能的原因与修复方案
1. 详情页直接访问Product类数组的固定索引
如果ProductDetailsActivity中存在类似这样的代码:
// 示例:直接访问images数组的索引1 binding.detailImage.setImageURI(Uri.parse(product?.images?.get(1)))
而API返回的第16项及以后的Product对象中,images数组(或其他数组字段)的长度仅为1,此时访问索引1就会触发越界异常。
修复方案:
访问数组前先判断长度,添加边界保护:
product?.images?.let { images -> // 优先取索引1的元素,长度不足时取索引0或默认值 val targetImage = if (images.size > 1) images[1] else images.getOrNull(0) ?: "" binding.detailImage.setImageURI(Uri.parse(targetImage)) }
2. Product类的Parcelable实现错误
如果Product类在实现Parcelable时,对数组/集合类型字段的序列化/反序列化处理不当,会导致传递到详情页的对象数据损坏,比如将长度为1的数组错误解析为更长的数组,后续访问时触发越界。
修复方案:
确保Product类正确实现Parcelable,以字符串数组为例:
data class Product( val id: Int, val title: String, val thumbnail: String, val images: Array<String> ) : Parcelable { constructor(parcel: Parcel) : this( parcel.readInt(), parcel.readString() ?: "", parcel.readString() ?: "", // 正确读取字符串数组,为空时返回空数组 parcel.createStringArray() ?: emptyArray() ) override fun writeToParcel(parcel: Parcel, flags: Int) { parcel.writeInt(id) parcel.writeString(title) parcel.writeString(thumbnail) parcel.writeStringArray(images) } override fun describeContents(): Int = 0 companion object CREATOR : Parcelable.Creator<Product> { override fun createFromParcel(parcel: Parcel): Product = Product(parcel) override fun newArray(size: Int): Array<Product?> = arrayOfNulls(size) } // 数组类型需重写equals和hashCode,避免数据比较错误 override fun equals(other: Any?): Boolean { if (this === other) return true if (javaClass != other?.javaClass) return false other as Product if (!images.contentEquals(other.images)) return false return true } override fun hashCode(): Int = images.contentHashCode() }
3. 可选优化:Adapter数据更新方式
虽然不是直接原因,但可以优化RecyclerView Adapter的数据更新逻辑,避免复用ViewHolder时的数据一致性问题:
使用DiffUtil替代直接修改集合+notifyDataSetChanged():
class ProductAdapter : RecyclerView.Adapter<ProductViewHolder>() { private var productList = emptyList<Product>() override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ProductViewHolder { val inflater = LayoutInflater.from(parent.context) return ProductViewHolder(inflater.inflate(R.layout.productlist_item, parent, false)) } override fun getItemCount(): Int = productList.size override fun onBindViewHolder(holder: ProductViewHolder, position: Int) { val product = productList[position] holder.render(product, holder.itemView.context) holder.itemView.setOnClickListener { val intent = Intent(it.context, ProductDetailsActivity::class.java) intent.putExtra("product", product) it.context.startActivity(intent) } } fun updateProducts(newProducts: List<Product>) { val diffCallback = ProductDiffCallback(productList, newProducts) val diffResult = DiffUtil.calculateDiff(diffCallback) productList = newProducts diffResult.dispatchUpdatesTo(this) } private class ProductDiffCallback( private val oldList: List<Product>, private val newList: List<Product> ) : DiffUtil.Callback() { override fun getOldListSize(): Int = oldList.size override fun getNewListSize(): Int = newList.size override fun areItemsTheSame(oldPos: Int, newPos: Int): Boolean = oldList[oldPos].id == newList[newPos].id override fun areContentsTheSame(oldPos: Int, newPos: Int): Boolean = oldList[oldPos] == newList[newPos] } }
内容的提问来源于stack exchange,提问作者Eklavya
相关产品推荐
相关产品推荐

