---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)
当前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)
冷启动,插件更新后必须要重起进程才能生效.
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.
* 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; iif (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 container) {
dispatchRestoreInstanceState(container);//执行派生类ViewGroup的处理逻辑
}
frameworks/base/core/java/android/view/ViewGroup.java
@Override
protected void dispatchRestoreInstanceState(SparseArray 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 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.");
}
}
还原一下场景:
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. 从插件的生效规则上看,是不可能出现上面的问题的. 目前是无法在本地复现该问题,同时每个升级插件的用户也只会发生一次这样的崩溃,因此目前所有的怀疑也都只是猜测.
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 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 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.
*
* 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保存数据状态