A, problem,

When using RecyclerView Java happened. Lang. An IllegalStateException: Cannot call this method while RecyclerView is computing a layout or scrolling crashed It is because of the drawing or sliding operation in the process of RecyclerView.

Second, the analysis

RecyclerView is used in engineering scenarios where there is a list of images with selected and unselected states. Each time an image is clicked, determine whether it is selected and call notifyItemChanged to update the UI state. There was no error on the first click, but an error on the second click. You can determine that the problem is caused by calling the notifyItemChanged method, and then look at the specific log to find the cause.

java.lang.IllegalStateException: Cannot call this method while RecyclerView is computing a layout or scrolling androidx.recyclerview.widget.RecyclerView{e51744b VFED..... ......ID 0,280-1440,2953 #7f09017f
app:id/rv_list}, adapter GalleryAdapter@71f8e28, layout:androidx.recyclerview.widget.GridLayoutManager@afe741, context:com.xx.xx.xx.activity.GalleryActivity@f0f49e5
        at androidx.recyclerview.widget.RecyclerView.assertNotInLayoutOrScroll(RecyclerView.java:3051)
        at androidx.recyclerview.widget.RecyclerView$RecyclerViewDataObserver.onItemRangeChanged(RecyclerView.java:5547)
        at androidx.recyclerview.widget.RecyclerView$AdapterDataObservable.notifyItemRangeChanged(RecyclerView.java:12268)
        at androidx.recyclerview.widget.RecyclerView$AdapterDataObservable.notifyItemRangeChanged(RecyclerView.java:12258)
        at androidx.recyclerview.widget.RecyclerView$Adapter.notifyItemChanged(RecyclerView.java:7370)
        at com.xx.xx.xx.xx.GalleryAdapter$3.onCheckedChanged(GalleryAdapter.java:89)
        at android.widget.CompoundButton.setChecked(CompoundButton.java:180)
        at com.xx.xx.xx.xx.GalleryAdapter.onBindItem(GalleryAdapter.java:72)
        at com.xx.xx.xx.xx.GalleryAdapter.onBindItem(GalleryAdapter.java:23)
        at com.xx.common.base.adapter.BaseSingleBindingAdapter.onBindViewHolder(BaseSingleBindingAdapter.java:47)
        at com.xx.common.base.adapter.BaseSingleBindingAdapter.onBindViewHolder(BaseSingleBindingAdapter.java:24)
        at androidx.recyclerview.widget.RecyclerView$Adapter.onBindViewHolder(RecyclerView.java:7065)
        at androidx.recyclerview.widget.RecyclerView$Adapter.bindViewHolder(RecyclerView.java:7107)
        at androidx.recyclerview.widget.RecyclerView$Recycler.tryBindViewHolderByDeadline(RecyclerView.java:6012)
        at androidx.recyclerview.widget.RecyclerView$Recycler.tryGetViewHolderForPositionByDeadline(RecyclerView.java:6279)
        at androidx.recyclerview.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.java:6118)
        at androidx.recyclerview.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.java:6114)
        at androidx.recyclerview.widget.LinearLayoutManager$LayoutState.next(LinearLayoutManager.java:2303)
        at androidx.recyclerview.widget.GridLayoutManager.layoutChunk(GridLayoutManager.java:561)
        at androidx.recyclerview.widget.LinearLayoutManager.fill(LinearLayoutManager.java:1587)
        at androidx.recyclerview.widget.LinearLayoutManager.onLayoutChildren(LinearLayoutManager.java:665)
        at androidx.recyclerview.widget.GridLayoutManager.onLayoutChildren(GridLayoutManager.java:170)
        at androidx.recyclerview.widget.RecyclerView.dispatchLayoutStep2(RecyclerView.java:4134)
        at androidx.recyclerview.widget.RecyclerView.dispatchLayout(RecyclerView.java:3851)
        at androidx.recyclerview.widget.RecyclerView.onLayout(RecyclerView.java:4404)
        at android.view.View.layout(View.java:22152)
        at android.view.ViewGroup.layout(ViewGroup.java:6290)
        at androidx.constraintlayout.widget.ConstraintLayout.onLayout(ConstraintLayout.java:1855)
        at android.view.View.layout(View.java:22152)
        at android.view.ViewGroup.layout(ViewGroup.java:6290)
        at android.widget.FrameLayout.layoutChildren(FrameLayout.java:332)
        at android.widget.FrameLayout.onLayout(FrameLayout.java:270)
        at android.view.View.layout(View.java:22152)
        at android.view.ViewGroup.layout(ViewGroup.java:6290)
        at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1829)
        at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1673)
        at android.widget.LinearLayout.onLayout(LinearLayout.java:1582)
        at android.view.View.layout(View.java:22152)
        at android.view.ViewGroup.layout(ViewGroup.java:6290)
        at android.widget.FrameLayout.layoutChildren(FrameLayout.java:332)
        at android.widget.FrameLayout.onLayout(FrameLayout.java:270)
        at android.view.View.layout(View.java:22152)
        at android.view.ViewGroup.layout(ViewGroup.java:6290)
        at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1673)
        at android.widget.LinearLayout.onLayout(LinearLayout.java:1582)
        at android.view.View.layout(View.java:22152)
        at android.view.ViewGroup.layout(ViewGroup.java:6290)
        at android.widget.FrameLayout.layoutChildren(FrameLayout.java:332)
        at android.widget.FrameLayout.onLayout(FrameLayout.java:270)
        at com.android.internal.policy.DecorView.onLayout(DecorView.java:786)
        at android.view.View.layout(View.java:22152)
        at android.view.ViewGroup.layout(ViewGroup.java:6290)
        at android.view.ViewRootImpl.performLayout(ViewRootImpl.java:3338)
        at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:2815)
        at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:1935)
        at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:8055)
        at android.view.Choreographer$CallbackRecord.run(Choreographer.java:1227)
        at android.view.Choreographer.doCallbacks(Choreographer.java:1029)
        at android.view.Choreographer.doFrame(Choreographer.java:942)
        at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:1208)
        at android.os.Handler.handleCallback(Handler.java:883)
        at android.os.Handler.dispatchMessage(Handler.java:100)
        at android.os.Looper.loop(Looper.java:214)
        at android.app.ActivityThread.main(ActivityThread.java:7707)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:516)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:950)

