java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState

問題:

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();
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容