Wrong state class, expecting View State but received class xxx Make sure other views do not use the

本文分析了一种Android应用中常见的崩溃问题:由于视图ID重复导致的状态恢复错误。通过详细解析源码,揭示了问题的根本原因在于不同类型的视图使用相同ID。文章还提供了组件加载策略及解决思路。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

0x01. 崩溃信息栈

---java.lang.IllegalArgumentException: Wrong state class, expecting View State but received class android.widget.TextView$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/plugin_xxx_voiceButton. Make sure other views do not use the same id.
android.view.View.onRestoreInstanceState(View.java:13827)
android.view.View.dispatchRestoreInstanceState(View.java:13803)
android.view.ViewGroup.dispatchRestoreInstanceState(ViewGroup.java:2923)
android.view.ViewGroup.dispatchRestoreInstanceState(ViewGroup.java:2923)
android.view.View.restoreHierarchyState(View.java:13781)
android.support.v4.app.Fragment.restoreViewState(Fragment.java:468)
android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:1094)
android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:1248)
android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:1230)
android.support.v4.app.FragmentManagerImpl.dispatchActivityCreated(FragmentManager.java:2042)
android.support.v4.app.FragmentController.dispatchActivityCreated(FragmentController.java:165)
android.support.v4.app.FragmentActivity.onStart(FragmentActivity.java:543)
com.xxx.xxx.xxx.activity.xxxActivity.onStart(xxxActivity.java:260)
com.xxx.xxx.xxx.activity.ActivityXXX.onStart(ActivityXXX.java:2302)
android.app.Instrumentation.callActivityOnStart(Instrumentation.java:1245)
com.jingdong.aura.xxx.xxx.xxx.callActivityOnStart(SourceFile:439)
android.app.Activity.performStart(Activity.java:6099)
android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2367)
android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2466)
android.app.ActivityThread.access$900(ActivityThread.java:175)
android.app.ActivityThread$H.handleMessage(ActivityThread.java:1369)
com.jingdong.aura.xxx.xxx.x$x.handleMessage(xxxFile:47)
android.os.Handler.dispatchMessage(Handler.java:98)
android.os.Looper.loop(Looper.java:135)
android.app.ActivityThread.main(ActivityThread.java:5418)
java.lang.reflect.Method.invoke(Native Method)
java.lang.reflect.Method.invoke(Method.java:372)
com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1037)
com.android.internal.os.ZygoteInit.main(ZygoteInit.java:832)

0x02. 分析

当前View: This view’s id is id/plugin_xxx_voiceButton.
行为:
expecting View State but received class android.widget.TextView$SavedState instead

V6.5.0 插件升级前

public static final int plugin_xxx_firstRightImage = 1208418738;
public static final int plugin_xxx_voiceButton = 1208418739;  //ImageButton
public static final int plugin_xxx_inputBox = 1208418740;     // extends EditText extends TextView
public static final int plugin_xxx_smileyButton = 1208418741;

V6.5.0-bu 插件升级后

public static final int plugin_xxx_firstRightImage = 1208418738;
public static final int plugin_xxx_layout_bottom_btn = 1208418739; 
public static final int plugin_xxx_voiceButton = 1208418740;    //ImageButton
public static final int plugin_xxx_inputBox = 1208418741;   // extends TextView

用户出现问题的时候崩溃信息里面标示当前加载的是升级后的组件版本号:11

当前view:
voiceButton = 1208418740, 但是收到的是plugin_xxx_inputBox = 1208418740(上一个版本的id,父类型是TextView)

0x04. 组件的加载策略

冷启动,插件更新后必须要重起进程才能生效.

0x05. 源码分析

frameworks/support/v4/java/android/support/v4/app/FragmentActivity.java

    /**
     * Dispatch onStart() to all fragments.  Ensure any created loaders are
     * now started.
     */
    @Override
    protected void onStart() {
        super.onStart();

        mStopped = false;
        mReallyStopped = false;
        mHandler.removeMessages(MSG_REALLY_STOPPED);

        if (!mCreated) {
            mCreated = true;
            mFragments.dispatchActivityCreated();
        }

        mFragments.noteStateNotSaved();
        mFragments.execPendingActions();

        mFragments.doLoaderStart();

        // NOTE: HC onStart goes here.

        mFragments.dispatchStart();
        mFragments.reportLoaderStart();
    }

frameworks/support/v4/java/android/support/v4/app/FragmentController.java

    /**
     * Moves all Fragments managed by the controller's FragmentManager
     * into the activity created state.
     * <p>Call when Fragments should be informed their host has been created.
     *
     * @see Fragment#onActivityCreated(Bundle)
     */
    public void dispatchActivityCreated() {
        mHost.mFragmentManager.dispatchActivityCreated();
    }

