IllegalStateException: Cannot call this method while RecyclerView is computing a layout or scrolling

时间:2019-01-31
本文章向大家介绍IllegalStateException: Cannot call this method while RecyclerView is computing a layout or scrolling,主要包括IllegalStateException: Cannot call this method while RecyclerView is computing a layout or scrolling使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

最近在使用RecyclerViewAdapter的时候遇到这个问题,这里总结记录一下,先看一下报错日志:

原因是我在Adapter中的item放了一个CheckBox, 并给CheckBox设置了监听,当选中状态改变的时候,在监听回调中直接调用notifyItemChanged就会出现这个问题。代码如下:

    @Override
    protected void convert(final BaseViewHolder helper, ContactDataSection item) {
        final ContactMobileManage mobileContact = item.getEntity();
        if (mobileContact != null) {
            ....
            CheckBox checkBox = helper.getView(R.id.ckb_check);
            checkBox.setChecked(mobileContact.isChecked());
            checkBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
                    @Override
                    public void onCheckedChanged(CompoundButton buttonView, final boolean isChecked) {
                      	    mobileContact.setChecked(isChecked);
                            notifyItemChanged(helper.getAdapterPosition());
                    }
            });
        }
    }

我们在RecyclerView源码中找到错误日志中提到的assertNotInLayoutOrScroll方法看一下:

 /**
     * Checks if RecyclerView is in the middle of a layout or scroll and throws an
     * {@link IllegalStateException} if it <b>is</b>.
     *
     * @param message The message for the exception. Can be null.
     * @see #assertInLayoutOrScroll(String)
     */
    void assertNotInLayoutOrScroll(String message) {
        if (isComputingLayout()) {
            if (message == null) {
                throw new IllegalStateException("Cannot call this method while RecyclerView is "
                        + "computing a layout or scrolling" + exceptionLabel());
            }
            throw new IllegalStateException(message);
        }
        if (mDispatchScrollCounter > 0) {
            Log.w(TAG, "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.",
                    new IllegalStateException("" + exceptionLabel()));
        }
    }

可以看到第一行isComputingLayout()方法为true的时候就会抛出这个异常,我们再看一下isComputingLayout()方法的代码:

    /**
     * Returns whether RecyclerView is currently computing a layout.
     * <p>
     * If this method returns true, it means that RecyclerView is in a lockdown state and any
     * attempt to update adapter contents will result in an exception because adapter contents
     * cannot be changed while RecyclerView is trying to compute the layout.
     * <p>
     * It is very unlikely that your code will be running during this state as it is
     * called by the framework when a layout traversal happens or RecyclerView starts to scroll
     * in response to system events (touch, accessibility etc).
     * <p>
     * This case may happen if you have some custom logic to change adapter contents in
     * response to a View callback (e.g. focus change callback) which might be triggered during a
     * layout calculation. In these cases, you should just postpone the change using a Handler or a
     * similar mechanism.
     *
     * @return <code>true</code> if RecyclerView is currently computing a layout, <code>false</code>
     *         otherwise
     */
    public boolean isComputingLayout() {
        return mLayoutOrScrollCounter > 0;
    }

它这里的注释写的很清楚了,当这个方法返回值为true时,RecyclerView会处于锁定状态,这时任何尝试更新Adapter内容的方法都将导致异常,因为RecyclerView在计算layout的时候不允许你更新Adapter内容。

后面它又说,你的代码不太可能处于该状态下运行,因为这个状态只有系统framework调用或者响应系统事件的时候才会发生(我信你个鬼哦,可它就是发生了啊,真是shit happens…)

最后,它又补充了一句,说如果确实发生了,那有可能是在你设置了View callback的情况下(如focus change callback),这时你需要使用Handler或类似的方法进行延时处理。

好吧。。。最后它还是承认了自己确实给开发者挖了一个坑。。。不知道RecyclerView还挖了多少坑

接下来就是我们的填坑时间了

解决方法:

    @Override
    protected void convert(final BaseViewHolder helper, ContactDataSection item) {
        final ContactMobileManage mobileContact = item.getEntity();
        if (mobileContact != null) {
        	...
            CheckBox checkBox = helper.getView(R.id.ckb_check);
            checkBox.setChecked(mobileContact.isChecked());
            checkBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
                @Override
                public void onCheckedChanged(CompoundButton buttonView, final boolean isChecked) {
                    if (mRecyclerView.isComputingLayout()) {
                        mRecyclerView.post(new Runnable() {
                            @Override
                            public void run() {
                                mobileContact.setChecked(isChecked);
                                notifyItemChanged(helper.getAdapterPosition());
                            }
                        });
                    } else {
                        mobileContact.setChecked(isChecked);
                        notifyItemChanged(helper.getAdapterPosition());
                    }
                }
            });
        }
    }

解决方法比较简单,既然RecyclerView已经给了判断方法了,那我们在onCheckedChanged方法中直接调用isComputingLayout()判断一下就可以了,如果该方法返回为true我们需要进行延时处理这里我直接调用RecyclerView对象的post方法或者你用Handler处理也可以。