【问题标题】:Cannot call notifyItemInserted() from RecyclerView.OnScrollListener无法从 RecyclerView.OnScrollListener 调用 notifyItemInserted()
【发布时间】:2018-08-24 16:41:39
【问题描述】:

最近我将我的recyclerview-v7:23 升级到recyclerview-v7:24.2.0。我的应用程序有一个无穷无尽的滚动列表。当我将加载视图添加到RecyclerView 时,错误消息指向notifyItemInserted 行(空对象表示正在加载,id 0 为空,-1 为页面末尾)并且之前它工作正常(recyclerview-v7:23 ) 但突然我得到了这样的错误,不知何故我的加载显示了两次,然后当它删除一个时,顶部仍然可见一个加载。

    W/RecyclerView: Cannot call this method in a scroll callback. Scroll callbacks might be run during a measure & layout pass where you cannot change the RecyclerView data. Any method call that might change the structure of the RecyclerView or the adapter contents should be postponed to the next frame.java.lang.IllegalStateException:  
 at android.support.v7.widget.RecyclerView.assertNotInLayoutOrScroll(RecyclerView.java:2403)
     at android.support.v7.widget.RecyclerView$RecyclerViewDataObserver.onItemRangeInserted(RecyclerView.java:4631)
     at android.support.v7.widget.RecyclerView$AdapterDataObservable.notifyItemRangeInserted(RecyclerView.java:10469)
     at android.support.v7.widget.RecyclerView$Adapter.notifyItemInserted(RecyclerView.java:6211)
     at com.sketchproject.infogue.fragments.MessageFragment.loadMessages(MessageFragment.java:109)
     at com.sketchproject.infogue.fragments.MessageFragment.access$100(MessageFragment.java:42)
     at com.sketchproject.infogue.fragments.MessageFragment$1.onLoadMore(MessageFragment.java:87)
     at com.sketchproject.infogue.modules.EndlessRecyclerViewScrollListener.onScrolled(EndlessRecyclerViewScrollListener.java:74)

当我删除部分错误(添加加载)时,它又可以正常工作了,我不知道为什么,新版本的 recyclerview 阻止将数据添加到适配器中太快,或者当“measure & recyclerview 的布局”完成,这是我的代码