frameworks/support/v4/java/android/support/v4/app/FragmentManager.java

    public void dispatchActivityCreated() {
        mStateSaved = false;
        moveToState(Fragment.ACTIVITY_CREATED, false);
    }
    void moveToState(int newState, boolean always) {
        moveToState(newState, 0, 0, always);
    }

    void moveToState(int newState, int transit, int transitStyle, boolean always) {
        if (mHost == null && newState != Fragment.INITIALIZING) {
            throw new IllegalStateException("No host");
        }

        if (!always && mCurState == newState) {
            return;
        }

        mCurState = newState;
        if (mActive != null) {
            boolean loadersRunning = false;
            for (int i=0; i<mActive.size(); i++) {
                Fragment f = mActive.get(i);
                if (f != null) {
                    moveToState(f, newState, transit, transitStyle, false);//处理Fragment对象f
                    if (f.mLoaderManager != null) {
                        loadersRunning |= f.mLoaderManager.hasRunningLoaders();
                    }
                }
            }

            if (!loadersRunning) {
                startPendingDeferredFragments();
            }
            if (mNeedMenuInvalidate && mHost != null && mCurState == Fragment.RESUMED) {
                mHost.onSupportInvalidateOptionsMenu();
                mNeedMenuInvalidate = false;
            }
        }
    }

    //具体的处理逻辑
    void moveToState(Fragment f, int newState, int transit, int transitionStyle,
            boolean keepActive) {
        // Fragments that are not currently added will sit in the onCreate() state.
        if ((!f.mAdded || f.mDetached) && newState > Fragment.CREATED) {
            newState = Fragment.CREATED;
        }
        if (f.mRemoving && newState > f.mState) {
            // While removing a fragment, we can't change it to a higher state.
            newState = f.mState;
        }
        // Defer start if requested; don't allow it to move to STARTED or higher
        // if it's not already started.
        if (f.mDeferStart && f.mState < Fragment.STARTED && newState > Fragment.STOPPED) {
            newState = Fragment.STOPPED;
        }
        if (f.mState < newState) {
            // For fragments that are created from a layout, when restoring from
            // state we don't want to allow them to be created until they are
            // being reloaded from the layout.
            if (f.mFromLayout && !f.mInLayout) {
                return;
            }  
            if (f.mAnimatingAway != null) {
                // The fragment is currently being animated...  but!  Now we
                // want to move our state back up.  Give up on waiting for the
                // animation, move to whatever the final state should be once
                // the animation is done, and then we can proceed from there.
                f.mAnimatingAway = null;
                moveToState(f, f.mStateAfterAnimating, 0, 0, true);
            }
            switch (f.mState) {
                case Fragment.INITIALIZING:
                    if (DEBUG) Log.v(TAG, "moveto CREATED: " + f);
                    if (f.mSavedFragmentState != null) {
                        f.mSavedFragmentState.setClassLoader(mHost.getContext().getClassLoader());
                        f.mSavedViewState = f.mSavedFragmentState.getSparseParcelableArray(
                                FragmentManagerImpl.VIEW_STATE_TAG);
                        f.mTarget = getFragment(f.mSavedFragmentState,
                                FragmentManagerImpl.TARGET_STATE_TAG);
                        if (f.mTarget != null) {
                            f.mTargetRequestCode = f.mSavedFragmentState.getInt(
                                    FragmentManagerImpl.TARGET_REQUEST_CODE_STATE_TAG, 0);
                        }
                        f.mUserVisibleHint = f.mSavedFragmentState.getBoolean(
                                FragmentManagerImpl.USER_VISIBLE_HINT_TAG, true);
                        if (!f.mUserVisibleHint) {
                            f.mDeferStart = true;
                            if (newState > Fragment.STOPPED) {
                                newState = Fragment.STOPPED;
                            }
                        }
                    }
                    f.mHost = mHost;
                    f.mParentFragment = mParent;
                    f.mFragmentManager = mParent != null
                            ? mParent.mChildFragmentManager : mHost.getFragmentManagerImpl();
                    f.mCalled = false;
                    f.onAttach(mHost.getContext());
                    if (!f.mCalled) {
                        throw new SuperNotCalledException("Fragment " + f
                                + " did not call through to super.onAttach()");
                    }
                    if (f.mParentFragment == null) {
                        mHost.onAttachFragment(f);
                    } else {
                        f.mParentFragment.onAttachFragment(f);
                    }

                    if (!f.mRetaining) {
                        f.performCreate(f.mSavedFragmentState);
                    } else {
                        f.restoreChildFragmentState(f.mSavedFragmentState);
                        f.mState = Fragment.CREATED;
                    }
                    f.mRetaining = false;
                    if (f.mFromLayout) {
                        // For fragments that are part of the content view
                        // layout, we need to instantiate the view immediately
                        // and the inflater will take care of adding it.
                        f.mView = f.performCreateView(f.getLayoutInflater(
                                f.mSavedFragmentState), null, f.mSavedFragmentState);
                        if (f.mView != null) {
                            f.mInnerView = f.mView;
                            if (Build.VERSION.SDK_INT >= 11) {
                                ViewCompat.setSaveFromParentEnabled(f.mView, false);
                            } else {
                                f.mView = NoSaveStateFrameLayout.wrap(f.mView);
                            }
                            if (f.mHidden) f.mView.setVisibility(View.GONE);
                            f.onViewCreated(f.mView, f.mSavedFragmentState);
                        } else {
                            f.mInnerView = null;
                        }
                    }
                case Fragment.CREATED:
                    if (newState > Fragment.CREATED) {
                        if (DEBUG) Log.v(TAG, "moveto ACTIVITY_CREATED: " + f);
                        if (!f.mFromLayout) {
                            ViewGroup container = null;
                            if (f.mContainerId != 0) {
                                if (f.mContainerId == View.NO_ID) {
                                    throwException(new IllegalArgumentException(
                                            "Cannot create fragment "
                                                    + f
                                                    + " for a container view with no id"));
                                }
                                container = (ViewGroup) mContainer.onFindViewById(f.mContainerId);
                                if (container == null && !f.mRestored) {
                                    String resName;
                                    try {
                                        resName = f.getResources().getResourceName(f.mContainerId);
                                    } catch (NotFoundException e) {
                                        resName = "unknown";
                                    }
                                    throwException(new IllegalArgumentException(
                                            "No view found for id 0x"
                                            + Integer.toHexString(f.mContainerId) + " ("
                                            + resName
                                            + ") for fragment " + f));
                                }
                            }
                            f.mContainer = container;
                            f.mView = f.performCreateView(f.getLayoutInflater(
                                    f.mSavedFragmentState), container, f.mSavedFragmentState);
                            if (f.mView != null) {
                                f.mInnerView = f.mView;
                                if (Build.VERSION.SDK_INT >= 11) {
                                    ViewCompat.setSaveFromParentEnabled(f.mView, false);
                                } else {
                                    f.mView = NoSaveStateFrameLayout.wrap(f.mView);
                                }
                                if (container != null) {
                                    Animation anim = loadAnimation(f, transit, true,
                                            transitionStyle);
                                    if (anim != null) {
                                        setHWLayerAnimListenerIfAlpha(f.mView, anim);
                                        f.mView.startAnimation(anim);
                                    }
                                    container.addView(f.mView);
                                }
                                if (f.mHidden) f.mView.setVisibility(View.GONE);
                                f.onViewCreated(f.mView, f.mSavedFragmentState);
                            } else {
                                f.mInnerView = null;
                            }
                        }

                        f.performActivityCreated(f.mSavedFragmentState);
                        if (f.mView != null) {
                            f.restoreViewState(f.mSavedFragmentState);  //这里开始RestoreViewState
                        }
                        f.mSavedFragmentState = null;
                    }
                case Fragment.ACTIVITY_CREATED:
                    if (newState > Fragment.ACTIVITY_CREATED) {
                        f.mState = Fragment.STOPPED;
                    }
                case Fragment.STOPPED:
                    if (newState > Fragment.STOPPED) {
                        if (DEBUG) Log.v(TAG, "moveto STARTED: " + f);
                        f.performStart();
                    }
                case Fragment.STARTED:
                    if (newState > Fragment.STARTED) {
                        if (DEBUG) Log.v(TAG, "moveto RESUMED: " + f);
                        f.performResume();
                        f.mSavedFragmentState = null;
                        f.mSavedViewState = null;
                    }
            }
        } else if (f.mState > newState) {
            switch (f.mState) {
                case Fragment.RESUMED:
                    if (newState < Fragment.RESUMED) {
                        if (DEBUG) Log.v(TAG, "movefrom RESUMED: " + f);
                        f.performPause();
                    }
                case Fragment.STARTED:
                    if (newState < Fragment.STARTED) {
                        if (DEBUG) Log.v(TAG, "movefrom STARTED: " + f);
                        f.performStop();
                    }
                case Fragment.STOPPED:
                    if (newState < Fragment.STOPPED) {
                        if (DEBUG) Log.v(TAG, "movefrom STOPPED: " + f);
                        f.performReallyStop();
                    }
                case Fragment.ACTIVITY_CREATED:
                    if (newState < Fragment.ACTIVITY_CREATED) {
                        if (DEBUG) Log.v(TAG, "movefrom ACTIVITY_CREATED: " + f);
                        if (f.mView != null) {
                            // Need to save the current view state if not
                            // done already.
                            if (mHost.onShouldSaveFragmentState(f) && f.mSavedViewState == null) {
                                saveFragmentViewState(f);
                            }
                        }
                        f.performDestroyView();
                        if (f.mView != null && f.mContainer != null) {
                            Animation anim = null;
                            if (mCurState > Fragment.INITIALIZING && !mDestroyed) {
                                anim = loadAnimation(f, transit, false,
                                        transitionStyle);
                            }
                            if (anim != null) {
                                final Fragment fragment = f;
                                f.mAnimatingAway = f.mView;
                                f.mStateAfterAnimating = newState;
                                final View viewToAnimate = f.mView;
                                anim.setAnimationListener(new AnimateOnHWLayerIfNeededListener(
                                        viewToAnimate, anim) {
                                    @Override
                                    public void onAnimationEnd(Animation animation) {
                                        super.onAnimationEnd(animation);
                                        if (fragment.mAnimatingAway != null) {
                                            fragment.mAnimatingAway = null;
                                            moveToState(fragment, fragment.mStateAfterAnimating,
                                                    0, 0, false);
                                        }
                                    }
                                });
                                f.mView.startAnimation(anim);
                            }
                            f.mContainer.removeView(f.mView);
                        }
                        f.mContainer = null;
                        f.mView = null;
                        f.mInnerView = null;
                    }
                case Fragment.CREATED:
                    if (newState < Fragment.CREATED) {
                        if (mDestroyed) {
                            if (f.mAnimatingAway != null) {
                                // The fragment's containing activity is
                                // being destroyed, but this fragment is
                                // currently animating away.  Stop the
                                // animation right now -- it is not needed,
                                // and we can't wait any more on destroying
                                // the fragment.
                                View v = f.mAnimatingAway;
                                f.mAnimatingAway = null;
                                v.clearAnimation();
                            }
                        }
                        if (f.mAnimatingAway != null) {
                            // We are waiting for the fragment's view to finish
                            // animating away.  Just make a note of the state
                            // the fragment now should move to once the animation
                            // is done.
                            f.mStateAfterAnimating = newState;
                            newState = Fragment.CREATED;
                        } else {
                            if (DEBUG) Log.v(TAG, "movefrom CREATED: " + f);
                            if (!f.mRetaining) {
                                f.performDestroy();
                            } else {
                                f.mState = Fragment.INITIALIZING;
                            }

                            f.performDetach();
                            if (!keepActive) {
                                if (!f.mRetaining) {
                                    makeInactive(f);
                                } else {
                                    f.mHost = null;
                                    f.mParentFragment = null;
                                    f.mFragmentManager = null;
                                }
                            }
                        }
                    }
            }
        }

        if (f.mState != newState) {
            Log.w(TAG, "moveToState: Fragment state for " + f + " not updated inline; "
                    + "expected state " + newState + " found " + f.mState);
            f.mState = newState;
        }
    }

