最新接了一个新的项目,发现切换页面用的是 Fragment 。
倒不是说 Fragment 不好, 而是Fragment 坑比较多,之前开发项目能不用 Fragment 的地方就不用。
因为是二次开发,重新写页面时间成本比较大。只能硬着头皮处理了。
崩溃如下 :
Caused by: java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState
at android.support.v4.app.FragmentManagerImpl.checkStateLoss(FragmentManager.java:1538)
at android.support.v4.app.FragmentManagerImpl.popBackStackImmediate(FragmentManager.java:624)
崩溃信息很简单,在 onSaveInstanceState 调用之后调用了 popBackStackImmediate 。
有很多小伙伴遇到这种问题都是线上版本,我来重现下这种 bug :
第一步做个延迟。( Handler 做延迟是为了模拟网络环境,设置5秒是为了让你有时间按下 Home 键盘)
第二布点击 Home 键。 (为了让 Activity 调用 onSaveInstanceState)
第三步 Crash 了 。
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
fragmentManager.popBackStackImmediate(className, 0);
}
},5000);
原因
进源码看一看吧 :
android.support.v4.app.FragmentManager 中 FragmentManagerImpl 的 popBackStackImmediate 方法的第一行调用了 checkStateLoss 。
@Override
public boolean popBackStackImmediate(String name, int flags) {
checkStateLoss();
executePendingTransactions();
return popBackStackState(mHost.getHandler(), name, -1, flags);
}
再进 checkStateLoss 看一下 , 原来异常是这里抛出的 , 当 mStateSaved = true 的时候,抛出了异常。
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);
}
}
mStateSaved 什么时候被赋值为 true 呢 ? 调用地方挺多的,这个崩溃主要在 FragmentActivity 中。
Parcelable saveAllState() {
execPendingActions();
if (HONEYCOMB) { // sdk > 11
mStateSaved = true;
}
FragmentActivity 方法中 onSaveInstanceState 调用了 onSaveInstanceState 。
/**
* Save all appropriate fragment state.
*/
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
Parcelable p = mFragments.saveAllState();
哦 原来是这样崩溃的 :
FragmentActivity.onSaveInstanceState () -> FragmentMagagerImp.saveAllState()-> mStateSaved 赋值为 true -> FragmentMagagerImp.popBackStackImmediate()–> FragmentMagagerImp.checkStateLoss() -> if(mStateSaved) crash 。
怎么解决这个 Bug 呢
- 放弃使用 Fragment , 使用自定义 View 或者 Activity 来实现交互 。 (当然项目时间紧,这是不现实的,如果要修个 bug ,需要重构界面,程序员心中都是 mmp 的)
- 既然 mStateSaved = true 会引起崩溃 ,那么我就把你设置为 false , 不让你崩溃吧 。
可以通过反射调用 noteStateNotSaved 方法 。noteStateNotSaved 方法可以设置 mStateSaved 为 false 。
public void noteStateNotSaved() {
mStateSaved = false;
}
反射方法如下 :
private void fixBug() {
try {
Class<? extends FragmentManager> aClass = fragmentManager.getClass();
Method method = aClass.getMethod("noteStateNotSaved");
method.setAccessible(true);
method.invoke(fragmentManager);
} catch (Exception e) {
e.printStackTrace();
}
}
那么最终具体调用是这样的 :
fixBug();
fragmentManager.popBackStackImmediate(className, 0);
注意
- 有同学可能直接 try catch 处理这类问题。直接捕获异常只是让他不崩溃,但是你正常的业务也不能执行了。
- 为什么用反射 ?因为 commitAllowingStateLoss方法允许状态丢失 , 但是 pop 没有允许 StateLoss 的方法。