popBackStackImmediate Can not perform this action after onSaveInstanceState

最新接了一个新的项目,发现切换页面用的是 Fragment 。
倒不是说 Fragment 不好, 而是Fragment 坑比较多,之前开发项目能不用 Fragment 的地方就不用。
因为是二次开发,重新写页面时间成本比较大。只能硬着头皮处理了。
崩溃如下 :

Caused by: java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState
 at android.support.v4.app.FragmentManagerImpl.checkStateLoss(FragmentManager.java:1538)
 at android.support.v4.app.FragmentManagerImpl.popBackStackImmediate(FragmentManager.java:624)

崩溃信息很简单,在 onSaveInstanceState 调用之后调用了 popBackStackImmediate 。
有很多小伙伴遇到这种问题都是线上版本,我来重现下这种 bug :
第一步做个延迟。( Handler 做延迟是为了模拟网络环境,设置5秒是为了让你有时间按下 Home 键盘)
第二布点击 Home 键。 (为了让 Activity 调用 onSaveInstanceState)
第三步 Crash 了 。

     new Handler().postDelayed(new Runnable() {
            @Override
            public void run() {
                     fragmentManager.popBackStackImmediate(className, 0);
            }
        },5000);

原因

进源码看一看吧 :
android.support.v4.app.FragmentManager 中 FragmentManagerImpl 的 popBackStackImmediate 方法的第一行调用了 checkStateLoss 。

   @Override
    public boolean popBackStackImmediate(String name, int flags) {
        checkStateLoss();
        executePendingTransactions();
        return popBackStackState(mHost.getHandler(), name, -1, flags);
    }

再进 checkStateLoss 看一下 , 原来异常是这里抛出的 , 当 mStateSaved = true 的时候,抛出了异常。

   private void checkStateLoss() {
        if (mStateSaved) {
            throw new IllegalStateException(
                    "Can not perform this action after onSaveInstanceState");
        }
        if (mNoTransactionsBecause != null) {
            throw new IllegalStateException(
                    "Can not perform this action inside of " + mNoTransactionsBecause);
        }
    }

mStateSaved 什么时候被赋值为 true 呢 ? 调用地方挺多的,这个崩溃主要在 FragmentActivity 中。

Parcelable saveAllState() {
        execPendingActions();
        if (HONEYCOMB) {  // sdk > 11
            mStateSaved = true;
        }

FragmentActivity 方法中 onSaveInstanceState 调用了 onSaveInstanceState 。

    /**
     * Save all appropriate fragment state.
     */
    @Override
    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        Parcelable p = mFragments.saveAllState();

哦 原来是这样崩溃的 :
FragmentActivity.onSaveInstanceState () -> FragmentMagagerImp.saveAllState()-> mStateSaved 赋值为 true -> FragmentMagagerImp.popBackStackImmediate()–> FragmentMagagerImp.checkStateLoss() -> if(mStateSaved) crash 。

怎么解决这个 Bug 呢

  1. 放弃使用 Fragment , 使用自定义 View 或者 Activity 来实现交互 。 (当然项目时间紧,这是不现实的,如果要修个 bug ,需要重构界面,程序员心中都是 mmp 的)
  2. 既然 mStateSaved = true 会引起崩溃 ,那么我就把你设置为 false , 不让你崩溃吧 。
    可以通过反射调用 noteStateNotSaved 方法 。noteStateNotSaved 方法可以设置 mStateSaved 为 false 。
    public void noteStateNotSaved() {
        mStateSaved = false;
    }

反射方法如下 :

  private void fixBug() {
        try {
            Class<? extends FragmentManager> aClass = fragmentManager.getClass();
            Method method = aClass.getMethod("noteStateNotSaved");
            method.setAccessible(true);
            method.invoke(fragmentManager);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

那么最终具体调用是这样的 :

     fixBug();
     fragmentManager.popBackStackImmediate(className, 0);

注意

  1. 有同学可能直接 try catch 处理这类问题。直接捕获异常只是让他不崩溃,但是你正常的业务也不能执行了。
  2. 为什么用反射 ?因为 commitAllowingStateLoss方法允许状态丢失 , 但是 pop 没有允许 StateLoss 的方法。

谢谢这个哥们

https://www.jianshu.com/p/090c68f7de5a

猜你喜欢

转载自blog.csdn.net/stupid56862/article/details/80804437