【问题标题】:RecyclerView causes force stop while refreshingRecyclerView 在刷新时导致强制停止
【发布时间】:2017-03-18 15:25:56
【问题描述】:

我正在使用来自 library. 的 Recyclerview

当我刷新 recyclerview 并同时滚动时,应用程序将强制关闭。

public class NewsFeed extends Fragment implements MyDynamicRecyclerView.SwipeRefreshListener, MyDynamicRecyclerView.LoadMoreListener {

MyDynamicRecyclerView myRecyclerview;
NewsFeedAdapter testAdapter;
ArrayList<ProgramModel> modelArrayList;
LinearLayoutManager linearLayoutManager;
Dialog dialog;
int limit;
private boolean firstSwipe;


public NewsFeed() {
    // Required empty public constructor
}

@Override
public void onDestroy() {
    super.onDestroy();
    Log.i("NewsFeedFragment", "Destroyed");
    if (dialog != null)
        dialog.dismiss();
}

@Override
public void onPause() {
    super.onPause();
    Log.i("NewsFeedFragment", "Paused");
    if (dialog != null)
        dialog.dismiss();
}

@Override
public void onResume() {
    super.onResume();
    Log.i("NewsFeedFragment", "Resumed");
    firstSwipe = false;
    if (checkInternetForNewsFeed() && firstSwipe) {
        removeItems();
        new FetchBooks(false).execute();
        firstSwipe = false;
        Log.d("Swipe", "First swipe");
    } else if (checkInternetForNewsFeed() && !firstSwipe) {
        fetchAllBooksByHeader();
        firstSwipe = true;
        Log.d("Swipe", "Not first swipe");
    } else {
        myRecyclerview.showInfoLayout();
    }

    getActivity().setTitle("Të gjithë");
}

public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {

    Log.i("NewsFeedFragment", "Created");

    View v = inflater.inflate(R.layout.news_feed_fragment_layout, container, false);

    dialog = new Dialog(getActivity());
    dialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
    dialog.getWindow().setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
    dialog.setContentView(R.layout.loading_layout);

    myRecyclerview = (MyDynamicRecyclerView) v.findViewById(R.id.myRecyclerview);

    modelArrayList = new ArrayList<>();
    testAdapter = new NewsFeedAdapter(getActivity().getApplicationContext(), modelArrayList, (AppCompatActivity) getActivity(), this, false);

    linearLayoutManager = new LinearLayoutManager(getActivity().getApplicationContext());

    myRecyclerview.setScrollContainer(false);
    myRecyclerview.setBackgroundColor("#FFFFFF");
    myRecyclerview.setSwipeRefresh(true);
    myRecyclerview.setColorSchemeColors(Color.RED, Color.YELLOW, Color.BLACK, Color.GREEN, Color.BLUE);
    myRecyclerview.setLoadMore(true, linearLayoutManager);


    myRecyclerview.setLoadMoreListener(this);
    myRecyclerview.setOnRefreshListener(this);


    myRecyclerview.setLayoutManager(linearLayoutManager);
    myRecyclerview.setItemAnimator(new DefaultItemAnimator());
    myRecyclerview.setSimpleDivider(false);
    myRecyclerview.setAdapter(testAdapter);


    limit = 0;
    if (checkInternetForNewsFeed()) {
        firstSwipe = false;
        fetchAllBooksFromDB();
    } else {
        firstSwipe = true;
        myRecyclerview.showInfoLayout();
    }

    return v;
}

public void fetchAllBooksByHeader() {
    limit = 0;
    if (modelArrayList.size() >= 1) {
        removeItems();
    }

    new FetchBooks(true).execute();
    testAdapter.notifyDataSetChanged();
}

public void fetchAllBooksFromDB() {
    if (modelArrayList.size() >= 1) {
        removeItems();
    }

    new FetchBooks(false).execute();
    testAdapter.notifyDataSetChanged();
}

public void addHeader() {
    if (modelArrayList.size() == 0)
        modelArrayList.add(new ProgramModel());
}

public void addItem(String title, String author, String imageName) {
    modelArrayList.add(new ProgramModel(title, author, imageName));
    testAdapter.notifyDataSetChanged();
}

public void removeItems() {
    modelArrayList.clear();
    addHeader();
}

@Override
public void OnRefresh() {

    getActivity().setTitle("Të gjithë");
    limit = 0;
    if (checkInternetForNewsFeed() && firstSwipe) {
        removeItems();
        new FetchBooks(false).execute();
        firstSwipe = false;
        Log.d("Swipe", "First swipe");
    } else if (checkInternetForNewsFeed() && !firstSwipe) {
        removeItems();
        new FetchBooks(true).execute();
        Log.d("Swipe", "Not first swipe");
    } else {
        myRecyclerview.showInfoLayout();
    }

    myRecyclerview.stopSwipeRefresh();


}

private boolean checkInternetForNewsFeed() {
    if (new ConnectivityState(getActivity()).isConnected()) {
        myRecyclerview.hideInfoLayout();
        return true;
    } else {
        // Fonts for the title and the message of the content
        Typeface messageTypeface = Typeface.createFromAsset(getActivity().getAssets(), "fonts/Roboto-Light.ttf");
        Typeface titleTypeface = Typeface.createFromAsset(getActivity().getAssets(), "fonts/Roboto-Bold.ttf");

        // Setting up the content
        myRecyclerview.setInfoIcon(R.drawable.warning_icon, "#000000");
        myRecyclerview.setInfoTitle("Nuk jeni i lidhur me internet.", 18, Color.GRAY, titleTypeface);
        myRecyclerview.setInfoMessage("Rifreskoni edhe njëherë për të kontrolluar aksesin në internet.", 13, Color.GRAY, messageTypeface);
        return false;
    }
}

@Override
public void OnLoadMore() {
    new FetchBooks(true).execute();
    MyDynamicToast.informationMessage(AppController.getInstance(), "...");
}

public static void hideLoadingDialogFromBookActivity(Dialog d) {
    d.hide();
}

private class FetchBooks extends AsyncTask<Void, Void, Void> {