private void loadArticles(final int page) {
        if (!isEndOfPage && apiArticleUrl != null) {
            if (swipeRefreshLayout == null || !swipeRefreshLayout.isRefreshing()) {
                allArticles.add(null);
                articleAdapter.notifyItemInserted(allArticles.size() - 1); // error here
            }

            JsonObjectRequest articleRequest = new JsonObjectRequest(Request.Method.GET, apiArticleUrl, null,
                    new Response.Listener<JSONObject>() {
                        @Override
                        public void onResponse(JSONObject response) {

                            try {
                                String status = response.getString("status");
                                JSONObject articles = response.getJSONObject("articles");

                                String nextUrl = articles.getString("next_page_url");
                                int currentPage = articles.getInt("current_page");
                                int lastPage = articles.getInt("last_page");
                                JSONArray data = articles.optJSONArray("data");

                                apiArticleUrl = nextUrl;

                                if (status.equals(APIBuilder.REQUEST_SUCCESS)) {
                                    if (swipeRefreshLayout == null || !swipeRefreshLayout.isRefreshing()) {
                                        allArticles.remove(allArticles.size() - 1);
                                        articleAdapter.notifyItemRemoved(allArticles.size());
                                    } else {
                                        swipeRefreshLayout.setRefreshing(false);
                                        int total = allArticles.size();
                                        for (int i = 0; i < total; i++) {
                                            allArticles.remove(0);
                                        }
                                        articleAdapter.notifyItemRangeRemoved(0, total);
                                    }

                                    List<Article> moreArticles = new ArrayList<>();

                                    if (data != null) {
                                        for (int i = 0; i < data.length(); i++) {
                                            JSONObject articleData = data.getJSONObject(i);
                                            Article article = new Article();
                                            article.setId(articleData.getInt(Article.ID));
                                            article.setSlug(articleData.getString(Article.SLUG));
                                            article.setTitle(articleData.getString(Article.TITLE));
                                            article.setFeatured(articleData.getString(Article.FEATURED_REF));
                                            article.setCategoryId(articleData.getInt(Article.CATEGORY_ID));
                                            article.setCategory(articleData.getString(Article.CATEGORY));
                                            article.setSubcategoryId(articleData.getInt(Article.SUBCATEGORY_ID));
                                            article.setSubcategory(articleData.getString(Article.SUBCATEGORY));
                                            article.setContent(articleData.getString(Article.CONTENT));
                                            article.setContentUpdate(articleData.getString(Article.CONTENT_UPDATE));
                                            article.setPublishedAt(articleData.getString(Article.PUBLISHED_AT));
                                            article.setView(articleData.getInt(Article.VIEW));
                                            article.setRating(articleData.getInt(Article.RATING_TOTAL));
                                            article.setStatus(articleData.getString(Article.STATUS));
                                            moreArticles.add(article);
                                        }
                                    }

                                    int curSize = articleAdapter.getItemCount();
                                    allArticles.addAll(moreArticles);

                                    if (allArticles.size() <= 0) {
                                        Log.i("INFOGUE/Article", "Empty on page " + page);
                                        isEndOfPage = true;
                                        Article emptyArticle = new Article(0, null, "Empty page");
                                        allArticles.add(emptyArticle);
                                    } else if (currentPage >= lastPage) {
                                        Log.i("INFOGUE/Article", "End on page " + page);
                                        isEndOfPage = true;
                                        Article endArticle = new Article(-1, null, "End of page");
                                        allArticles.add(endArticle);
                                    }

                                    articleAdapter.notifyItemRangeInserted(curSize, allArticles.size() - 1);
                                } else {
                                    Log.i("INFOGUE/Article", "Error on page " + page);
                                    Helper.toastColor(getContext(), R.string.error_unknown, R.color.color_warning_transparent);

                                    isEndOfPage = true;
                                    Article failureArticle = new Article();
                                    failureArticle.setId(-2);
                                    failureArticle.setTitle(getString(R.string.error_unknown));
                                    allArticles.add(failureArticle);
                                }
                            } catch (JSONException e) {
                                e.printStackTrace();
                            }
                        }
                    },
                    new Response.ErrorListener() {
                        @Override
                        public void onErrorResponse(VolleyError error) {
                            error.printStackTrace();

                            if (swipeRefreshLayout != null && swipeRefreshLayout.isRefreshing()) {
                                swipeRefreshLayout.setRefreshing(false);
                            }

                            // remove last loading
                            allArticles.remove(allArticles.size() - 1);
                            articleAdapter.notifyItemRemoved(allArticles.size());

                            String errorMessage = getString(R.string.error_unknown);
                            NetworkResponse networkResponse = error.networkResponse;
                            if (networkResponse == null) {
                                if (error.getClass().equals(TimeoutError.class)) {
                                    errorMessage = getString(R.string.error_timeout);
                                } else if (error.getClass().equals(NoConnectionError.class)) {
                                    errorMessage = getString(R.string.error_no_connection);
                                }
                            } else {
                                if (networkResponse.statusCode == 404) {
                                    errorMessage = getString(R.string.error_not_found);
                                } else if (networkResponse.statusCode == 500) {
                                    errorMessage = getString(R.string.error_server);
                                } else if (networkResponse.statusCode == 503) {
                                    errorMessage = getString(R.string.error_maintenance);
                                }
                            }
                            Helper.toastColor(getContext(), errorMessage, R.color.color_danger_transparent);

                            // add error view holder
                            isEndOfPage = true;
                            Article errorArticle = new Article();
                            errorArticle.setId(-2);
                            errorArticle.setTitle(errorMessage);
                            allArticles.add(errorArticle);
                        }
                    }
            );

            articleRequest.setTag("articles");
            articleRequest.setRetryPolicy(new DefaultRetryPolicy(
                    APIBuilder.TIMEOUT_SHORT,
                    DefaultRetryPolicy.DEFAULT_MAX_RETRIES,
                    DefaultRetryPolicy.DEFAULT_BACKOFF_MULT));
            VolleySingleton.getInstance(getContext()).addToRequestQueue(articleRequest);
        }
    }

该代码是从 onCreate 方法调用的

    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
            View view = inflater.inflate(R.layout.fragment_article_list, container, false);

            // Set the adapter
            if (view instanceof RecyclerView) {
                Context context = view.getContext();
                recyclerView = (RecyclerView) view;

                // determine column of list
                LinearLayoutManager linearLayoutManager;
                if (mColumnCount <= 1) {
                    linearLayoutManager = new LinearLayoutManager(context);
                } else {
                    linearLayoutManager = new GridLayoutManager(context, mColumnCount);
            }

        // if article list authored by logged user then prefer editable view holder
        if (mMyArticle) {
            articleAdapter = new ArticleRecyclerViewAdapter(allArticles, mArticleListListener, mArticleEditableListener);
        } else {
            articleAdapter = new ArticleRecyclerViewAdapter(allArticles, mArticleListListener, hasHeader);
        }

        // set the adapter and attach custom scroll listener that triggered onLoadMore() and onReachTop()
        recyclerView.setAdapter(articleAdapter);
        recyclerView.setLayoutManager(linearLayoutManager);
        recyclerView.addOnScrollListener(new EndlessRecyclerViewScrollListener(linearLayoutManager) {
            @Override
            public void onLoadMore(final int page, int totalItemsCount) {
                if (!isFirstCall) {
                    loadArticles(page);
                }
            }

            @Override
            public void onReachTop(boolean isFirst) {
                // activate swipe function when list reach top only, find out where do fragment attached
                if (getActivity() instanceof ArticleActivity) {
                    ((ArticleActivity) getActivity()).setSwipeEnable(isFirst);
                } else if (getActivity() instanceof ApplicationActivity) {
                    ((ApplicationActivity) getActivity()).setSwipeEnable(isFirst);
                }
            }
        });

        if (isFirstCall) {
            isFirstCall = false;
            loadArticles(0);
        }
    }
    return view;
} 