frameworks/support/v4/java/android/support/v4/app/Fragment.java

    final void restoreViewState(Bundle savedInstanceState) {
        if (mSavedViewState != null) {
            mInnerView.restoreHierarchyState(mSavedViewState);//进入ViewGroup处理逻辑
            mSavedViewState = null;
        }
        mCalled = false;
        onViewStateRestored(savedInstanceState);
        if (!mCalled) {
            throw new SuperNotCalledException("Fragment " + this
                    + " did not call through to super.onViewStateRestored()");
        }
    }

frameworks/base/core/java/android/view/View.java

    /**
     * Restore this view hierarchy's frozen state from the given container.
     *
     * @param container The SparseArray which holds previously frozen states.
     *
     * @see #saveHierarchyState(android.util.SparseArray)
     * @see #dispatchRestoreInstanceState(android.util.SparseArray)
     * @see #onRestoreInstanceState(android.os.Parcelable)
     */
    public void restoreHierarchyState(SparseArray<Parcelable> container) {
        dispatchRestoreInstanceState(container);//执行派生类ViewGroup的处理逻辑
    }

frameworks/base/core/java/android/view/ViewGroup.java

    @Override
    protected void dispatchRestoreInstanceState(SparseArray<Parcelable> container) {
        super.dispatchRestoreInstanceState(container);//执行父类View中的处理逻辑
        final int count = mChildrenCount;
        final View[] children = mChildren;
        for (int i = 0; i < count; i++) {//遍历子View
            View c = children[i];
            if ((c.mViewFlags & PARENT_SAVE_DISABLED_MASK) != PARENT_SAVE_DISABLED) {
                c.dispatchRestoreInstanceState(container);//调用子View中的方法.
            }
        }
    }