    private boolean reload;

    FetchBooks(boolean reload) {
        this.reload = reload;
    }

    @Override
    protected void onPreExecute() {

    }

    @Override
    protected Void doInBackground(Void... params) {
        getBooksFromDB();
        return null;
    }

    private void getBooksFromDB() {
        StringRequest requestBooks = new StringRequest(Request.Method.GET, AppConfig.URL_FETCH_BOOKS, new Response.Listener<String>() {
            @Override
            public void onResponse(String response) {
                try {
                    JSONArray jsonArray = new JSONArray(response);

                    for (int i = limit; i < jsonArray.length(); i++) {
                        if (i < (limit + 5)) {
                            JSONObject jsonObject = jsonArray.getJSONObject(i);
                            String imageName = "http://ec2-52-39-232-168.us-west-2.compute.amazonaws.com/files/books/" + jsonObject.getString("cover");
                            modelArrayList.add(new ProgramModel(jsonObject.getString("title"), jsonObject.getString("author"), imageName));
                        } else {
                            break;
                        }
                    }
                    limit += 5;
                    if (reload) {
                        testAdapter.notifyDataSetChanged();
                    }
                } catch (JSONException e) {
                    e.printStackTrace();
                    MyDynamicToast.errorMessage(AppController.getInstance(), "" + e.getMessage());
                }
            }
        }, new Response.ErrorListener() {
            @Override
            public void onErrorResponse(VolleyError error) {
                MyDynamicToast.errorMessage(AppController.getInstance(), "Request did not work!");
            }
        });

        AppController.getInstance().addToRequestQueue(requestBooks);

    }

}

}

