问题:
java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState
at android.app.FragmentManagerImpl.checkStateLoss(FragmentManager.java:1434)
at android.app.FragmentManagerImpl.enqueueAction(FragmentManager.java:1452)
at android.app.BackStackRecord.commitInternal(BackStackRecord.java:707)
at android.app.BackStackRecord.commit(BackStackRecord.java:671)
at android.app.DialogFragment.show(DialogFragment.java:230)
at my.test.app.TimerActivity.e(Unknown Source)
at my.test.app.TimerActivity.onTimerFinsh(Unknown Source)
at my.test.app.view.TimerView.dispatchDraw(Unknown Source)
at android.view.View.updateDisplayListIfDirty(View.java:16052)
at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:3748)
at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:3728)
at android.view.View.updateDisplayListIfDirty(View.java:16020)
at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:3748)
at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:3728)
at android.view.View.updateDisplayListIfDirty(View.java:16020)
at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:3748)
at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:3728)
at android.view.View.updateDisplayListIfDirty(View.java:16020)
at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:3748)
at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:3728)
at android.view.View.updateDisplayListIfDirty(View.java:16020)
at android.view.ThreadedRenderer.updateViewTreeDisplayList(ThreadedRenderer.java:656)
at android.view.ThreadedRenderer.updateRootDisplayList(ThreadedRenderer.java:662)
at android.view.ThreadedRenderer.draw(ThreadedRenderer.java:770)
at android.view.ViewRootImpl.draw(ViewRootImpl.java:2791)
at android.view.ViewRootImpl.performDraw(ViewRootImpl.java:2599)
at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:2198)
at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:1246)
at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:6302)
at android.view.Choreographer$CallbackRecord.run(Choreographer.java:871)
at android.view.Choreographer.doCallbacks(Choreographer.java:683)
at android.view.Choreographer.doFrame(Choreographer.java:619)
at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:857)
at android.os.Handler.handleCallback(Handler.java:751)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:159)
at android.app.ActivityThread.main(ActivityThread.java:6097)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:865)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:755)
原因
在使用DialogFragment,可能会出现这个bug。
从堆栈信息可以看到,错误来自FragmentManagerImpl 类的checkStateLoss()方法。该方法如下:
private void checkStateLoss() {
if(mStateSaved)
throw new IllegalStateException("Can not perform this action after onSaveInstanceState");
if(mNoTransactionsBecause != null)
throw new IllegalStateException((new StringBuilder()).append("Can not perform this action inside of ").append(mNoTransactionsBecause).toString());
else
return;
}
从代码可以看出,当mStateSaved对象为true时,就会抛出该异常。在FragmentManagerImpl 类中查找mStateSaved,发现只有在saveAllState()中该值才会被置为false。继续查找saveAllState()方法,我在Activity的onSaveInstanceState(Bundle bundle)方法找到了其调用。
protected void onSaveInstanceState(Bundle bundle) {
bundle.putBundle("android:viewHierarchyState", mWindow.saveHierarchyState());
android.os.Parcelable parcelable = mFragments.saveAllState();
if(parcelable != null)
bundle.putParcelable("android:fragments", parcelable);
getApplication().dispatchActivitySaveInstanceState(this, bundle);
}
当找到了问题的来源后,就可以开始去解决了。
解決方案
方案一:重写onSaveInstanceState(Bundle outState)方法
从上文得知只有调用了onSaveInstanceState(Bundle outState)方法,才会抛出该异常,所以只要我们在acitivity中重写该方法,然后注释或删掉super.onSaveInstanceState(outState)方法就行。
@Override
protected void onSaveInstanceState(Bundle outState) {
//super.onSaveInstanceState(outState);
}
方案二:反射调用showAllowingStateLoss(FragmentManager manager, String tag)方法
从上文的堆栈信息可以看到在调用checkStateLoss()方法之前是先调用了enqueueAction(Runnable runnable, boolean flag)方法,该方法中有以下代码:
public void enqueueAction(Runnable runnable, boolean flag) {
if(!flag)
checkStateLoss();
......
}
由此可以看出,只要传入的flag是true,该方法便不会被调用。继续从堆栈信息向上跟踪到类BackStackRecord,其有如下两个方法:
public int commit() {
return commitInternal(false);
}
public int commitAllowingStateLoss() {
return commitInternal(true);
}
再向上跟踪可以看到DialogFragment类中的showAllowingStateLoss(FragmentManager manager, String tag)方法,该方法被@hide标记,我们可反射调用它来替换方法,最终解决代码如下:
try {
Class aClass = Class.forName("android.app.DialogFragment");
Class[] argsClass = new Class[2];
argsClass[0] = FragmentManager.class;
argsClass[1] = String.class;
Object[] params = new Object[2];
params[0] = getFragmentManager();
params[1] = "MyDialog";
Method method = aClass.getMethod("showAllowingStateLoss", argsClass);
method.invoke(dialog,params);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
方案三:越过showAllowingStateLoss(FragmentManager manager, String tag)方法直接调用FragmentTransaction类的commitAllowingStateLoss()方法
从方案二的分析可知,调用了DialogFragment类中的showAllowingStateLoss(FragmentManager manager, String tag)方法可以避免该问题,而该方法代码如下:
/** {@hide} */
public void showAllowingStateLoss(FragmentManager manager, String tag) {
mDismissed = false;
mShownByMe = true;
FragmentTransaction ft = manager.beginTransaction();
ft.add(this, tag);
ft.commitAllowingStateLoss();
}
根据该代码内容,我们可以直接在展示DialogFragment时做出如下调用:
FragmentTransaction ft = getFragmentManager().beginTransaction();
ft.add(dialog, "MyDialog");
ft.commitAllowingStateLoss();
网友评论