frameworks/base/core/java/android/view/View.java

    /**
     * Called by {@link #restoreHierarchyState(android.util.SparseArray)} to retrieve the
     * state for this view and its children. May be overridden to modify how restoring
     * happens to a view's children; for example, some views may want to not store state
     * for their children.
     *
     * @param container The SparseArray which holds previously saved state.
     *
     * @see #dispatchSaveInstanceState(android.util.SparseArray)
     * @see #restoreHierarchyState(android.util.SparseArray)
     * @see #onRestoreInstanceState(android.os.Parcelable)
     */
    protected void dispatchRestoreInstanceState(SparseArray<Parcelable> container) {
        if (mID != NO_ID) {
            Parcelable state = container.get(mID);// 根据当前view的id,从从容器中取出之前缓存的当前View的状态(就是一个Parcelable对象)
            if (state != null) {
                // Log.i("View", "Restoreing #" + Integer.toHexString(mID)
                // + ": " + state);
                mPrivateFlags &= ~PFLAG_SAVE_STATE_CALLED; //恢复mPrivateFlags的初始值
                onRestoreInstanceState(state);//调用View中的onRestoreInstanceState
                if ((mPrivateFlags & PFLAG_SAVE_STATE_CALLED) == 0) {
                    throw new IllegalStateException(
                            "Derived class did not call super.onRestoreInstanceState()");
                }
            }
        }
    }