所以基本上我将 onRefreshListener 添加到 recyclerView 并调用“onRefresh”。在这个类中,我创建了一个内部类,它将从特定 url 获取一些数据并添加填充了从该 url 接收到的数据的项目。 当我在滚动时尝试刷新时,它给了我这个:

03-18 16:11:22.855 5970-5970/com.libraryhf.libraryharryfultz E/AndroidRuntime: 致命异常: main 进程:com.libraryhf.libraryharryfultz,PID:5970 java.lang.IndexOutOfBoundsException:检测到不一致。无效的项目位置 4(offset:4).state:10 在 android.support.v7.widget.RecyclerView$Recycler.tryGetViewHolderForPositionByDeadline(RecyclerView.java:5458) 在 android.support.v7.widget.GapWorker.prefetchPositionWithDeadline(GapWorker.java:270) 在 android.support.v7.widget.GapWorker.flushTaskWithDeadline(GapWorker.java:324) 在 android.support.v7.widget.GapWorker.flushTasksWithDeadline(GapWorker.java:337) 在 android.support.v7.widget.GapWorker.prefetch(GapWorker.java:344) 在 android.support.v7.widget.GapWorker.run(GapWorker.java:370) 在 android.os.Handler.handleCallback(Handler.java:751) 在 android.os.Handler.dispatchMessage(Handler.java:95) 在 android.os.Looper.loop(Looper.java:154) 在 android.app.ActivityThread.main(ActivityThread.java:6724) 在 java.lang.reflect.Method.invoke(本机方法) 在 com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1520) 在 com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1410)

我在互联网上查找了一些信息,声称这是一个谷歌应该解决的问题。我已经尝试了几天来解决这个问题,但没有任何结果。

如果有人能指出正确的方向,我将不胜感激:解决这个问题或至少处理它。非常感谢!

【问题讨论】:

    标签: android asynchronous android-asynctask android-recyclerview


    【解决方案1】:

    在适配器上设置列表后不要修改它。尤其是不是来自不同的线程。


    getBooksFromDB 中,您调用modelArrayList.add(),这很可能是您崩溃的原因,因为您从另一个线程修改了支持您的适配器的列表。

    当您调用 notifyDataSetChanged 时,它会在您实际开始修改列表之前调用,因为修改发生在后台。

    new FetchBooks(false).execute(); // runs in background and modifies list
    testAdapter.notifyDataSetChanged(); // executes immediately
    

    如果您需要修改此列表(很可能不需要),您应该立即通知适配器更改并且必须发生更改在 ui 线程上
    您还可以使用notifyItem*methods 更新您的适配器什么,例如notifyItemRangeInserted。或者您可以查看 DiffUtil 以帮助您更新适配器。


    处理列表更改的最常见方法是交换支持适配器的整个列表,然后调用notifyDataSetChanged

    创建一个 new 列表,将您的项目放入其中,然后将新列表传递给您的适配器。在您的适配器中放置类似...

    void setBooks(List<Book> books) {
      mBooks = books; // swap the whole list
      notifyDataSetChanged(); // notify of change
    }
    

    ...你不会遇到这个问题。

    【讨论】:

    • 我不太明白你解释的第一部分。那么我什么时候可以在 FetchBooks 内部类中或在 new FetchBooks(false).execute() 之后通知DataSetChanged()?
    • 两者都不是。在您的 AsyncTask 中调用它是错误的线程,并且在启动任务执行后直接调用它在您实际修改列表之前。如前所述,您应该修改列表,而是创建一个您作为一个整体交换的新列表,或者使用不同的方式,如notifyItemInserted(但实施起来有点困难)
    • 我不能使用 notifyItemInserted 因为我使用的 RecyclerView 库没有那个功能。还有什么办法吗?
    • 这是适配器上的一个方法,所以应该是可用的。你可以随时交换整个列表,如图所示
    猜你喜欢
    • 2012-05-25
    • 1970-01-01
    • 2021-03-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-11-28
    相关资源
    最近更新 更多