程序员人生 网站导航

FragmentTransaction解析 Fragment Transactions和Activity状态丢失

栏目:综合技术时间:2017-02-23 09:26:36

关于FragmentTransaction之前用到过但是了解不全面,只是会简单使用。今天再次碰到所在在此将它详细记录:通过两篇比较好的文章总结1下,相信看完这两篇文章你暂时的问题都会得到解决,如果还有甚么疑问大家可以留言讨论。

我转的第1篇文章是作者对他人的文章进1步修改得到的更容易懂的作品:

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

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

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

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

[java] view plain copy 在CODE上查看代码片派生到我的代码片
  1. /** 
  2.  * Return the FragmentManager for interacting with fragments associated 
  3.  * with this activity. 
  4.  */  
  5. public FragmentManager getSupportFragmentManager() {  
  6.     return mFragments;  
  7. }  
        而这个返回的mFragments是1个FragmentManagerImpl类 的实例,他继承自FragmentManager这个类:
[java] view plain copy 在CODE上查看代码片派生到我的代码片
  1. final FragmentManagerImpl mFragments = new FragmentManagerImpl();  
        我们在FragmentManager这个类中还看到beginTransaction()这个抽象方法,打开他的实现类
[java] view plain copy 在CODE上查看代码片派生到我的代码片
  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. }  
        我们看到这个实现类中的该方法是返回1个BackStateRecord类的实体,我们继续去追踪这个类,就会发现,这个类实际上是继承自FragmentTransaction的,并且,我们在这里看到我们熟习的方法:
[java] view plain copy 在CODE上查看代码片派生到我的代码片
  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 在CODE上查看代码片派生到我的代码片
  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 在CODE上查看代码片派生到我的代码片
  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. }  
        这个方法很简单,就只是1个简单的判断而已,并且只有调用commit方法才会履行这里,commitAllowingStateLoss则直接跳过这步,这里我们调用commit方法时,系统系判断状态(mStateSaved)是不是已保存,如果已保存,则抛出"Can not perform this action after onSaveInstanceState"异常,这就是我们遇到的问题了,而用commitAllowingStateLoss方法则不会这样,这就与我们之前分析的activity去保存状态对应上了,在activity保存状态完成以后调用commit时,这个mStateSaved就是已保存状态,所以会抛出异常。



通过第1篇相信大多数人知道了FragmentTransaction的commit方法与commitAllowingStateLoss方法的区分和前者产生异常的缘由,同时也知道

FragmentTransaction.addToBackStack(String name)方法的作用是将transactions存到activity的backstack中,以便进行回退。


第2篇文章是1篇比较有深度的文章,相信作者是个牛人,要说明的东西很好理解:

Fragment Transactions和Activity状态丢失

  • 微信小程序入门与实战经常使用组件 API 开发技能 项目实战
  • 扛得住的MySQL数据库架构
  • 算法与数据结构C++精解
  • 所向无敌的响应式开发

下面的堆栈跟踪和异常代码,自从Honeycomb的初始发行版本就1直使得StackOverflow很迷惑。

这篇博客将会解释,这个异常在甚么时候产生和为何会产生?并且提供几种方法让这类异常不会产生在你的利用中。

为何会抛出这个异常?

这类异常的出现是由于,在Activity的状态保存以后,尝试去提交1个FragmentTransaction。这类现象被称为活动状态丢失(Activity State Loss)。但是,在我们了解这类异常的真正含义之前,让我们先看看当onSaveInstanceState()函数被调用的时候到底产生了甚么。

正如最近我在关于Binders & Death Recipients博客里面讨论的那样,Android利用在Android运行环境里很难决定自己的命运。Android系统可以在任什么时候候通过结束1个进程以释放内存,而且background activities可能在没有任何正告的情况下被清算。为了确保这类不肯定的行动对用户是透明的,在Activity可以烧毁之前,通过调用onSaveInstanceState()方法,架构给每一个Activity1个保存本身状态的机会。在重新加载已保存的状态时,对foreground和background Activities的切换,为用户带来了无缝切换的体验。用户不用去关心这个Activity是不是被系统烧毁了。

在框架调用onSaveInstanceState()方法时,给这个方法传递了1个Bundle对象。Activity可以通过这个对象来存储它的状态,而且Activity把它的dialogs、fragments和views的状态都保存在这个对象里面。当这个函数返回时,系统打包这个Bundle对象通过1个Binder接口传递给系统服务处理,然后它会被安全的存储下来。当系统决定重新创建这个Activity的时候,它会给这个利用传回1个相同的Bundle对象,通过这个对象可以重新装载Activity烧毁时的状态。

那为何会抛出这个异常呢?这个问题源于这样的事实,Bundle对象代表1个Activity在调用onSaveInstanceState()方法的1个瞬间快照,仅此而已。这意味着,当你在onSaveInstanceState()方法调用后会调用FragmentTransaction的commit方法。这个transaction将不会被记住,由于它没有在第1时间记录为这个Activity的状态的1部份。从用户的角度来看,这个transaction将会丢失,可能致使UI状态丢失。为了保证用户的体验,Android不惜1切代价避免状态的丢失。因此,不管甚么时候产生,都将简单的抛出1个IllegalStateException异常。

甚么时候会抛出这个异常?