我的问题是:

  1. 问题是否来自新版本的RecyclerView
  2. 在滚动监听器中实现notifyItemInserted 有错吗?它以前工作过。
  3. 我该如何解决这个问题?

更新

when I logged the code inside first call and scroll,
09-12 03:49:10.078 7046-7046/com.sketchproject.infogue I/Infogue/Contributor: Followers URL http://192.168.43.141:8000/api/contributor/support/followers?contributor_id=1
09-12 03:49:26.421 7046-7046/com.sketchproject.infogue I/Infogue/Contributor: Followers first call
09-12 03:49:26.421 7046-7046/com.sketchproject.infogue I/Infogue/Contributor: Followers URL http://192.168.43.141:8000/api/contributor/support/followers?contributor_id=1
09-12 03:49:26.617 7046-7046/com.sketchproject.infogue I/Infogue/Contributor: Followers second call (scroll)
09-12 03:49:26.618 7046-7046/com.sketchproject.infogue I/Infogue/Contributor: Followers URL http://192.168.43.141:8000/api/contributor/support/followers?contributor_id=1
09-12 03:49:27.365 7046-7046/com.sketchproject.infogue I/Infogue/Contributor: Followers second call (scroll)

它们在第一次加载时被调用两次,在第一次调用并添加加载视图后,滚动被触发并再次调用。

【问题讨论】:

  • 你找到解决方案了吗,如果有,可以发一下吗?

标签: android android-recyclerview


【解决方案1】:

您也可以使用视图发布。

   recyclerView.post(new Runnable() {
        public void run() {
            articleAdapter.notifyItemInserted(allArticles.size() - 1);
        }
    });

【讨论】:

  • 谨防在 UI 线程之外修改 recyclerView 适配器...这可能会导致恼人的 java.lang.IndexOutOfBoundsException: Inconsistency detected。如果在其他地方修改了适配器,则项位置无效。
  • 只是给 +1 @FlorianT 警告的快速消息,此异常将会发生。
【解决方案2】:
  1. 问题不在于新版本的 Recyclerview。

2 & 3. 您不能在设置时更改项目(通过调用 onBindViewHolder)。在这种情况下,您必须通过调用 Handler.post() 在当前循环结束时调用 notifyItemInserted

Handler handler = new Handler();

    final Runnable r = new Runnable() {
        public void run() {
            articleAdapter.notifyItemInserted(allArticles.size() - 1);
        }
    };

    handler.post(r);

希望能解决你的问题。

【讨论】:

  • recyclerView.addOnScrollListener(new EndlessRecyclerViewScrollListener(linearLayoutManager) { @Override public void onLoadMore(final int page, int totalItemsCount) { if (!isFirstCall) { loadArticles(page); } } }); 但是当活动出现时它被调用了两次,有一种方法可以防止在第一次请求完成后移动isFirstCall = false;(成功/错误)然后我认为最新的 RecyclerView 有不同onScroll 行为
  • 当屏幕下方还有 4 个项目时,您应该调用 loadArticles(page) 方法。关注这篇文章guides.codepath.com/android/…
  • 是的,我按照那个教程做的,之前效果很好,但是现在问题已经解决了..
  • 好的,太好了!!
  • 究竟是什么问题?
【解决方案3】:

您也可以使用 Google Play 服务的Tasks API

Tasks.call(new Callable<Void>() {
    @Override
    public Void call() throws Exception {
        allArticles.add(null);
        articleAdapter.notifyItemInserted(allArticles.size() - 1);
        return null;
    }
});

【讨论】:

  • 如果Android SDK和java都为你提供了必要的工具,为什么还要使用谷歌的Tasks API,这似乎更自然。
【解决方案4】:

在某些情况下 onScrollStateChanged 可能就足够了

override fun onScrollStateChanged(recyclerView: RecyclerView?, newState: Int) {
    super.onScrollStateChanged(recyclerView, newState)
    adapter.notifyDataSetChanged()
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-11-30
    • 2016-01-13
    相关资源
    最近更新 更多