具体的异常信息
今天在bugly上看到一个bug:IllegalStateException,在此记下对该bug的一些思考。
首先追查Exception信息:
java.lang.IllegalStateException
Can not perform this action after onSaveInstanceState
android.support.v4.app.u.v(FragmentManager.java:1377)
android.support.v4.app.u.c(FragmentManager.java:504)
android.support.v4.app.FragmentActivity.onBackPressed(FragmentActivity.java:178)
然后很明显可以看到问题出现在v4包的FragmentManager.java中,去FragmentManager.java中看下
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);
}
}
很容易看到在checkStateLoss()抛出的异常,然后再继续往前找,然后看到BackStackState.java中enqueueAction()中调用了,或许很郁闷BackStackState是什么?
在FragmentManagerImpl.java中有这么一个方法
@Override
public FragmentTransaction beginTransaction() {
return new BackStackRecord(this);
}
经常使用到fragment的小伙伴应该知道这是什么了吧
public void enqueueAction(Runnable action, boolean allowStateLoss) {
if (!allowStateLoss) {
checkStateLoss();
}
。。。
}
接着往前看,然后在BackStackRecord.java中
public int commit() {
return commitInternal(false);
}
public int commitAllowingStateLoss() {
return commitInternal(true);
}
int commitInternal(boolean allowStateLoss) {
。。。
mManager.enqueueAction(this, allowStateLoss);
return mIndex;
}
看到这应该很明显了吧,commit()与commitAllowingStateLoss()两个方法均调用了commitInternal方法,不同的是参数不同,因此使用commit的时候enqueueAction函数中调用了checkStateLoss(),然后当mStateSaved==true时就抛出了该异常。可能你还说那我们使用commitAllowingStateLoss()就不会抛异常了呗,真简单。。可是我觉得使用commitAllowingStateLoss只能说是把错误遮住而并没有去解决!
为什么会产生?
既然知道是因为调用commit()时checkStateLoss中mStateSaved==true抛出,那何时mStateSaved==true呢?其实从变量命名也应该猜出作用了:
public void dispatchStop() {
// See saveAllState() for the explanation of this. We do this for
// all platform versions, to keep our behavior more consistent between
// them.
mStateSaved = true;
moveToState(Fragment.STOPPED, false);
}
Parcelable saveAllState() {
// Make sure all pending operations have now been executed to get
// our state update-to-date.
execPendingActions();
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;
}
。。。
}
这里为什么会出现两个不同的地方使mStateSaved=true,因为3.0版本是一个分界线。在3.0之前,onSaveInstanceState()在onPause之前调用,而在3.0之后onSaveInstanceState() 在onStop()之前调用,因此之前的版本也更容易抛出异常。 到这里我们也明白这个异常产生的原因:我们在activity的状态被保存后仍然去commit一个FragmentTransaction。
因为我们的activity不是一直存在的,当我们的系统内存不够的时候,我们后台的activity可能被系统kill。但是系统允许activity被kill之前用onSaveInstanceState() 用一个bundle保存dialog,fragment,view等的状态信息。然后重建Activity时又利用相应的信息。那么,为什么异常会抛出呢?bundle中存储的只是当onSaveInstanceState() 调用那一时刻的Activity信息,所以当你在onSaveInstanceState()之后调用FragmentTransaction#commit() 时, transaction并没有被当做Activity的信息进行保存,也就是transaction丢失了,导致意外的UI状态丢了,因此抛出了IllegalStateException异常。
那么如何解决呢
- 在activity生命周期中调用commit时足够谨慎。在oncreate()中或许不容易出问题,但是涉及到与其他activity的交互时,例如onActivityResult(), onStart(), 和onResume()等方法就很容易出问问题,因为这个时候很有可能Activity还未加载完全。如果应用需要commit那么可以将其放在onPostResume(),如果是FragmentActivity还可以放在onResumeFragments()中,避免放在其它生命周期中。
/**
* This is the fragment-orientated version of {@link #onResume()} that you
* can override to perform operations in the Activity at the same point
* where its fragments are resumed. Be sure to always call through to
* the super-class.
*/
protected void onResumeFragments() {
mFragments.dispatchResume();
}
/**
* Called when activity resume is complete (after {@link #onResume} has
* been called).
*/
protected void onPostResume() {
。。。
}
2 避免异步调用,例如在AsyncTask的postExecute中调用commit。此时调用很有可能activity还未重建成功。我认为可以在postExecute中给一个标志,然后在onPostResume中判断执行。
3. 最后无奈的方法:commitAllowingStateLoss()。只是用与于避免抛出异常,其实质并没有解决问题。
最后
这是写的第一篇博客,写的有点渣。。
要学的知识还很多,慢慢积累。。。