堆棧信息
bug0.png
版本分布
bug1.png
機型
bug2.png
問題分析
0,Activity->onBackPressed
public void onBackPressed() {
if (!mFragments.popBackStackImmediate()) {
finish();
}
}
1,FragmentManager->popBackStackImmediate
/**
* Like {@link #popBackStack()}, but performs the operation immediately
* inside of the call. This is like calling {@link #executePendingTransactions()}
* afterwards.
* @return Returns true if there was something popped, else false.
*/
public abstract boolean popBackStackImmediate();
2,FragmentManagerImpl->popBackStackImmediate
@Override
public boolean popBackStackImmediate() {
checkStateLoss();
executePendingTransactions();
return popBackStackState(mHost.getHandler(), null, -1, 0);
}
3,FragmentManagerImpl->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);
}
}
NOTE:崩潰的原因就是mStateSaved=true拋出了IllegalStateException,需要找到mStateSaved在哪些地方被賦值為true
4,FragmentManagerImpl->saveAllState
Parcelable saveAllState() {
// Make sure all pending operations have now been executed to get
// our state update-to-date.
execPendingActions();
mStateSaved = true;//賦值
.....
}
mSateSaved只會在saveAllState()中被重新賦值為true,繼續跟進saveAllState()在哪些場景下會被調用
5, Activity->onSaveInstanceState
protected void onSaveInstanceState(Bundle outState) {
outState.putBundle(WINDOW_HIERARCHY_TAG, mWindow.saveHierarchyState());
Parcelable p = mFragments.saveAllState();//在這被調用
if (p != null) {
outState.putParcelable(FRAGMENTS_TAG, p);
}
getApplication().dispatchActivitySaveInstanceState(this, outState);
}
可以得出初步的結論是當Activity調用onSaveInstanceState,間接改變了FragmentManagerImpl中mStateSaved的值,onBackPressed()被調用時會導致FragmentManagerImpl檢查mStateSaved值,為true就拋出異常導致崩潰
6,關于onSaveInstanceState
這一篇介紹了onSaveInstanceState的調用時機,所以分析可能的操作步驟是,Activity->Home(或者電源鍵)->回Activity->onBackPressed。
當用戶回道回到Activity時,生命周期必然會經過onResume,mStateSaved狀態會重置成false,onBackPressed就不會引起崩潰,這就是矛盾。
解決方案
反射FragmentActivity中的FragmentController對象,調用noteStateNotSaved方法,修改mStateSaved的值為false。
這樣的做的原因是:用戶點擊back鍵的目的是退出界面或者dismiss當前界面的dialog(或者其他浮層),對于前者不用考慮狀態保存的問題,對于后者Activity的生命周期會重新onResume,不會影響Fragment狀態保存。
@Override
public void onBackPressed() {
fixBug();
super.onBackPressed();
}
private void fixBug() {
try {
Class clz = BaseActivity.class;
while (clz != FragmentActivity.class) {
clz = clz.getSuperclass();
}
Field field = clz.getDeclaredField("mFragments");
field.setAccessible(true);
Object object = field.get(this);//獲取mFragments對象
Class<?> fieldClazz = object.getClass();
Method method = fieldClazz.getMethod("noteStateNotSaved");
method.setAccessible(true);
method.invoke(object);
} catch (Exception e) {
e.printStackTrace();
}
}