如果之前你遇到过这个异常,或许你已注意到异常抛出的时间在不同的版本平台有细微的差别。或许你会发现,老版本的机器抛出异常的频率更低,或你的利用使用Support Library比使用官方的框架类的时候更容易抛出异常。这个细微的区分已致使1些人在猜想Support Library有bug,是不值得相信的。但是,这样的料想完全毛病。

这些细微区分存在的缘由是源于Honeycomb上对Activity生命周期所做的巨大改变。在Honeycomb之前,Activity直到暂停后才斟酌被烧毁。这意味着在onPause()方法之前onSaveInstanceState()方法被立即调用。但是,从Honeycomb开始,斟酌烧毁Activity只能是在他们停止以后,这意味着onSaveInstanceState()方法现在是在onStop()方法之前调用,以此代替在onPause()方法之前调用。这些不同总结以下表:

 Honeycomb之前的版本Honeycomb及更新的版本
Activities会在onPause()调用前被结束?NONO
Activities会在onStop()调用前被结束?YESNO
onSaveInstanceState(Bundle)会在哪些方法调用前被履行?onPause()onStop()

作为Activity生命周期已做的细微改变的结果,Support Library有时候需要根据平台的版本来改变它的行动。比如,在Honeycomb及以上的装备中,每当1个commit方法在onSaveInstanceState()方法以后调用时,都会抛出1个异常来提示开发者状态丢失产生了。但是,在Honeycomb之前的装备上,每次它产生时并抛出异常将更受限制,他们的onSaveInstanceState()方法在Activity的生命周期中更早调用,结果更容易产生状态丢失。Android团队被迫做了1个折衷的办法:为了更好的与老版本平台交互,老的装备不能不接受偶然状态丢失可能产生在onPause()方法和onStop()方法之间。Support Library在不同平台的行动总结以下表:

 Honeycomb之前的版本Honeycomb及更新的版本
commit()在onPause()前被调用OKOK
commit()在onPause()和onStop()履行中间被调用STATE LOSSOK
commit()在onStop()以后被调用EXCEPTIONEXCEPTION

如何避免抛出异常?

1旦你了解了到底产生了甚么,避免产生Activity状态丢失将会很简单。如果你读了这篇博客,那末很荣幸你更好的了解了Support Library是怎样工作的,和在你的利用中避免状态丢失为何如此的重要。假设你查看这个博客是为了查找快速解决的办法,那末,当你在你的利用中使用FragmentTransactions的时候,应牢记以下的这些建议:

建议1

当你在Activity生命周期函数里面提交transactions的时候要谨慎。大部份的利用仅仅在onCreate()方法被调用的开始时间提交transactions,或在相利用户输入的时候,因此将不可能碰到任何问题。但是,当你的transactions在其他的Activity生命周期函数提交,如onActivityResult()onStart()onResume(),事情将会变得奥妙。例如,你不应当在FragmentActivity的onResume()方法中提交transactions。由于有些时候这个函数可以在Activity的状态恢复前被调用(可以查看相干文档了解更多信息)。如果你的利用要求在除onCreate()函数以外的其他Activity生命周期函数中提交transaction,你可以在FragmentActivity的onResumeFragments()函数或Activity的onPostResume()函数中提交。这两个函数确保在Activity恢复到原始状态以后才会被调用,从而避免了状态丢失的可能性。(示例:看看我对this StackOverflow question的回答,来想一想如何提交FragmentTransactions作为Activity的onActivityResult方法被调用的响应)。

建议2

避免在异步回调函数中提交transactions。包括经常使用的方法,比如AsyncTask的onPostExecute方法和LoaderManager.LoaderCallbacks的onLoadFinished方法。在这些方法中履行transactions的问题是,当他们被调用的时候,他们完全没有Activity生命周期确当前状态。例如,斟酌下面的事件序列:

  1. 1个Activity履行1个AsyncTask。
  2. 用户按下“Home”键,致使Activity的onSaveInstanceState()onStop()方法被调用。
  3. AsyncTask完成并且onPostExecute方法被调用,而它没成心识到Activity已结束了。
  4. 在onPostExecute函数中提交的FragmentTransaction,致使抛出1个异常。

1般来讲,避免这类类型异常的最好办法就是不要在异步回调函数中提交transactions。Google工程师似乎同意这个信条。根据Android Developers group上的这篇文章,Android团队认为UI主要的改变,源于从异步回调函数提交FragmentTransactions引发不好的用户体验。如果你的利用需要在这些回调函数中履行transaction而没有简单的方法可以确保这个回调函数不好在onSaveInstanceState()以后调用。你可能需要诉诸于使用commitAllowingStateLoss方法,并且处理可能产生的状态丢失。(可以看看StackOverflow上的另外两篇文章,这1篇和另外一篇)。

建议3

作为最后的办法,使用commitAllowingStateLoss()函数。commit()函数和commitAllowingStateLoss()函数的唯1区分就是当产生状态丢失的时候,后者不会抛出1个异常。通常你不应当使用这个函数,由于它意味可能产生状态丢失。固然,更好的解决方案是commit函数确保在Activity的状态保存之前调用,这样会有1个好的用户体验。除非状态丢失的可能无可避免,否则就不应当使用commitAllowingStateLoss()函数。

第2篇作者给出了使用"提交"的时机,和使用建议,并总结出:
commit()函数和commitAllowingStateLoss()函数的唯1区分就是当产生状态丢失的时候,后者不会抛出1个异常。通常你不应当使用这个函数,由于它意味可能产生状态丢失


------分隔线----------------------------
------分隔线----------------------------

最新技术推荐