源码分析commitAllowingStateLoss() 和commit()的区别(挑重点去知道)

12 篇文章 1 订阅

之前在使用Fragment的时候偶尔会有这么一个报错,Can not perform this action after onSaveInstanceState,意思为无法再onSaveInstanceState之后执行该操作,这个操作就是指commit(),之前也没怎么在意,后来通过查看源码去了解了一下这个问题,以下是对这个问题的解析及对应解决办法的对比。

        Fragment是我们经常用到的东西,常用的操作有添加(add)、移除(remove)、替换(replace)等,这些操作组成一个集合transaction,我们在通过调用getSupportFragmentManager().beginTransaction()来获取这个FragmentTransaction类的实例来管理这些操作,将他们存进由activity管理的back stack中,这样我们就可以进行fragment变化的回退操作,也可以这样去获取FragmentTransaction类的实例:

[java]  view plain  copy
  1. FragmentManager  mFragmentManager = getSupportFragmentManager();    
  2. FragmentTransaction  mFragmentTransaction = mFragmentManager.beginTransaction();    
        为什么我们会有这种报错呢,因为我们在使用add(),remove(),replace()等方法将Fragment的变化添加进去,然后在通过commit去提交这些变化(另外,在commit之前可以去调用addToBackState()方法,将这些变化加入到activity管理的back stack中去,这样用户调用返回键就可以回退这些变化了),提交完成之后这些变化就会应用到我们的Fragment中去。但是,这个commit()方法,你只能在avtivity存储他的状态之前调用,也就是onSaveInstanceState(),我们都知道activity有一个保存状态的方法和恢复状态的方法,这个就不详细解释了,在onSaveInstanceState()方法之后去调用commit(),就会抛出我们遇到的这个异常,这是因为在onSaveInstanceState()之后调用commit()方法,这些变化就不会被activity存储,即这些状态会被丢失,但我们可以去用commitAllowingStateLoss()这个方法去代替commit()来解决这个为题,下面我们通过源码去看这两个方法的区别。

        首先从我们获取FragmentTransaction类的实例开始,即getSupportFragmentManager(),源码是这样的:

[java]  view plain  copy
  1. /** 
  2.  * Return the FragmentManager for interacting with fragments associated 
  3.  * with this activity. 
  4.  */  
  5. public FragmentManager getSupportFragmentManager() {  
  6.     return mFragments;  
  7. }  
        而这个返回的mFragments是一个FragmentManagerImpl类 的实例,他继承自FragmentManager这个类:
[java]  view plain  copy
  1. final FragmentManagerImpl mFragments = new FragmentManagerImpl();  
        我们在FragmentManager这个类中还看到beginTransaction()这个抽象方法,打开他的实现类
[java]  view plain  copy
  1. final class FragmentManagerImpl extends FragmentManager {  
  2.   
  3.     ... ...  
  4.   
  5.     @Override  
  6.     public FragmentTransaction beginTransaction() {  
  7.         return new BackStackRecord(this);  
  8.     }  
  9.   
  10.     .... ...  
  11.   
  12. }  
        我们看到这个实现类中的该方法是返回一个BackStateRecord类的实体,我们继续去追踪这个类,就会发现,这个类其实是继承自FragmentTransaction的,并且,我们在这里看到我们熟悉的方法:
[java]  view plain  copy
  1. final class BackStackRecord extends FragmentTransaction implements  
  2.         FragmentManager.BackStackEntry, Runnable {  
  3.   
  4.     public BackStackRecord(FragmentManagerImpl manager) {  
  5.         mManager = manager;  
  6.     }  
  7.   
  8.     public int commit() {  
  9.         return commitInternal(false);  
  10.     }  
  11.   
  12.     public int commitAllowingStateLoss() {  
  13.         return commitInternal(true);  
  14.     }  
  15.       
  16.     int commitInternal(boolean allowStateLoss) {  
  17.         if (mCommitted) throw new IllegalStateException("commit already called");  
  18.         if (FragmentManagerImpl.DEBUG) {  
  19.             Log.v(TAG, "Commit: " + this);  
  20.             LogWriter logw = new LogWriter(TAG);  
  21.             PrintWriter pw = new PrintWriter(logw);  
  22.             dump("  "null, pw, null);  
  23.         }  
  24.         mCommitted = true;  
  25.         if (mAddToBackStack) {  
  26.             mIndex = mManager.allocBackStackIndex(this);  
  27.         } else {  
  28.             mIndex = -1;  
  29.         }  
  30.         mManager.enqueueAction(this, allowStateLoss);  
  31.         return mIndex;  
  32.     }  
  33.   
  34. }  
        终于找到了我们有用的东西了,这里省略了其他不必要的代码,只留下我们需要用的核心代码,有兴趣可以自己去查看源码,这里还有省略掉我们常用的add、remove、replace等方法,回归正题,看我们要找的两个方法commit()和commitAllowingStateLoss(),他们都调用了commitInternal(boolean allowStateLoss)这个方法,只是传入的参数不同,我们去看commitInternal方法,该方法首先去判断是否已经commit,这个简单,直接跳过,最后他执行的是FragmentTransactionImpl类的enqueueAction方法,好,不要嫌麻烦,我们继续去追踪这个方法:
[java]  view plain  copy
  1. public void enqueueAction(Runnable action, boolean allowStateLoss) {  
  2.     if (!allowStateLoss) {  
  3.         checkStateLoss();  
  4.     }  
  5.     synchronized (this) {  
  6.         if (mActivity == null) {  
  7.             throw new IllegalStateException("Activity has been destroyed");  
  8.         }  
  9.         if (mPendingActions == null) {  
  10.             mPendingActions = new ArrayList<Runnable>();  
  11.         }  
  12.         mPendingActions.add(action);  
  13.         if (mPendingActions.size() == 1) {  
  14.             mActivity.mHandler.removeCallbacks(mExecCommit);  
  15.             mActivity.mHandler.post(mExecCommit);  
  16.         }  
  17.     }  
  18. }  
        通过这个方法我们可以看到,他首先去根据commit和commitAllowingStateLoss这两个方法传入的参数不同去判断,然后将任务扔进activity的线程队列中,这里我们重点去看的是checkStateLoss()这个方法:
[java]  view plain  copy
  1. private void checkStateLoss() {  
  2.     if (mStateSaved) {  
  3.         throw new IllegalStateException(  
  4.                 "Can not perform this action after onSaveInstanceState");  
  5.     }  
  6.     if (mNoTransactionsBecause != null) {  
  7.         throw new IllegalStateException(  
  8.                 "Can not perform this action inside of " + mNoTransactionsBecause);  
  9.     }  
  10. }  
        这个方法很简单,就只是一个简单的判断而已,并且只有调用commit方法才会执行这里,commitAllowingStateLoss则直接跳过这步,这里我们调用commit方法时,系统系判断状态(mStateSaved)是否已经保存,如果已经保存,则抛出"Can not perform this action after onSaveInstanceState"异常,这就是我们遇到的问题了,而用commitAllowingStateLoss方法则不会这样,这就与我们之前分析的activity去保存状态对应上了,在activity保存状态完成之后调用commit时,这个mStateSaved就是已经保存状态,所以会抛出异常。

        长篇大论终于讲完了,其实回头一看并没有多么复杂,就跟着源码一步一步去找,就会找到我们发生错误的地方,看了源码之后发现,原来并没有多么难,so easy!哈哈

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值