Copy the code

In RecyclerView, when RecyclerView Adapter to update data, according to the process executes assertNotInLayoutOrScroll () this method. As can be seen from the name of the method, it is used to check whether layout or scroll is being used.

void assertInLayoutOrScroll(String message) {
        if (!isComputingLayout()) {
            if (message == null) {
                throw new IllegalStateException("Cannot call this method unless RecyclerView is "
                        + "computing a layout or scrolling");
            }
            throw new IllegalStateException(message);
        }
 }
Copy the code

Where, isComputingLayout() is used to determine

  public boolean isComputingLayout() {
      return mLayoutOrScrollCounter > 0;
  }
Copy the code

And the mLayoutOrScrollCounter variable or flag bit is going to be mLayoutOrScrollCounter++ when you start drawing; MLayoutOrScrollCounter — when exiting the drawing. That is, the next drawing or sliding begins before the last drawing or sliding is finished. Corresponding to the application scenario in the project, I clicked the picture to start the next update before the last update of the picture display state was finished. Specifically, the problem is caused by drawing updates, not sliding.

Three,

To understand Android messaging, you should know that UI refreshes in Android are actually implemented through the Handler messaging mechanism. Through the distribution of messages, thus different processing. As you can see from the last few lines in the Log, the last few lines correspond to the code execution flow after calling the notifyItemChanged method.

 at android.os.Handler.handleCallback(Handler.java:883)
 at android.os.Handler.dispatchMessage(Handler.java:100)
 at android.os.Looper.loop(Looper.java:214)
Copy the code

A Runnable method block is placed in MessageQueue via handler. post to wait for execution. The main thread’s Looper loops through messages in this queue. In general, this process is sequential. Wait until the previous message processing is complete before fetching the rest of the processing from MessageQueue. So you can use the handler. post method to perform the notifyItemChanged operation, and wait until the previous notifyItemChanged process is complete before performing the subsequent notifyItemChanged operation. Solutions are as follows:

mHandler.post(() -> notifyItemChanged(position));
Copy the code

In addition, according to the source code, there is a situation that will produce the above problem, the solution is also very simple, to determine whether the processing is in the sliding state. Do not update data if it is in a sliding state.

if (recyclerView.getScrollState() == RecyclerView.SCROLL_STATE_IDLE
       && (recyclerView.isComputingLayout() == false)) {
      dataAdapter.notifyDataSetChanged();
}
Copy the code

Four,

Handler is a very important class in Android, both in the Framework layer and the application development layer can be seen everywhere, especially related to UI updates and threads. Therefore, you need to understand the principle of the Handler in depth, so that the problems related to it can be easily solved.