Fragment commit 的正确姿式

今天工做的时候在 bugly 上看到一个奔溃分析中有这么一个问题: html

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

因而就开始 Google,发现这问题由来已久,早在11年的时候就已经有人在提出这个问题。(时至今日仍出现这问题,不得不说 Google 你是能够的) android

在 Stack Overflow 上的这篇文章中也说了一些奇技淫巧去解决问题,可是回答者也并不理解为何会这样,只知道这样是能够解决的。而问题出现的缘由其实也能够猜想出来,就是在 activity 屡次的进行先后台切换后,致使了 fragment 的 commit 操做发生在 activity 的 onSaveInstanceState 以后。 bash

本着探索一下的精神,开始看源码。首先看了 commit 的源码。戳开 commit 后,看到了 FragmentTransaction 这个抽象类。在源码的最后能够看到,这里定义了四个跟 commit 有关的方法。(因为英文比较渣,单看英文的注释彷佛有点难以理解四个方法之间的差别,因而又去 Google 了一下,而后发现了这篇文章,有点茅塞顿开的感受)异步

ok,接着继续来看 commit 的源码。
BackStackRecord这个类里看到了具体实现以下:ide

@Override
    public int commit() {
        return commitInternal(false);
    }

    int commitInternal(boolean allowStateLoss) {
        if (mCommitted) throw new IllegalStateException("commit already called");
        if (FragmentManagerImpl.DEBUG) {
            Log.v(TAG, "Commit: " + this);
            LogWriter logw = new LogWriter(TAG);
            PrintWriter pw = new PrintWriter(logw);
            dump(" ", null, pw, null);
            pw.close();
        }
        mCommitted = true;
        if (mAddToBackStack) {
            mIndex = mManager.allocBackStackIndex(this);
        } else {
            mIndex = -1;
        }
        mManager.enqueueAction(this, allowStateLoss);
        return mIndex;
    }复制代码

从上面能够发现,关键在于enqueueAction这个方法上,因而继续跟,在FragmentManager的源码发现以下:ui

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);
        }
    }

    /**
     * Adds an action to the queue of pending actions.
     *
     * @param action the action to add
     * @param allowStateLoss whether to allow loss of state information
     * @throws IllegalStateException if the activity has been destroyed
     */
    public void enqueueAction(OpGenerator action, boolean allowStateLoss) {
        if (!allowStateLoss) {
            checkStateLoss();
        }
        synchronized (this) {
            if (mDestroyed || mHost == null) {
                if (allowStateLoss) {
                    // This FragmentManager isn't attached, so drop the entire transaction. return; } throw new IllegalStateException("Activity has been destroyed"); } if (mPendingActions == null) { mPendingActions = new ArrayList<>(); } mPendingActions.add(action); scheduleCommit(); } }复制代码

从上面能够看出,有三种状况会致使异常:this

  1. Can not perform this action after onSaveInstanceState
  2. Can not perform this action inside of xxx
  3. Activity has been destroyed

针对方法:spa

  1. 使用 commitAllowingStateLoss 方法
  2. 避免在异步回调里进行 fragment transaction 的操做(能够参考这里最后几点)
  3. 把 commit 操做包含在 !isFinishing() 内部(这里给出了解决方案)

因此,我的比较推荐的 commit 姿式应该是:code

if (!isFinishing()) {
        getSupportFragmentManager().beginTransaction()
            .replace(R.id.fragment_container, fragment)
            .commitAllowingStateLoss();
}复制代码

(这里说明一下,commitAllowingStateLoss方法不是必定要这样的,就像在参考文章里说的,当你肯定你没法避免那个异常的时候才用这个,官方推荐的是用 commit;而使用commitAllowingStateLoss致使的后果在参考文章里也有说明)

以上,纯粹是我的的小总结,也欢迎朋友们对文中不对的地方进行指正。Peace~~
(最后再唠嗑一句,英语过得去而且有耐心的朋友强烈推荐看一下参考文献的第一篇,讲得比较详细)

参考文献

  1. www.androiddesignpatterns.com/2013/08/fra…
  2. stackoverflow.com/questions/7…
  3. blog.chengyunfeng.com/?p=1016
  4. stackoverflow.com/questions/9…
相关文章
相关标签/搜索