RecyclerView的NO_POSITION 不是异常值!

本文探讨了在使用RecyclerView时遇到的getAdapterPosition()返回-1导致的程序崩溃问题。官方解释称在布局未完成时,该方法会返回NO_POSITION(-1)。为避免崩溃,建议在调用后立即检查返回值。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

  1. RecyclerView 是当前代码中使用最多的控件之一。

        代码编写过程中针对列表的点击事件 很多同学都会在自己定义的RecyclerView.Adapter 里面 onBindViewHolder()中添加监听事件。

        这里就会发生一个很神奇的事情, 当你通过getAdapterPosition() 去拿当前选中位置的时候有时候会返回-1;导致程序崩溃

holder.view.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
            		/*在这里拿到选中的条目数据*/
                list.get(holder.getAdapterPosition());
            }
        });

测试会偶先这样的崩溃bug。

09-11 17:19:06.690 30454 30454 E AndroidRuntime: java.lang.IndexOutOfBoundsException: Invalid index -1, size is 8
09-11 17:19:06.690 30454 30454 E AndroidRuntime: 	at java.util.ArrayList.throwIndexOutOfBoundsException(ArrayList.java:255)

2. Cause:

进入RecyclerView.java 的 getAdapterPosition()方法:

/**
         * Note that if you've called {@link RecyclerView.Adapter#notifyDataSetChanged()}, until the
         * next layout pass, the return value of this method will be {@link #NO_POSITION}.
         *
         * @return The adapter position of the item if it still exists in the adapter.
         * {@link RecyclerView#NO_POSITION} if item has been removed from the adapter,
         * {@link RecyclerView.Adapter#notifyDataSetChanged()} has been called after the last
         * layout pass or the ViewHolder has already been recycled.
         */
public final int getAdapterPosition() {
            if (mOwnerRecyclerView == null) {
                return NO_POSITION;
            }
            return mOwnerRecyclerView.getAdapterPositionFor(this);
}

官方明确表示,在layout布局没有完成的时候 会返回 NO_POSITION, 因此 返回-1 是一个很正常的情况。

最好的解决方案,是在使用 getAdapterPosition() 方法的时候 马上进行返回值的检查。

比如:

holder.view.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
            		/*在这里拿到选中的条目数据*/
                int pos = holder.getAdapterPosition();
                if(pos != RecyclerView.NO_POSITION)list.get(pos);
            }
        });

 

 

 

 

