問題:
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,可能會出現(xiàn)這個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;
}
??從代碼可以看出,當(dāng)mStateSaved對象為true時,就會拋出該異常。在FragmentManagerImpl 類中查找mStateSaved,發(fā)現(xiàn)只有在saveAllState()中該值才會被置為false。繼續(xù)查找saveAllState()方法,我在Activity的onSaveInstanceState(Bundle bundle)方法找到了其調(diào)用。
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);
}
??當(dāng)找到了問題的來源后,就可以開始去解決了。
解決方案
方案一:重寫onSaveInstanceState(Bundle outState)方法
??從上文得知只有調(diào)用了onSaveInstanceState(Bundle outState)方法,才會拋出該異常,所以只要我們在acitivity中重寫該方法,然后注釋或刪掉super.onSaveInstanceState(outState)方法就行。
@Override
protected void onSaveInstanceState(Bundle outState) {
//super.onSaveInstanceState(outState);
}
方案二:反射調(diào)用showAllowingStateLoss(FragmentManager manager, String tag)方法
??從上文的堆棧信息可以看到在調(diào)用checkStateLoss()方法之前是先調(diào)用了enqueueAction(Runnable runnable, boolean flag)方法,該方法中有以下代碼:
public void enqueueAction(Runnable runnable, boolean flag) {
if(!flag)
checkStateLoss();
......
}
??由此可以看出,只要傳入的flag是true,該方法便不會被調(diào)用。繼續(xù)從堆棧信息向上跟蹤到類BackStackRecord,其有如下兩個方法:
public int commit() {
return commitInternal(false);
}
public int commitAllowingStateLoss() {
return commitInternal(true);
}
??再向上跟蹤可以看到DialogFragment類中的showAllowingStateLoss(FragmentManager manager, String tag)方法,該方法被@hide標(biāo)記,我們可反射調(diào)用它來替換方法,最終解決代碼如下:
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)方法直接調(diào)用FragmentTransaction類的commitAllowingStateLoss()方法
??從方案二的分析可知,調(diào)用了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();
}
??根據(jù)該代碼內(nèi)容,我們可以直接在展示DialogFragment時做出如下調(diào)用:
FragmentTransaction ft = getFragmentManager().beginTransaction();
ft.add(dialog, "MyDialog");
ft.commitAllowingStateLoss();