1.FragmentTransaction的commit()和commitAllowingStateLoss()的区别
1.1源码
1.1.1 FragmentTransaction
什么是FragmentTransaction
l 所有对Fragment的操作,比如增加、移除、替换等的改变,都可构成一个集合,这个集合被叫做一个transaction,而FragmentTransaction中的方法,可以处理这个transaction;
l 并且可以将transaction存进由activity管理的back stack中,这样用户就可以进行fragment变化的回退操作;
FragmentTransaction的实例
l FragmentManager mFragmentManager = getSupportFragmentManager();
FragmentTransaction mFragmentTransaction = mFragmentManager.beginTransaction();
commit
l FragmentTransaction最后需要进行commit的操作才有效,但你只能在activity存储它的状态(当用户要离开activity时)之前调用commit(),如果在存储状态之后调用commit(),将会抛出一个异常
l 这是因为当activity再次被恢复时commit之后的状态将丢失。如果丢失也没关系,可以使用commitAllowingStateLoss()方法
问题来了:这两个方法区别何在?
1.1.2查看源码搞搞报错原因
1. 找一下FragmentManager和FragmentTransaction的实例代码
public FragmentManager getSupportFragmentManager() { return mFragments; } |
2. 继续找mFragments
final FragmentManagerImpl mFragments = new FragmentManagerImpl(); |
3. 继续找FragmentManagerImpl
final class FragmentManagerImpl extends FragmentManager { @Override public FragmentTransaction beginTransaction() { //我们可以发现transaction发现实例的真相 return new BackStackRecord(this); } } |
4. 继续找BackStackRecord
final class BackStackRecord extends FragmentTransaction implements FragmentManager.BackStackEntry, Runnable { .......... public int commit() { return commitInternal(false); } public int commitAllowingStateLoss() { return commitInternal(true); } int commitInternal(boolean allowStateLoss) { if (mCommitted) throw new IllegalStateException("commit already called"); if (FragmentManagerImpl.DEBUG) Log.v(TAG, "Commit: " + this); mCommitted = true; if (mAddToBackStack) { mIndex = mManager.allocBackStackIndex(this); } else { mIndex = -1; } mManager.enqueueAction(this, allowStateLoss); return mIndex; } .......... } |
在这个类里,我们可以看到commit()和commitAllowingStateLoss()的区别——他们都调用了同一个方法commitInternal(),区别就是参数allowStateLoss不同——我们看到这个参数,最终传到mManager.enqueueAction(this, allowStateLoss)
5. 那我们又去FragmentManager瞧瞧
public void enqueueAction(Runnable action, boolean allowStateLoss) { if (!allowStateLoss) { |
方法在对 commit和commitAllowingStateLoss的传参进行判断后,将任务扔进activity的线程队列中。那这个两个方法区别就在传参判断后的处理方法checkStateLoss
6. 让我们查看一下checkStateLoss方法
看对参数进行判断后,做了什么样的处理:
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); } } |
可以发现:
1) 当使用commit方法时,系统将进行状态判断,如果状态(mStateSaved)已经保存,将发生"Can not perform this action after onSaveInstanceState"错误。
2) 如果mNoTransactionsBecause已经存在,将发生"Can not perform this action inside of " + mNoTransactionsBecause错误
1.2下面开始讲道理
1.2.1activity脆弱的人生
Android应用在Android运行环境里很难决定自己的命运。Android系统可以在任何时候通过结束一个进程以释放内存,而且background activities可能在没有任何警告的情况下被清理。
为了确保这种不确定的行为对于用户是透明的,在Activity可以销毁之前,通过调用onSaveInstanceState()方法,架构给每个Activity一个保存自身状态的机会。
因此在重新加载已保存的状态时,对于foreground和background Activities的切换,为用户带来了无缝切换的体验。用户不用去关心这个Activity是否被系统销毁了
1.2.2谁能证明你存在过?onSaveInstanceState()!
在框架调用onSaveInstanceState()方法时,给这个方法传递了一个Bundle对象。Activity可以通过这个对象来存储它的状态,而且Activity把它的dialogs、fragments以及views的状态都保存在这个对象里面。当这个函数返回时,系统打包这个Bundle对象通过一个Binder接口传递给系统服务处理,然后它会被安全的存储下来。当系统决定重新创建这个Activity的时候,它会给这个应用传回一个相同的Bundle对象,通过这个对象可以重新装载Activity销毁时的状态
1.2.3报错的原因——bundle快照机制
那为什么会抛出这个异常呢?这个问题源于这样的事实,Bundle对象代表一个Activity在调用onSaveInstanceState()方法的一个瞬间快照,仅此而已。这意味着,当你在onSaveInstanceState()方法调用后会调用FragmentTransaction的commit方法。这个transaction将不会被记住,因为它没有在第一时间记录为这个Activity的状态的一部分。从用户的角度来看,这个transaction将会丢失,可能导致UI状态丢失。为了保证用户的体验,Android不惜一切代价避免状态的丢失。因此,无论什么时候发生,都将简单的抛出一个IllegalStateException异常
简单来说就是:activity可以销毁之前,有个方法通过bundle记录了activity的各种信息,但这个bundle是个快照(幸好玩wow知道这是啥),你在这个bundle对象创建完了,再去commit,肯定不会被记住,因为bundle都 定好了;而系统果断不能接受这种异端(否则就可能回出现ui状态的丢失),所以必须报错
1.2.4如何避免
1) 建议一:(一般不会有这个困扰)
假如:如果你的应用要求在除onCreate()函数之外的其他Activity生命周期函数中提交transaction,你可以在FragmentActivity的onResumeFragments()函数或者Activity的onPostResume()函数中提交。不过,大部分的应用仅仅在onCreate()方法被调用的开始时间提交transactions,或者在相应用户输入的时候,因此将不可能碰到任何问题。
2) 建议二:避免在异步回调函数中提交transactions
举个栗子:
i. 一个Activity执行一个AsyncTask
ii. 用户按下“Home”键,导致Activity的onSaveInstanceState()和onStop()方法被调用
iii. AsyncTask完成并且onPostExecute方法被调用,而它没有意识到Activity已经结束了
iv. 在onPostExecute函数中提交的FragmentTransaction,导致抛出一个异常
如果你的应用需要在这些回调函数中执行transaction而没有简单的方法可以确保这个回调函数不好在onSaveInstanceState()之后调用,那么你可能需要诉诸于使用commitAllowingStateLoss方法并且处理可能发生的状态丢失。
2.android.R.id.content
在安卓布局文件中添加控件时,系统定义的此Fragment的id为android.R.id.content,所以调用findViewById(android.R.id.content)可以得到此Fragment的view