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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.19 14:37:13