onRestoreInstanceState(Parcelable state)实现:

android7.0的实现

    /**
     * Hook allowing a view to re-apply a representation of its internal state that had previously
     * been generated by {@link #onSaveInstanceState}. This function will never be called with a
     * null state.
     *
     * @param state The frozen state that had previously been returned by
     *        {@link #onSaveInstanceState}.
     *
     * @see #onSaveInstanceState()
     * @see #restoreHierarchyState(android.util.SparseArray)
     * @see #dispatchRestoreInstanceState(android.util.SparseArray)
     */
    @CallSuper
    protected void onRestoreInstanceState(Parcelable state) {
        mPrivateFlags |= PFLAG_SAVE_STATE_CALLED;
        if (state != null && !(state instanceof AbsSavedState)) {
            throw new IllegalArgumentException("Wrong state class, expecting View State but "
                    + "received " + state.getClass().toString() + " instead. This usually happens "
                    + "when two views of different type have the same id in the same hierarchy. "
                    + "This view's id is " + ViewDebug.resolveId(mContext, getId()) + ". Make sure "
                    + "other views do not use the same id.");
        }
        if (state != null && state instanceof BaseSavedState) {
            mStartActivityRequestWho = ((BaseSavedState) state).mStartActivityRequestWhoSaved;
        }
    }

android 4.4.4上的实现,目前崩溃集中在4.0版本.

/**
     * Hook allowing a view to re-apply a representation of its internal state that had previously
     * been generated by {@link #onSaveInstanceState}. This function will never be called with a
     * null state.
     *
     * @param state The frozen state that had previously been returned by
     *        {@link #onSaveInstanceState}.
     *
     * @see #onSaveInstanceState()
     * @see #restoreHierarchyState(android.util.SparseArray)
     * @see #dispatchRestoreInstanceState(android.util.SparseArray)
     */
    protected void onRestoreInstanceState(Parcelable state) { 
        mPrivateFlags |= PFLAG_SAVE_STATE_CALLED; 
        if (state != BaseSavedState.EMPTY_STATE && state != null) { //异常检测
            throw new IllegalArgumentException("Wrong state class, expecting View State but "
                    + "received " + state.getClass().toString() + " instead. This usually happens "
                    + "when two views of different type have the same id in the same hierarchy. "
                    + "This view's id is " + ViewDebug.resolveId(mContext, getId()) + ". Make sure "
                    + "other views do not use the same id.");
        }
    }

0x06. 总结

还原一下场景:

0x01.无人客服的界面重新onStart();
0x02.恢复view的状态;

 ```Parcelable state = container.get(mID);```把升级之前的无人客服插件的另外一个View的状态(Parcelable对象)从内存中恢复了出来.

0x03.触发了state != BaseSavedState.EMPTY_STATE

因为如果是根据id相同的同一个View从container中取出的state值必须是BaseSavedState.EMPTY_STATE, 因此导致异常发生.

```public static final AbsSavedState EMPTY_STATE = new AbsSavedState() {};```这个就是EMPTY_STATE的本来面目,同一个view的EMPTY_STATE对象必然是相同的.

0x04. 从插件的生效规则上看,是不可能出现上面的问题的. 目前是无法在本地复现该问题,同时每个升级插件的用户也只会发生一次这样的崩溃,因此目前所有的怀疑也都只是猜测.

View state的存储过程和上面的还原过程刚好相反,这里就只看看最后的调用,因为和EMPTY_STATE相关.

saveHierarchyState()
    +------dispatchSaveInstanceState(SparseArray container)
                             +------onSaveInstanceState()

View.java

    /**
     * Store this view hierarchy's frozen state into the given container.
     *
     * @param container The SparseArray in which to save the view's state.
     *
     * @see #restoreHierarchyState(android.util.SparseArray)
     * @see #dispatchSaveInstanceState(android.util.SparseArray)
     * @see #onSaveInstanceState()
     */
    public void saveHierarchyState(SparseArray<Parcelable> container) {
        dispatchSaveInstanceState(container);
    }

    /**
     * Called by {@link #saveHierarchyState(android.util.SparseArray)} to store the state for
     * this view and its children. May be overridden to modify how freezing happens to a
     * view's children; for example, some views may want to not store state for their children.
     *
     * @param container The SparseArray in which to save the view's state.
     *
     * @see #dispatchRestoreInstanceState(android.util.SparseArray)
     * @see #saveHierarchyState(android.util.SparseArray)
     * @see #onSaveInstanceState()
     */
    protected void dispatchSaveInstanceState(SparseArray<Parcelable> container) {
        if (mID != NO_ID && (mViewFlags & SAVE_DISABLED_MASK) == 0) {
            mPrivateFlags &= ~PFLAG_SAVE_STATE_CALLED;
            Parcelable state = onSaveInstanceState();
            if ((mPrivateFlags & PFLAG_SAVE_STATE_CALLED) == 0) {
                throw new IllegalStateException(
                        "Derived class did not call super.onSaveInstanceState()");
            }
            if (state != null) {
                // Log.i("View", "Freezing #" + Integer.toHexString(mID)
                // + ": " + state);
                container.put(mID, state);
            }
        }
    }

    /**
     * Hook allowing a view to generate a representation of its internal state
     * that can later be used to create a new instance with that same state.
     * This state should only contain information that is not persistent or can
     * not be reconstructed later. For example, you will never store your
     * current position on screen because that will be computed again when a
     * new instance of the view is placed in its view hierarchy.
     * <p>
     * Some examples of things you may store here: the current cursor position
     * in a text view (but usually not the text itself since that is stored in a
     * content provider or other persistent storage), the currently selected
     * item in a list view.
     *
     * @return Returns a Parcelable object containing the view's current dynamic
     *         state, or null if there is nothing interesting to save. The
     *         default implementation returns null.
     * @see #onRestoreInstanceState(android.os.Parcelable)
     * @see #saveHierarchyState(android.util.SparseArray)
     * @see #dispatchSaveInstanceState(android.util.SparseArray)
     * @see #setSaveEnabled(boolean)
     */
    @CallSuper
    protected Parcelable onSaveInstanceState() {
        mPrivateFlags |= PFLAG_SAVE_STATE_CALLED;
        if (mStartActivityRequestWho != null) {
            BaseSavedState state = new BaseSavedState(AbsSavedState.EMPTY_STATE);
            state.mStartActivityRequestWhoSaved = mStartActivityRequestWho;
            return state;
        }
        return BaseSavedState.EMPTY_STATE;
    }

参考:

View的onSaveInstanceState和onRestoreInstanceState过程分析

What is an Android window?

从源码上分析Android View保存数据状态

