View State but received class androidx.recyclerview.widget.RecyclerView$SavedState instead same id

记一次id重复引发的闪退。


    java.lang.IllegalArgumentException: Wrong state class, expecting View State but received class androidx.recyclerview.widget.RecyclerView$SavedState instead. This usually happens when two views of different type have the same id in the same hierarchy. This view's id is id/0x1. Make sure other views do not use the same id.
        at android.view.View.onRestoreInstanceState(View.java:21045)
        at android.view.View.dispatchRestoreInstanceState(View.java:21017)
        at android.view.ViewGroup.dispatchRestoreInstanceState(ViewGroup.java:4000)
        at android.view.ViewGroup.dispatchRestoreInstanceState(ViewGroup.java:4006)
        at android.view.View.restoreHierarchyState(View.java:20995)
        at androidx.fragment.app.Fragment.restoreViewState(Fragment.java:548)
        at androidx.fragment.app.FragmentManagerImpl.moveToState(FragmentManagerImpl.java:907)
        at androidx.fragment.app.FragmentManagerImpl.moveFragmentToExpectedState(FragmentManagerImpl.java:1238)
        at androidx.fragment.app.BackStackRecord.executeOps(BackStackRecord.java:434)
        at androidx.fragment.app.FragmentManagerImpl.executeOps(FragmentManagerImpl.java:2079)
        at androidx.fragment.app.FragmentManagerImpl.executeOpsTogether(FragmentManagerImpl.java:1869)
        at androidx.fragment.app.FragmentManagerImpl.removeRedundantOperationsAndExecute(FragmentManagerImpl.java:1824)
        at androidx.fragment.app.FragmentManagerImpl.execPendingActions(FragmentManagerImpl.java:1727)
        at androidx.fragment.app.FragmentManagerImpl$2.run(FragmentManagerImpl.java:150)
        at android.os.Handler.handleCallback(Handler.java:938)
        at android.os.Handler.dispatchMessage(Handler.java:99)
        at android.os.Looper.loopOnce(Looper.java:201)
        at android.os.Looper.loop(Looper.java:288)
        at android.app.ActivityThread.main(ActivityThread.java:7838)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:548)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1003)

页面复现在ReactFragment里面,来回切换fragment导致闪退,发现问题步骤:
一、在fragment内的声明周期打印日志,看是在哪个声明周期出现问题
二、打印fragment内部所有view或者viewgroup的id,可以看到是哪两个id重复

一、声明周期

1、通过打印reactfragment的声明周期,发现fragment切换时,会走 onDestroyView ,切回reactfragment,会重新走onViewCreated。
2、问题解决步骤,不让ReactFragment走onDestroyView,FragmentTabHost 重写 doTabChanged 里的 detach和attach,改成hide和show

二、打印fragment所有view的id

1、在onViewCreated 打印view和viewgroup的id


  @Override
  public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
    super.onViewCreated(view, savedInstanceState);
    Log.e("东方不败", "ReactFragment-onViewCreated-2, " + view.getId());
    getId(view, "0");
  }
// 获取id
  private void getId(View view, String index) {
    if (view instanceof ViewGroup) {
      ViewGroup vg = (ViewGroup) view;
      getString(view, 1, index);
      for (int i = 0; i < vg.getChildCount(); i++) {
        View vc = vg.getChildAt(i);
        getString(vc, 3, index);
        getId(vc, "" + index + "-" + i);
      }
    } else {
      getString(view, 2, index);
    }
  }
// 如果是textview 获取text内容,其他直接打印view
  private void getString(View view, int tag, String index) {
    String vs = view.toString();
    StringBuilder sb = new StringBuilder();
    sb.append(index);
    sb.append(":");
    if (tag == 2) {
      if (view instanceof ReactTextView) {
        sb.append(((ReactTextView) view).getText());
      } else if (view instanceof ReactImageView) {
        sb.append("ReactImageView");
      } else {
        sb.append(vs);
      }
    } else {
      sb.append(vs);
    }
    sb.append(" ---- id:");
    sb.append(view.getId());
    Log.e("东方不败-" + tag, sb.toString());
  }

打印的日志如下:


