四种commit使用细节及源码分析——Fragment(二)

一、 每个事务(FragmentTranscation)只能被commit一次

介绍

代码段一
public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
        transaction.add(R.id.fl_main, new ContentFragment(), null).commit();
        transaction.add(R.id.fl_main, new ContentFragment(), null).commit();
        transaction.add(R.id.fl_main, new ContentFragment(), null).commit();
    }
}

如果运行上面的代码,是会报错的,因为每个事务只能提交一次

 Caused by: java.lang.IllegalStateException: commit already called

每个提交都重新创建一个事务,是可以正常运行的,例如下面的代码,

代码段二
public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        getSupportFragmentManager().beginTransaction().add(R.id.fl_main, new ContentFragment(), null).commit();
        getSupportFragmentManager().beginTransaction().add(R.id.fl_main, new ContentFragment(), null).commit();
        getSupportFragmentManager().beginTransaction().add(R.id.fl_main, new ContentFragment(), null).commit();
    }
}

源码分析:

在FragmentManager.java类中找到如下函数,

代码段三
    @Override
    public FragmentTransaction beginTransaction() {
        return new BackStackRecord(this);
    }

原来,我们开启的每一个事务都是一个回退栈记录(BackStackRecord是FragmentTransaction的一个具体实现类)

在BackStackRecord.java 类中,我们看到四种commit的函数,后面的章节,将会对着四种进行比较分析。我们接着分析为什么一个事务只能提交一次。

代码段四
    @Override
    public int commit() {
        return commitInternal(false);
    }

    @Override
    public int commitAllowingStateLoss() {
        return commitInternal(true);
    }

    @Override
    public void commitNow() {
        disallowAddToBackStack();
        mManager.execSingleAction(this, false);
    }

    @Override
    public void commitNowAllowingStateLoss() {
        disallowAddToBackStack();
        mManager.execSingleAction(this, true);
    }

commit()会调用commitInternal(),

代码段五
    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);
        }
        mCommitted = true;
        if (mAddToBackStack) {
            mIndex = mManager.allocBackStackIndex(this);
        } else {
            mIndex = -1;
        }
        mManager.enqueueAction(this, allowStateLoss);
        return mIndex;
    }

在第3行代码,如果mCommitted为true则会抛出异常。在第10行,mCommitted=true。整个源码中对mCommitted进行赋值的地方仅此1处,第一次提交mCommitted置为true,第二次提交就会抛出异常,所以一个事务只能被提交一次

二、 Activity执行完onSaveInstanceState()方法后不能再执行commit()方法

我想手动控制Fragment的添加显示,在Activity被onDestroy的时候将Fragment移除掉(代码如下)

代码段六
public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        getSupportFragmentManager().beginTransaction().add(R.id.fl_main, new ContentFragment(), "ContentFragment").commit();
    }

    @Override
    protected void onDestroy() {
        getSupportFragmentManager().beginTransaction().remove(getSupportFragmentManager().findFragmentByTag("ContentFragment")).commit();
        super.onDestroy();
    }
}

运行下程序,发现退出时程序崩溃了…我们得到如下崩溃日志:

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

意思是:不能在onSaveInstanceState()被执行之后调用commit()

源码分析:

Android 3.0(SDK>=11)以后的Activity,知道如何管理Fragment,也就是Activity继承了FragmentActivity,在FragmentActivity.java类中,看看在activity销毁前执行的函数,onSaveInstanceState()

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

mFragments 是一个FragmentController类型的变量,于是来到了FragmentController.java找到saveAllState()

代码段八
    public Parcelable saveAllState() {
        return mHost.mFragmentManager.saveAllState();
    }

接下来到FragmentManager.java中看看saveAllState()

代码段九
    static final boolean HONEYCOMB = android.os.Build.VERSION.SDK_INT >= 11;
    Parcelable saveAllState() {
        ...
        if (HONEYCOMB) {
            // As of Honeycomb, we save state after pausing.  Prior to that
            // it is before pausing.  With fragments this is an issue, since
            // there are many things you may do after pausing but before
            // stopping that change the fragment state.  For those older
            // devices, we will not at this point say that we have saved
            // the state, so we will allow them to continue doing fragment
            // transactions.  This retains the same semantics as Honeycomb,
            // though you do have the risk of losing the very most recent state
            // if the process is killed...  we'll live with that.
            mStateSaved = true;
        }
        ...

如果是大于等于11版本的安卓,会执行mStateSaved = true;

继续回到commit()函数的分析,可以看到最终执行commitInternal(),然后执行enqueueAction()函数

代码段十
    public void enqueueAction(OpGenerator action, boolean allowStateLoss) {
        if (!allowStateLoss) {
            checkStateLoss();
        }
         synchronized (this) {
            if (mDestroyed || mHost == null) {
                throw new IllegalStateException("Activity has been destroyed");
            }
            if (mPendingActions == null) {
                mPendingActions = new ArrayList<>();
            }
            mPendingActions.add(action);
            scheduleCommit();
        }
    }
代码段十一
    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);
        }
    }

代码的逻辑明白了,

但是为什么要这么做呢?

原因是在Android 11版本后的activity会管理fragment,也就是说在activity 在执行onSaveInstanceState()的时候会保存fragment的状态,用于在恢复activity的时候使用。那么在保存fragment的状态后,就不能再去改变fragment的状态,也就是不允许提交事务,这样才能保障activity恢复的时候,可以显示原来fragment的状态。

三、commit() 和commitAllowingStateLoss()

commitAllowingStateLoss() 提交事务,允许状态丢失,就是在onSaveInstanceState()保存了状态后,这时候再次发生的提交,这些新的状态是不会被保存的。

源码分析

在代码段三可知道,commit() 和commitAllowingStateLoss() 这两个函数会调用commitInternal(),只是传入的参数不一样,前者是false,后者是true。

这个参数用于判断,在enqueueAction()(代码段十)中是否执行checkStateLoss(),如果不执行就可以正常提交,但是状态可能会丢失。

使用commitAllowingStateLoss(),不会执行checkStateLoss(),所以可能造成状态丢失

状态丢失

如果在onSaveInstanceState()之后,调用的是commitAllowingStateLoss(),是丢失什么状态,在什么情况下丢失呢?
可能会丢掉FragmentManager的状态, 即onSaveInstanceState之后任何被添加或被移除的Fragments.

举例说明:

  1. 在Activity里显示一个FragmentA;
  2. 然后Activity放在后台, onStop()和onSaveInstanceState()被调用;
  3. 在某个事件触发下, 你用FragmentB replace FragmentA , 使用的是 commitAllowingStateLoss().

这时候, 用户再返回应用, 可能会有两种情况发生:

  1. 如果系统杀死了activity,activity将会重建, 使用上述步骤2保存的状态, 所以A会显示, B不会显示;
  2. 如果系统没有杀死activity, 会被提到前台, FragmentB就会显示出来, 到下次Activity stop的时候, 这个包含了B的状态就会被存

(上述测试可以利用开发者选项中的”Don’t Keep Activities”选项).

四、commit(), commitNow() 和 executePendingTransactions()

一个事务的提交到执行,经历了如下的流程:
commit() >>enqueueAction()>>scheduleCommit()>>execPendingActions()

代码段十二
    private void scheduleCommit() {
        synchronized (this) {
            boolean postponeReady =
                    mPostponedTransactions != null && !mPostponedTransactions.isEmpty();
            boolean pendingReady = mPendingActions != null && mPendingActions.size() == 1;
            if (postponeReady || pendingReady) {
                mHost.getHandler().removeCallbacks(mExecCommit);
                mHost.getHandler().post(mExecCommit);
            }
        }
    }
代码段十三
    Runnable mExecCommit = new Runnable() {
        @Override
        public void run() {
            execPendingActions();
        }
    };

事务最终的执行是在execPendingActions()中被执行

  • 调用commit并不是立即执行的, 它会被发送到主线程的任务队列当中去, 当主线程准备好执行它的时候执行.

  • popBackStack()的工作也是这样, 发送到主线程任务队列中去. 也即说它们都是异步的.

  • 在commit()调用之后加上 executePendingTransactions(),这个事务会立即执行, 即变异步为同步.

代码段十四
    @Override
    public boolean executePendingTransactions() {
        boolean updates = execPendingActions();
        forcePostponedTransactions();
        return updates;
    }

在executePendingTransactions()中直接调用了execPendingActions();可以立即执行事务

五、commitNow()和commitNowAllowingStateLoss()

在代码段三可知道,commitNow()和commitNowAllowingStateLoss()都调用了execSingleAction()。execSingleAction()函数内的执行和execPendingActions()内的执行差不多,就是去执行这个事务

support library从v24.0.0开始提供了 commitNow 方法, 之前用executePendingTransactions()会将所有pending在队列中还有你新提交的transactions都执行了, 而commitNow 将只会执行你当前要提交的transaction. 所以commitNow 避免你会不小心执行了那些你可能并不想执行的transactions.

不能加入回退栈

不能对要加在back stack中的transaction使用commitNow(), 即addToBackStack()和commitNow()不能同时使用。为什么呢?

如果你有一个提交使用了commit(), 紧接着又有另一个提交使用了commitNow(), 两个都想加入back stack, 那back stack会变成什么样呢? 到底是哪个transaction在上, 哪个在下? 答案将是一种不确定的状态, 因为系统并没有提供任何保证来确保顺序, 所以系统决定干脆不支持这个操作。

前面提过popBackStack()是异步的, 所以它同样也有一个同步的兄弟popBackStackImmediate().

实际应用的时候怎么选择呢?

  1. 如果你需要同步操作, 并且你不需要加到back stack里, 使用commitNow().
    support library在FragmentPagerAdapter里就使用了commitNow()来保证在更新结束的时候, 正确的页面被加上或移除.
  2. 如果你操作很多transactions, 并且不需要同步, 或者你需要把transactions加在back stack里, 那就使用commit().
  3. 如果想在某一时间, 执行所有的transactions, 那么使用executePendingTransactions().

源码分析:

在代码四种的,commitNow()和commitNowAllowingStateLoss()都有调用disallowAddToBackStack(),

代码段十五
    @Override
    public FragmentTransaction disallowAddToBackStack() {
        if (mAddToBackStack) {
            throw new IllegalStateException(
                    "This transaction is already being added to the back stack");
        }
        mAllowAddToBackStack = false;
        return this;
    }

在加入回退栈的函数,会进行判断,是否允许加入回退栈

    @Override
    public FragmentTransaction addToBackStack(String name) {
        if (!mAllowAddToBackStack) {
            throw new IllegalStateException(
                    "This FragmentTransaction is not allowed to be added to the back stack.");
        }
        mAddToBackStack = true;
        mName = name;
        return this;
    }

参考:
https://www.jianshu.com/p/f50a1d7ab161
https://www.jianshu.com/p/d9143a92ad94
https://www.cnblogs.com/mengdd/p/5827045.html

发布了242 篇原创文章 · 获赞 775 · 访问量 224万+

猜你喜欢

转载自blog.csdn.net/xx326664162/article/details/88379490
今日推荐