Fragment系列文章,带你深入理解Fragment的工作机制、类结构
Fragment 深入理解——Fragment(一)
四种commit使用细节及源码分析——Fragment(二)
Fragment 架构与主流程解析——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.
举例说明:
- 在Activity里显示一个FragmentA;
- 然后Activity放在后台, onStop()和onSaveInstanceState()被调用;
- 在某个事件触发下, 你用FragmentB replace FragmentA , 使用的是 commitAllowingStateLoss().
这时候, 用户再返回应用, 可能会有两种情况发生:
- 如果系统杀死了activity,activity将会重建, 使用上述步骤2保存的状态, 所以A会显示, B不会显示;
- 如果系统没有杀死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().
实际应用的时候怎么选择呢?
- 如果你需要同步操作, 并且你不需要加到back stack里, 使用commitNow().
support library在FragmentPagerAdapter里就使用了commitNow()来保证在更新结束的时候, 正确的页面被加上或移除. - 如果你操作很多transactions, 并且不需要同步, 或者你需要把transactions加在back stack里, 那就使用commit().
- 如果想在某一时间, 执行所有的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