东方不败: ReactFragment-onViewCreated-2, -1
东方不败-1: 0:android.widget.RelativeLayout{cb2969a V.E...... ......ID 0,0-1080,2152} ---- id:-1
东方不败-3: 0:com.facebook.react.ReactRootView{7093bcb V.E...... .......D 0,0-1080,2152 #1} ---- id:1
东方不败-1: 0-0:com.facebook.react.ReactRootView{7093bcb V.E...... .......D 0,0-1080,2152 #1} ---- id:1
东方不败-3: 0-0:com.facebook.react.views.view.ReactViewGroup{a4c2b50 V.E...... .......D 0,0-1080,2152 #31} ---- id:49
东方不败-1: 0-0-0:com.facebook.react.views.view.ReactViewGroup{a4c2b50 V.E...... .......D 0,0-1080,2152 #31} ---- id:49
......
......
东方不败-3: 0-0-0-0-0-0-0-0-0-0-0:com.reactnativepagerview.NestedScrollableHost{a48a774 V.E...... .......D 0,243-1080,2152 #a3} ---- id:163
东方不败-1: 0-0-0-0-0-0-0-0-0-0-0-3:com.reactnativepagerview.NestedScrollableHost{a48a774 V.E...... .......D 0,243-1080,2152 #a3} ---- id:163
东方不败-3: 0-0-0-0-0-0-0-0-0-0-0-3:androidx.viewpager2.widget.ViewPager2{be9e3f V.E...... .......D 0,0-1080,1909} ---- id:-1
东方不败-1: 0-0-0-0-0-0-0-0-0-0-0-3-0:androidx.viewpager2.widget.ViewPager2{be9e3f V.E...... .......D 0,0-1080,1909} ---- id:-1
东方不败-3: 0-0-0-0-0-0-0-0-0-0-0-3-0:androidx.viewpager2.widget.ViewPager2$RecyclerViewImpl{91ac30c VFED..... .......D 0,0-1080,1909 #1} ---- id:1
东方不败-1: 0-0-0-0-0-0-0-0-0-0-0-3-0-0:androidx.viewpager2.widget.ViewPager2$RecyclerViewImpl{91ac30c VFED..... .......D 0,0-1080,1909 #1} ---- id:1
东方不败-3: 0-0-0-0-0-0-0-0-0-0-0-3-0-0:android.widget.FrameLayout{f1ae455 V.E...... .......D 0,0-1080,1909} ---- id:-1
东方不败-1: 0-0-0-0-0-0-0-0-0-0-0-3-0-0-0:android.widget.FrameLayout{f1ae455 V.E...... .......D 0,0-1080,1909} ---- id:-1

可以看出 androidx.viewpager2.widget.ViewPager2$RecyclerViewImpl 和 com.facebook.react.ReactRootView 的id重复。
接着看为什么会重复?
ViewPager2里面的RecyclerViewImpl的id是通过view设置的

mRecyclerView = new RecyclerViewImpl(context);
mRecyclerView.setId(ViewCompat.generateViewId());

通过这种方式生成的id会自增,那就说明可能ReactRootView的id不是通过这种方法生成的,如果不是,那在ReactFragment里面手动设置下id就行,更或者,在ReactFragment里面直接调用下ViewCompat.generateViewId() 就行,调用一次sNextGeneratedId 就会自增,这样就不用出现重复id了。
2、再跟踪下ReactRootView的id生成方式
通过 npm start的终端日志可以看出 rootTag 是1
ReactRootView —> runApplication —> getRootViewTag,在看设置tag的地方
反查引用,setRootViewTag —> attachRootViewToInstance


    final int rootTag;

    if (reactRoot.getUIManagerType() == FABRIC) {
      rootTag =
          uiManager.startSurface(
              reactRoot.getRootViewGroup(),
              reactRoot.getJSModuleName(),
              initialProperties == null
                  ? new WritableNativeMap()
                  : Arguments.fromBundle(initialProperties),
              reactRoot.getWidthMeasureSpec(),
              reactRoot.getHeightMeasureSpec());
      reactRoot.setRootViewTag(rootTag);
      reactRoot.setShouldLogContentAppeared(true);
    } else {
      rootTag =
          uiManager.addRootView(
              reactRoot.getRootViewGroup(),
              initialProperties == null
                  ? new WritableNativeMap()
                  : Arguments.fromBundle(initialProperties),
              reactRoot.getInitialUITemplate());
      reactRoot.setRootViewTag(rootTag);
      reactRoot.runApplication();
    }

继续跟踪反查引用地方
UIManagerModule --> addRootView --> final int tag = ReactRootViewTagGenerator.getNextRootViewTag();

public class ReactRootViewTagGenerator {

  // Keep in sync with ReactIOSTagHandles JS module - see that file for an explanation on why the
  // increment here is 10.
  private static final int ROOT_VIEW_TAG_INCREMENT = 10;

  private static int sNextRootViewTag = 1;

  public static synchronized int getNextRootViewTag() {
    final int tag = sNextRootViewTag;
    sNextRootViewTag += ROOT_VIEW_TAG_INCREMENT;
    return tag;
  }
}

可以看出确实ReactRootView的tag是1,但是不能说明id是1
继续看UIManagerModule --> addRootView的方法

mUIImplementation.registerRootView(rootView, tag, themedRootContext);

registerRootView中

mOperationsQueue.addRootView(tag, rootView);

UIViewOperationQueue —> addRootView—>NativeViewHierarchyManager—>addRootView—>addRootViewGroup


  protected final synchronized void addRootViewGroup(int tag, View view) {
    if (DEBUG_MODE) {
      FLog.d(TAG, "addRootViewGroup[%d]: %s", tag, (view != null ? view.toString() : "<null>"));
    }
    if (view.getId() != View.NO_ID) {
      FLog.e(
        TAG,
        "Trying to add a root view with an explicit id ("
          + view.getId()
          + ") already "
          + "set. React Native uses the id field to track react tags and will overwrite this field. "
          + "If that is fine, explicitly overwrite the id field to View.NO_ID before calling "
          + "addRootView.");
    }

    mTagsToViews.put(tag, view);
    mTagsToViewManagers.put(tag, mRootViewManager);
    mRootTags.put(tag, true);
    view.setId(tag);
  }

由此可以看出tag就是id,最后会给view的id设置成tag

感兴趣的同学还可以看下uimanager的startSurface方法,请自行跟踪,同理,根ReactRootView的id也是1

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值