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,可能会出现这个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();

你可能感兴趣的:(java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState)