记一次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