Fatal Exception: java.lang.IllegalArgumentException: Wrong state class, expecting View State but received class androidx.appcompat.widget.Toolbar$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/toolbar. Make sure other views do not use the same id. at android.view.View.onRestoreInstanceState(View.java:21043) at android.view.View.dispatchRestoreInstanceState(View.java:21015) at android.view.ViewGroup.dispatchRestoreInstanceState(ViewGroup.java:4063) at android.view.ViewGroup.dispatchRestoreInstanceState(ViewGroup.java:4069) at android.view.ViewGroup.dispatchRestoreInstanceState(ViewGroup.java:4069) at android.view.ViewGroup.dispatchRestoreInstanceState(ViewGroup.java:4069) at android.view.ViewGroup.dispatchRestoreInstanceState(ViewGroup.java:4069) at android.view.View.restoreHierarchyState(View.java:20993) at com.android.internal.policy.PhoneWindow.restoreHierarchyState(PhoneWindow.java:2185) at android.app.Activity.onRestoreInstanceState(Activity.java:1753) at android.app.Activity.performRestoreInstanceState(Activity.java:1706) at android.app.Instrumentation.callActivityOnRestoreInstanceState(Instrumentation.java:1356) at android.app.ActivityThread.handleStartActivity(ActivityThread.java:3618) at android.app.servertransaction.TransactionExecutor.performLifecycleSequence(TransactionExecutor.java:221) at android.app.servertransaction.TransactionExecutor.cycleToPath(TransactionExecutor.java:201) at android.app.servertransaction.TransactionExecutor.executeLifecycleState(TransactionExecutor.java:173) at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:97) at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2187) at android.os.Handler.dispatchMessage(Handler.java:106) at android.os.Looper.loop(Looper.java:236) at android.app.ActivityThread.main(ActivityThread.java:8057) at java.lang.reflect.Method.invoke(Method.java) at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:620) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1011) 线上崩溃分析
最新发布
07-02
<think>好的,我现在需要分析用户提供的这个Android崩溃日志。首先,崩溃类型是IllegalArgumentException,错误信息是“Wrong state class, expecting View Statebut received class androidx.appcompat.widget.Toolbar$SavedState instead.This usually happens whentwo views of differenttype have the sameid in the samehierarchy. This view's id is id/toolbar. Makesure other views donot use the sameid.”。看起来是视图状态恢复时的问题。首先,我需要理解这个错误的原因。根据错误信息,当两个不同类型的视图在同一个视图层级中使用了相同的ID时,就会发生这种情况。系统期望恢复某个视图的状态,但接收到的却是另一个不同类型的视图的状态。例如,可能有一个Toolbar和其他视图(比如LinearLayout)使用了相同的ID,导致在恢复状态时出现类型不匹配。 接下来,我需要检查用户提供的代码和上下文。用户之前讨论的是关于Parcelable异常的问题,但现在这个崩溃是另一个不同的问题,涉及视图ID冲突。用户可能在布局文件中重复使用了相同的ID,尤其是在包含多个不同类型的视图时。 可能的场景:用户可能在某个Activity或Fragment的布局中定义了一个ID为toolbar的视图,比如一个Toolbar,而在另一个布局文件(例如通过include或者动态加载的Fragment)中,可能有另一个不同类型的视图也使用了id/toolbar。当系统尝试恢复状态时,保存的状态属于Toolbar,但当前视图可能是另一个类型,导致类型不匹配。需要验证这一点,用户需要检查所有相关的XML布局文件,确认是否有多个不同类型的视图共享相同的ID。特别是检查主布局文件和被包含的布局文件(如通过<include>标签引入的),以及各个Fragment的布局文件。另外,用户可能在代码中动态修改了视图的类型,比如在运行时替换了某个容器中的视图,但没有更新对应的ID,或者没有处理状态保存和恢复的逻辑。 解决方案方面,首先建议用户检查所有布局文件,确保同一层级内的视图ID唯一。如果确实需要复用ID,应确保同一时间只有一个视图存在,或者在替换视图时处理好状态保存。此外,可以重写onSaveInstanceState和onRestoreInstanceState方法,自定义处理状态保存,避免冲突。 还需要考虑是否使用了ViewPager或Fragment切换导致的视图层级问题,不同Fragment可能使用了相同的ID,尤其是在动态加载的情况下。另外,检查是否有自定义视图组件可能引入了ID冲突。 总结步骤:1.检查所有相关布局文件,确认id/toolbar是否在多个不同类型的视图中使用。 2.检查是否有include标签或合并布局导致的重复ID。 3.确保在动态替换视图时,正确管理ID和状态保存。 4.如果使用Fragment,检查各个Fragment的布局是否与其他部分存在ID冲突。5.考虑在自定义视图中正确实现状态保存和恢复逻辑。最后,提供具体的修复建议和示例代码,帮助用户解决这个问题,并预防类似问题的发生。</think>### 崩溃原因深度分析 #### 异常类型:**`IllegalArgumentException`** ```java Wrong state class, expecting View State but received class androidx.appcompat.widget.Toolbar$SavedState ``` #### 核心问题定位 - **根本原因**:**两个不同类型的视图在同一个视图层级中使用了相同的 `id`**。 - **具体场景**: 系统在恢复视图状态时,发现 `id/toolbar` 对应的视图类型与保存的状态类型不一致(例如:原本是 `Toolbar`,但当前是 `LinearLayout` 或其他类型)。 --- ### 典型触发场景 1. **布局文件中重复 `id` 定义** - 示例: ```xml <!-- activity_main.xml --> <androidx.appcompat.widget.Toolbar android:id="@+id/toolbar" ... /> <!-- included_layout.xml --> <LinearLayout android:id="@+id/toolbar" <!-- ❌ 与 Toolbar 冲突 --> ... /> ``` - **问题**:`<include>` 标签引入的布局与父布局中的 `id` 冲突。 2. **Fragment 动态替换导致的 `id` 复用** - 示例: ```kotlin supportFragmentManager.beginTransaction() .replace(R.id.container, MyFragment()) // container ID 被多个 Fragment 复用 .commit() ``` - **问题**:不同 Fragment 的布局中使用了相同 `id`。 3. **自定义视图与系统组件 `id` 冲突** - 示例: ```xml <!-- 自定义视图 --> <com.example.CustomView android:id="@+id/toolbar" ... /> <!-- 系统 Toolbar --> <androidx.appcompat.widget.Toolbar android:id="@+id/toolbar" <!-- ❌ 冲突 --> ... /> ``` 4. **ViewPager + FragmentPagerAdapter 导致的视图复用** - **问题**:`ViewPager` 预加载机制可能导致不同页面的 `id` 冲突。 --- ### 修复方案 #### 方法 1:确保 `id` 唯一性 - **步骤**: 1. **全局搜索 `@+id/toolbar`**:检查所有布局文件、`<include>` 布局和 Fragment 的 XML。 2. **修改冲突 `id`**: ```xml <!-- 修复后的 included_layout.xml --> <LinearLayout android:id="@+id/linear_layout_toolbar" <!-- ✅ 修改为唯一 ID --> ... /> ``` #### 方法 2:避免动态替换视图时的 `id` 冲突 - **场景**:动态添加/替换视图时。 - **修复代码**: ```kotlin // 使用新布局时移除旧视图 val container = findViewById<ViewGroup>(R.id.container) container.removeAllViews() val newView = layoutInflater.inflate(R.layout.new_layout, container, false) container.addView(newView) ``` #### 方法 3:Fragment 布局 `id` 命名规范化 - **最佳实践**:为每个 Fragment 的根布局分配唯一 `id`。 ```xml <!-- fragment_a.xml --> <FrameLayout android:id="@+id/fragment_a_root" ... /> <!-- fragment_b.xml --> <FrameLayout android:id="@+id/fragment_b_root" ... /> ``` #### 方法 4:禁用视图状态保存(极端情况) - **适用场景**:明确不需要恢复视图状态。 - **修复代码**: ```kotlin override fun onSaveInstanceState(outState: Bundle) { // 不保存视图状态 super.onSaveInstanceState(outState) } ``` --- ### 调试工具推荐 1. **布局检查器(Layout Inspector)** - **路径**:Android Studio → `Tools` → `Layout Inspector` - **用途**:实时查看视图层级和 `id` 分布。 2. **日志打印视图层级** ```kotlin val decorView = window.decorView Log.d("ViewHierarchy", getViewHierarchy(decorView)) fun getViewHierarchy(view: View): String { val sb = StringBuilder() fun traverse(view: View, depth: Int) { sb.append(" ".repeat(depth * 2) + view.javaClass.simpleName + " ID: ${view.id}\n") if (view is ViewGroup) { for (i in 0 until view.childCount) { traverse(view.getChildAt(i), depth + 1) } } } traverse(view, 0) return sb.toString() } ``` --- ### 预防措施 | 场景 | 风险 | 推荐做法 | |------|------|----------| | 多布局文件共用 `id` | 视图状态冲突 | 命名规范(如 `toolbar_main`, `toolbar_sub`) | | Fragment 动态替换 | `id` 复用 | 使用唯一根布局 `id` | | ViewPager 预加载 | 状态混乱 | 在 `Fragment` 中重写 `setUserVisibleHint()` | | 自定义视图 | 与系统组件冲突 | 使用 `@+id/custom_` 前缀 | ---
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值