使用 AsyncTask 和 notifyItemInserted() 重新填充 RecyclerView 后出现 IndexOutOfBoundsException
IndexOutOfBoundsException after refilling RecyclerView with AsyncTask and notifyItemInserted()
我有一个使用 AsyncTask 填充数据的 RecyclerView。当我用 clear()
和 mAdapter.notifyDataSetChanged()
清除列表然后尝试用 AsyncTask 再次填充它时,我得到这个异常:
java.lang.IndexOutOfBoundsException: Inconsistency detected. Invalid view holder adapter position
在我的 AsyncTask 中用 notifyDataSetChanged()
替换 notifyItemInserted()
解决了问题,但我认为这不是一个好的解决方案,我想了解为什么第一种方法不起作用.
我的 AsyncTask doInBackground()
方法:
@Override
protected Void doInBackground(Void... voids) {
for (int i = 0; i < 100; i++) {
mDataContainer.addItem(i);
publishProgress(i);
}
return null;
}
和我的 AsyncTask onProgressUpdate()
方法:
@Override
protected void onProgressUpdate(Integer... values) {
mAdapter.notifyItemInserted(values[0]);
super.onProgressUpdate(values);
}
我希望有人能帮助我解决这个问题。提前致谢。
编辑:
这是适配器:
private class MyAdapter extends RecyclerView.Adapter<MyAdapter.MyViewHolder> {
private List<Item> mItems;
private int selectedPos = RecyclerView.NO_POSITION;
private class MyViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
private Item mItem;
private TextView mNameTextView;
private TextView mMembersTextView;
MyViewHolder(View view) {
super(view);
itemView.setOnClickListener(this);
mNameTextView = itemView.findViewById(R.id.item_name);
mMembersTextView = itemView.findViewById(R.id.item_members);
}
@Override
public void onClick(View view) {
notifyItemChanged(selectedPos);
selectedPos = getLayoutPosition();
notifyItemChanged(selectedPos);
mOnItemSelectedListener.onItemSelected(mItem);
}
}
MyAdapter(List<Item> items) {
mItems = items;
}
@NonNull
@Override
public MyViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(getActivity())
.inflate(R.layout.list_item, parent, false);
return new MyViewHolder(view);
}
@Override
public void onBindViewHolder(MyViewHolder myViewHolder, int position) {
Item item = mItems.get(position);
myViewHolder.mItem = item;
myView.mNameTextView.setText(item.getName());
myView.mMembersTextView.setText(String.format(Locale.US,"%d/50", item.getMembers()));
if (!mIsInit) {
// select item that was selected before orientation change
if (selectedPos != RecyclerView.NO_POSITION) {
Item selectedItem = mItems.get(selectedPos);
mOnItemSelectedListener.onItemSelected(selectedItem);
// else select item 0 as default on landscape mode
} else if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE){
selectedPos = 0;
mOnItemSelectedListener.onItemSelected(MyViewHolder.mItem);
}
mIsInit = true;
}
if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) {
myViewHolder.itemView.setSelected(selectedPos == position);
}
}
@Override
public int getItemCount() {
return mItems.size();
}
}
我认为最好在后台添加所有值并在所有数据插入后通知适配器
@Override
protected Void doInBackground(Void... voids) {
for (int i = 0; i < 100; i++) {
mDataContainer.addItem(i);
}
return null;
}
@Override
protected void onPostExecute(Long result) {
mAdapter.notifyItemRangeInserted(0, 100);
}
我还建议你不要使用 AsyncTask
因为它是 deprecated on Android 11
您应该只从 UI 线程向适配器添加项目(并通知)。从后台更改适配器的支持数据,然后稍后通过 onProgressUpdate
在 UI 线程上通知很可能会导致竞争条件。在这种情况下,应在通知适配器之前将 mDataContainer.addItem(i);
移动到 onProgressUpdate
。
如果没有看到 mDataContainer
的定义,很难确认这是否是唯一的问题,但在这里修复同步肯定是解决这个问题的第一步。
我有一个使用 AsyncTask 填充数据的 RecyclerView。当我用 clear()
和 mAdapter.notifyDataSetChanged()
清除列表然后尝试用 AsyncTask 再次填充它时,我得到这个异常:
java.lang.IndexOutOfBoundsException: Inconsistency detected. Invalid view holder adapter position
在我的 AsyncTask 中用 notifyDataSetChanged()
替换 notifyItemInserted()
解决了问题,但我认为这不是一个好的解决方案,我想了解为什么第一种方法不起作用.
我的 AsyncTask doInBackground()
方法:
@Override
protected Void doInBackground(Void... voids) {
for (int i = 0; i < 100; i++) {
mDataContainer.addItem(i);
publishProgress(i);
}
return null;
}
和我的 AsyncTask onProgressUpdate()
方法:
@Override
protected void onProgressUpdate(Integer... values) {
mAdapter.notifyItemInserted(values[0]);
super.onProgressUpdate(values);
}
我希望有人能帮助我解决这个问题。提前致谢。
编辑: 这是适配器:
private class MyAdapter extends RecyclerView.Adapter<MyAdapter.MyViewHolder> {
private List<Item> mItems;
private int selectedPos = RecyclerView.NO_POSITION;
private class MyViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
private Item mItem;
private TextView mNameTextView;
private TextView mMembersTextView;
MyViewHolder(View view) {
super(view);
itemView.setOnClickListener(this);
mNameTextView = itemView.findViewById(R.id.item_name);
mMembersTextView = itemView.findViewById(R.id.item_members);
}
@Override
public void onClick(View view) {
notifyItemChanged(selectedPos);
selectedPos = getLayoutPosition();
notifyItemChanged(selectedPos);
mOnItemSelectedListener.onItemSelected(mItem);
}
}
MyAdapter(List<Item> items) {
mItems = items;
}
@NonNull
@Override
public MyViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(getActivity())
.inflate(R.layout.list_item, parent, false);
return new MyViewHolder(view);
}
@Override
public void onBindViewHolder(MyViewHolder myViewHolder, int position) {
Item item = mItems.get(position);
myViewHolder.mItem = item;
myView.mNameTextView.setText(item.getName());
myView.mMembersTextView.setText(String.format(Locale.US,"%d/50", item.getMembers()));
if (!mIsInit) {
// select item that was selected before orientation change
if (selectedPos != RecyclerView.NO_POSITION) {
Item selectedItem = mItems.get(selectedPos);
mOnItemSelectedListener.onItemSelected(selectedItem);
// else select item 0 as default on landscape mode
} else if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE){
selectedPos = 0;
mOnItemSelectedListener.onItemSelected(MyViewHolder.mItem);
}
mIsInit = true;
}
if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) {
myViewHolder.itemView.setSelected(selectedPos == position);
}
}
@Override
public int getItemCount() {
return mItems.size();
}
}
我认为最好在后台添加所有值并在所有数据插入后通知适配器
@Override
protected Void doInBackground(Void... voids) {
for (int i = 0; i < 100; i++) {
mDataContainer.addItem(i);
}
return null;
}
@Override
protected void onPostExecute(Long result) {
mAdapter.notifyItemRangeInserted(0, 100);
}
我还建议你不要使用 AsyncTask
因为它是 deprecated on Android 11
您应该只从 UI 线程向适配器添加项目(并通知)。从后台更改适配器的支持数据,然后稍后通过 onProgressUpdate
在 UI 线程上通知很可能会导致竞争条件。在这种情况下,应在通知适配器之前将 mDataContainer.addItem(i);
移动到 onProgressUpdate
。
如果没有看到 mDataContainer
的定义,很难确认这是否是唯一的问题,但在这里修复同步肯定是解决这个问题的第一步。