代码如下:public class PhoneActivity extends AppCompatActivity { ListView contactsview; static List<Person> contactslist=new ArrayList<Person>(); PhoneAdapter phoneAdapter=new PhoneAdapter(this,contactslist); // static map star[] @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_phone); initview(); if (!isTaskRoot()) { Intent intent=getIntent(); String flag=intent.getStringExtra("flag"); int position=Integer.parseInt(intent.getStringExtra("position")); // Toast.makeText(PhoneActivity.this,flag, Toast.LENGTH_SHORT).show(); Person person = contactslist.get(position); if(flag.equals("used")){ // Person person = contactslist.get(position); person.setStar(true); // 设置为显示星星 } else{ person.setStar(false); } } } private void initview() { contactsview=(ListView) findViewById(R.id.contactsview); PhoneAdapter phoneAdapter=new PhoneAdapter(this,contactslist); contactsview.setAdapter(phoneAdapter); if(ContextCompat.checkSelfPermission(PhoneActivity.this,Manifest.permission.READ_CONTACTS)!= PackageManager.PERMISSION_GRANTED){ ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.READ_CONTACTS},1); } else {readContacts();} SharedPreferences sharedPreferences= getSharedPreferences("people", MODE_PRIVATE); String isstar=sharedPreferences.getString("Isstar",""); List<String> result = new ArrayList<>(Arrays.asList(isstar.split(","))); if (result != null && !result.isEmpty()) { try { for (String item : result) { contactslist.get(Integer.parseInt(item)).setStar(true); } } catch (NumberFormatException e) { e.printStackTrace(); // 处理转换失败的情况,例如使用默认值 } } phoneAdapter.notifyDataSetChanged(); } private void readContacts() { Cursor cursor = null; try { cursor = getContentResolver().query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI, null, null, null, null); if (cursor != null) { while (cursor.moveToNext()) { @SuppressLint("Range") String display_name = cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME)); @SuppressLint("Range") String number = cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER)); // 每次循环都创建一个新的Person对象 Person person = new Person(); person.setName(display_name); person.setNumber(number); contactslist.add(person); } phoneAdapter.notifyDataSetChanged(); } } catch (Exception e) { e.printStackTrace(); } finally { if (cursor != null) { cursor.close(); } } }
最新发布
07-13
<think>我们正在处理一个Android应用中的`IndexOutOfBoundsException`异常,具体错误信息是: `java.lang.IndexOutOfBoundsException: Index 12 out of bounds for length 4`,发生在`contactslist.get(position)`这一行。 ### 问题分析 1. **错误原因**:尝试从一个长度为4的集合(如`List`或数组)中获取索引为12的元素。有效索引范围是0到3,而12超出了这个范围。 2. **发生位置**:在`PhoneActivity`的启动过程中(`onCreate`方法内),具体是在访问联系人列表(`contactslist`)时发生。 3. **可能场景**: - 从数据库或网络获取的联系人数据只有4条,但代码中试图访问第13条(索引从0开始,索引12表示第13个元素)。 - 用于计算索引的变量`position`可能被错误赋值(如使用错误的变量、硬编码值或计算错误)。 ### 解决步骤 #### 1. 定位错误代码 在`PhoneActivity`中查找`contactslist.get(position)`的调用位置。通常出现在: - 列表适配器(如`ArrayAdapter`或`RecyclerView.Adapter`)的`getView`/`onBindViewHolder`方法中。 - 直接访问列表的代码(如根据用户点击位置获取联系人)。 #### 2. 检查数据源 确保数据源`contactslist`已正确初始化并包含数据: ```java // 示例:检查联系人列表大小 Log.d("DEBUG", "联系人列表大小: " + contactslist.size()); ``` #### 3. 验证索引值 检查变量`position`的值来源: - 如果是用户点击事件(如`RecyclerView`的点击监听),确保`position`在有效范围内: ```java // 在点击监听器中添加范围检查 @Override public void onClick(View v) { int position = getAdapterPosition(); if (position != RecyclerView.NO_POSITION && position < contactslist.size()) { Contact contact = contactslist.get(position); // 安全操作 } } ``` - 如果是通过`Intent`传递的索引值,需验证其有效性: ```java int position = getIntent().getIntExtra("position", -1); if (position >= 0 && position < contactslist.size()) { // 安全获取 } else { // 处理无效索引 } ``` #### 4. 修复代码(防御性编程) - **Java 方案**: ```java // 使用条件判断 if (contactslist != null && position >= 0 && position < contactslist.size()) { Contact contact = contactslist.get(position); } else { Log.e("TAG", "无效索引或空列表: position=" + position); } ``` - **Kotlin 方案**(使用安全操作符): ```kotlin val contact = contactslist?.getOrNull(position) contact?.let { // 安全操作 } ?: run { Log.e("TAG", "无效索引或空列表: position=$position") } ``` #### 5. 预防措施 - **使用不可变集合**:用`Collections.unmodifiableList()`包装列表,防止外部修改导致大小变化。 - **索引计算**:若索引由公式计算(如`position = currentIndex * 2`),需确保结果不超过集合大小: $$ \text{position}_{\text{valid}} = \min(\text{计算值}, \text{contactslist.size()} - 1) $$ - **单元测试**:为列表访问逻辑编写测试用例,覆盖边界情况(空列表、索引过大/过小)。 ### 常见陷阱 1. **异步加载数据**:在数据加载完成前访问列表。解决方案: - 使用`LiveData`或回调通知数据就绪。 - 添加加载状态检查: ```java if (isDataLoaded) { // 访问列表 } ``` 2. **列表更新不同步**:在获取数据后更新了列表但未通知适配器(如忘记调用`notifyDataSetChanged()`),导致适配器使用旧索引范围。 ### 相关问题 §§相关问题§§ 1. 如何全局捕获并处理Android中的`IndexOutOfBoundsException`? 2. 在`RecyclerView`适配器中如何避免索引越界? 3. 有哪些工具可以自动检测Android中的数组越界问题? 4. Kotlin提供了哪些安全操作符防止集合越界访问?</think>### 解决 Android 联系人列表的 IndexOutOfBoundsException 问题 #### 问题根源分析 `java.lang.IndexOutOfBoundsException: Index 12 out of bounds for length 4` 表示: 1. 您尝试访问列表中的第 **13 个元素**(索引从 0 开始,索引 12 = 第 13 个元素) 2. 但实际联系人列表只有 **4 个元素** 3. 错误发生在 `contactslist.get(position)` 操作中 4. 典型场景:在 `RecyclerView/ListView` 的适配器或点击事件处理中访问列表[^1] #### 解决方案步骤 ##### 1. 紧急修复 - 添加索引检查 在访问列表前添加防御性检查: ```java // Java 方案 if (position >= 0 && position < contactslist.size()) { Contact contact = contactslist.get(position); // 后续操作 } else { Log.e("CONTACTS", "无效索引: " + position + "/" + contactslist.size()); // 处理错误情况 } ``` ```kotlin // Kotlin 方案 (推荐) val contact = contactslist.getOrNull(position)?.apply { // 安全操作联系人数据 } ?: run { Log.w("CONTACTS", "索引越界: $position/${contactslist.size}") // 错误处理 } ``` ##### 2. 定位问题代码 在 `PhoneActivity` 中搜索以下关键点: ```java // 常见出错位置 contactslist.get(adapterPosition) // RecyclerView 点击事件 listView.getItemAtPosition(position) // ListView 操作 intent.getIntExtra("position", 0) // 从其他Activity传递的位置 ``` ##### 3. 数据一致性检查 ```java // 在加载数据后添加验证 Log.d("DATA_CHECK", "联系人数量: " + contactslist.size()); // 确保数据加载和界面更新同步 runOnUiThread(() -> { adapter.notifyDataSetChanged(); // 刷新适配器 }); ``` ##### 4. 高级预防措施 **数学索引验证公式**: $$ \text{validPosition} = \begin{cases} \text{position} & \text{if } 0 \leq \text{position} < n \\ \text{clamp}(\text{position}, 0, n-1) & \text{otherwise} \end{cases} $$ **使用安全封装类**: ```kotlin class SafeContactList(private val contacts: List<Contact>) { fun getSafe(position: Int): Contact? { return contacts.getOrNull(position.coerceIn(0, contacts.lastIndex) } } ``` #### 常见错误场景和修复 | 场景 | 错误代码 | 修复方案 | |------|----------|----------| | 异步加载数据未完成 | `get(clickedPos)` | 添加加载状态检查 | | 列表更新后未通知适配器 | 修改数据后未调用 `notifyDataSetChanged` | 确保每次数据变化都通知适配器 | | 分页加载边界情况 | `get(pageSize * currentPage)` | 添加页码验证:`currentPage >= totalPages` | | 设备旋转后位置丢失 | 使用临时位置变量 | 通过 `onSaveInstanceState` 保存位置 | #### 预防性编程建议 1. **使用 Kotlin 安全操作符** ```kotlin contactslist.elementAtOrNull(position)?.let { contact -> // 安全操作 } ``` 2. **实现全局异常监控** ```java Thread.setDefaultUncaughtExceptionHandler((thread, ex) -> { if (ex instanceof IndexOutOfBoundsException) { logToServer("位置错误: " + ex.getMessage()); } }); ``` 3. **添加单元测试边界用例** ```kotlin @Test fun `test contact access boundary`() { val emptyList = emptyList<Contact>() assertNull(emptyList.getSafe(-1)) assertNull(emptyList.getSafe(0)) } ``` 4. **使用静态代码分析工具** - Android Studio 的 `Lint` 检查 - `SpotBugs` 插件检测空集合访问 - `SonarQube` 配置索引越界规则 > **关键提示**:当使用 `RecyclerView` 时,始终在 `onBindViewHolder` 中使用 `holder.adapterPosition` 而非缓存的位置值,因为位置可能在数据更新后失效[^2]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值