尝试刷新数据后滚动时,RecyclerView崩溃并出现IndexOutOfBoundsException。
所需的功能:在API请求成功填充一次RecyclerView之后,我想刷新RecyclerView并在刷新时上下滚动。
当前功能:如果我在刷新数据时不滚动,则该应用程序不会崩溃。如果在发出刷新请求后滚动,它将崩溃并显示IndexOutOfBoundsException。
我已经花了数周的时间尝试解决此问题而不发布问题,并且我相信我已经尝试了足够的潜在解决方案来向Stack Overflow寻求指导。关于同一主题,这里有无数的问题,但不幸的是,没有一个问题解决了我的问题。预先感谢您的考虑。
以下是其他人建议的解决方案:
要使用adapter.notifyDataSetChanged(),但我了解到
在Android文档中被视为“不得已”
在adapter.notifyDataSetChanged()之前调用list.clear
要使用adapter.getItemCount()将数据集中所有当前项目的位置定位为一个名为“ position”的整数,然后将其传递给adapter.notifyItemRangeChanged(position)
设置adapter.setHasStableIds(true)
调用mRecyclerView.getRecycledViewPool()。clear()和mAdapter.notifyDataSetChanged();
显然,如果RecyclerView在LinearLayout中,则“通知”方法不起作用(这可能与Android中的一个旧错误有关,现在可以修复,但我不确定。)
所有这些建议都会导致“致命异常”。
我的应用使用了五个文件:
JobsAdapter(适配器)
JobsListItem(字母和字母)
JobsOut(片段)
jobs_recyclerview
jobs_listitem
我只包含了Adapter和Fragment的代码,因为我确信布局文件以及Getter和Setter的格式正确。
分段:
public class JobsOut extends Fragment {
String jobId;
String jobTitle;
String jobNumber;
String jobStartTime;
String dispatchType;
@BindView(R.id.jobsOutRecyclerView) RecyclerView jobsOutRecyclerView;
@BindView(R.id.fab) FloatingActionButton refreshFab;
private List<JobsListItem> dispatch;
private RecyclerView.Adapter mJobsOutAdapter;
public RecyclerView.LayoutManager dispatchLayoutManager;
OkHttpClient client = new OkHttpClient();
Handler handler = new Handler();
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.recycler_test, container, false);
ButterKnife.bind(this, rootView);
dispatch = new ArrayList<>();
jobsOutRecyclerView.setHasFixedSize(true);
dispatchLayoutManager = new LinearLayoutManager(getContext());
jobsOutRecyclerView.setLayoutManager(dispatchLayoutManager);
downloadDispatch();
refreshFab.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
downloadDispatch();
getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
dispatch.clear();
}
});
}
});
return rootView;
}
@Override
public void onDestroy() {
super.onDestroy();
handler.removeCallbacksAndMessages(this);
}
private void downloadDispatch() {
final okhttp3.Request request = new okhttp3.Request.Builder()
.url("url")
.header("X_SUBDOMAIN", "SUBDOMAIN")
.header("X-AUTH-TOKEN", "API_KEY")
.build();
Call call = client.newCall(request);
call.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
}
@Override
public void onResponse(Call call, okhttp3.Response response) throws IOException {
try {
String jsonData = response.body().string();
JSONObject getRootObject = new JSONObject(jsonData);
JSONObject metaObject = getRootObject.getJSONObject("meta");
final String row_count = metaObject.getString("total_row_count");
{
if (row_count.equals("0")) {
// do something for no jobs
} else {
JSONObject getArray = new JSONObject(jsonData);
JSONArray opportunitiesArray = getArray.getJSONArray("opportunities");
for (int i = 0; i < opportunitiesArray.length(); i++) {
JSONObject opportunity = opportunitiesArray.getJSONObject(i);
jobId = opportunity.getString("id");
jobTitle = opportunity.getString("subject");
jobNumber = opportunity.getString("number");
jobStartTime = opportunity.getString("starts_at");
dispatchType = opportunity.getString("customer_collecting");
// Take Strings from response and send them to JobsListItem
final JobsListItem item = new JobsListItem(jobId, jobTitle, jobNumber, jobStartTime, dispatchType);
// If the adapter hasn't been created, do this
if (mJobsOutAdapter == null) {
new Handler(Looper.getMainLooper()).post(new Runnable() {
@Override
public void run() {
mJobsOutAdapter = new JobsAdapter(dispatch, getContext());
jobsOutRecyclerView.setAdapter(mJobsOutAdapter);
dispatch.add(item);
}
});
}
// If the adapter has been created, just do this
else if (mJobsOutAdapter != null) {
new Handler(Looper.getMainLooper()).post(new Runnable() {
@Override
public void run() {
dispatch.add(item);
mJobsOutAdapter.notifyDataSetChanged();
}
});
}
}
}
}
} catch (IOException e) {
Log.e("TAG", "IO exception caught: ", e);
} catch (JSONException e) {
Log.e("TAG", "TAG exception caught: ", e);
}
}
});
}
适配器:
public class JobsAdapter extends RecyclerView.Adapter<JobsAdapter.ViewHolder> {
private List<JobsListItem> mJobsListItem;
private Context context;
public JobsAdapter(List<JobsListItem> mJobsListItem, Context context) {
this.mJobsListItem = mJobsListItem;
this.context = context;
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.jobs_listitem, parent, false);
return new ViewHolder(view);
}
@Override
public void onBindViewHolder(final ViewHolder holder, int position) {
final JobsListItem mJobsListItemViewHolder = this.mJobsListItem.get(position);
// holders go here and do things with text and what-not
}
@Override
public int getItemCount() {
return mJobsListItem.size();
}
public class ViewHolder extends RecyclerView.ViewHolder {
// BindView's with ButterKnife go here and all that jazz
public ViewHolder(View itemView) {
super(itemView);
ButterKnife.bind(this, itemView);
}
}
崩溃的Logcat:
26404-26404 E/AndroidRuntime: FATAL EXCEPTION: main
Process: uk.co.plasmacat.techmate, PID: 26404
java.lang.IndexOutOfBoundsException: Inconsistency detected. Invalid item position 4(offset:4).state:16
at android.support.v7.widget.RecyclerView$Recycler.tryGetViewHolderForPositionByDeadline(RecyclerView.java:5504)
at android.support.v7.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.java:5440)
at android.support.v7.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.java:5436)
at android.support.v7.widget.LinearLayoutManager$LayoutState.next(LinearLayoutManager.java:2224)
at android.support.v7.widget.LinearLayoutManager.layoutChunk(LinearLayoutManager.java:1551)
at android.support.v7.widget.LinearLayoutManager.fill(LinearLayoutManager.java:1511)
at android.support.v7.widget.LinearLayoutManager.scrollBy(LinearLayoutManager.java:1325)
at android.support.v7.widget.LinearLayoutManager.scrollVerticallyBy(LinearLayoutManager.java:1061)
at android.support.v7.widget.RecyclerView.scrollByInternal(RecyclerView.java:1695)
at android.support.v7.widget.RecyclerView.onTouchEvent(RecyclerView.java:2883)
at android.view.View.dispatchTouchEvent(View.java:10063)
at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2630)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2307)
at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2636)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2321)
at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2636)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2321)
at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2636)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2321)
at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2636)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2321)
at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2636)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2321)
at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2636)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2321)
at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2636)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2321)
at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2636)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2321)
at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2636)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2321)
at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2636)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2321)
at com.android.internal.policy.DecorView.superDispatchTouchEvent(DecorView.java:413)
at com.android.internal.policy.PhoneWindow.superDispatchTouchEvent(PhoneWindow.java:1819)
at android.app.Activity.dispatchTouchEvent(Activity.java:3127)
at android.support.v7.view.WindowCallbackWrapper.dispatchTouchEvent(WindowCallbackWrapper.java:71)
at android.support.v7.view.WindowCallbackWrapper.dispatchTouchEvent(WindowCallbackWrapper.java:71)
at com.android.internal.policy.DecorView.dispatchTouchEvent(DecorView.java:375)
at android.view.View.dispatchPointerEvent(View.java:10283)
at android.view.ViewRootImpl$ViewPostImeInputStage.processPointerEvent(ViewRootImpl.java:4522)
at android.view.ViewRootImpl$ViewPostImeInputStage.onProcess(ViewRootImpl.java:4353)
at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:3893)
at android.view.ViewRootImpl$InputStage.onDeliverToNext(ViewRootImpl.java:3946)
at android.view.ViewRootImpl$InputStage.forward(ViewRootImpl.java:3912)
at android.view.ViewRootImpl$AsyncInputStage.forward(ViewRootImpl.java:4039)
at android.view.ViewRootImpl$InputStage.apply(ViewRootImpl.java:3920)
at android.view.ViewRootImpl$AsyncInputStage.apply(ViewRootImpl.java:4096)
at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:3893)
at android.view.ViewRootImpl$InputStage.onDeliverToNext(ViewRootImpl.java:3946)
at android.view.ViewRootImpl$InputStage.forward(ViewRootImpl.java:3912)
at android.view.ViewRootImpl$InputStage.apply(ViewRootImpl.java:3920)
at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:3893)
at android.view.ViewRootImpl.deliverInputEvent(ViewRootImpl.java:6341)
at android.view.ViewRootImpl.doProcessInputEvents(ViewRootImpl.java:6315)
at android.view.ViewRootImpl.enqueueInputEvent(ViewRootImpl.java:6265)
at
android.view.ViewRootImpl$WindowInputEventReceiver.onInputEvent(ViewRootImpl.java:6444)
at android.view.InputEventReceiver.dispatchInputEvent(InputEventReceiver.java:185)
at android.view.InputEventReceiver.nativeConsumeBatchedInputEvents(Native Method)
at android.view.InputEventReceiver.consumeBatchedInputEvents(InputEventReceiver.java:176)
at android.view.ViewRootImpl.doConsumeBatchedInput(ViewRootImpl.java:6415)
at android.view.ViewRootImpl$ConsumeBatchedInputRunnable.run(ViewRootImpl.java:6467)
at android.view.Choreographer$CallbackRecord.run(Choreographer.java:874)
at android.view.Choreographer.doCallbacks(Choreographer.java:686)
at android.view.Choreographer.doFrame(Choreographer.java:615)
at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:860)
at android.os.Handler.handleCallback(Handler.java:751)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:154)
at android.app.ActivityThread.main(ActivityThread.java:6290)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:886)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:776)
如果您有时间,我将非常感谢您的帮助。
谢谢!
最佳答案
您尝试做的事很普遍,当回收者视图需要向其适配器索取数据(因为它已经滚动)并且其所需的位置在Adatper中不存在时,索引越界就会发生。例如:适配器尝试获取项目编号“ N”,并且数据包含N-1(或更小)。
大多数情况下,这是由于多种因素造成的:
穿线。所有这些(大部分情况下)都应该在UI线程上处理(通知和其他内容)。网络请求显然是在后台线程中发生的,我认为最终onResponse
现在又回到了主线程上(否则您将获得其他异常)。仔细检查我的测试Looper.getMainLooper() == Looper.myLooper()
(或类似内容)。
您正在主线程上做很多(不需要的)工作。您从网络收到响应,然后解析JSON并在主线程中创建对象……为什么不分担所有工作,一旦有了项目列表,就将其传递给适配器。
您每次都无法有效地拨打notifyDataSetChanged()
(这很糟糕)。为什么不使用(包含在Android中)DiffUtil
类仅通知更改的范围?请允许我为您提供一个很好的示例:https://guides.codepath.com/android/using-the-recyclerview#diffing-larger-changes
实施这些更改大约需要30分钟,这将使您的代码更健壮。
如果您使用RXJava使其成为流,则奖励积分:-)
注意:您应该创建一次适配器,然后在每次拥有新数据时只需调用setItems(your_list_of_items)
。 DiffUtil和适配器应该知道如何处理。您的活动/片段/网络代码中有很多不属于其中的“业务逻辑”。您的所有“ onResponse”方法应该做的就是准备数据,并将其传递给负责管理数据的类(适配器)。看到此// If the adapter hasn't been created, do this
时,我皱了皱眉。为什么此代码在此处创建适配器?谁来测试这个?如果用其他方法更改OKHttp怎么办? (为什么不使用改型并使其更简单?)。
我的意思是,您可以采取多种措施来简化程序员的生活,而不是利用现有的解决方案。