实际项目出现此问题
一般是FragmentTransaction事务提交时报错,将commit换为commitAllowingStateLoss
从这里可以引申出Fragment的FragmentTransaction 的commit()和commitAllowingStateLoss()以及commitNow()和commitNowAllowingStateLoss()
commit
只能在activity存储它的状态(onSaveInstanceState(),当用户要离开activity时)之前调用commit(),如果在存储状态之后调用commit(),将会抛出一个异常,这是因为当activity再次被恢复时commit之后的状态将丢失。如果丢失也没关系,那么使用commitAllowingStateLoss()方法,commit和CommitNow都会抛出异常,如果在onSaveInstanceState()后执行的话。allowStateLoss=false,方法后面会执行检查checkStateLoss(),就是已经保存状态或stop的就会抛出异常IllegalStateException
commitNow
commitNow不允许addToBackStack,commitNow是不允许加入BackStack中去的,会将mAddToBackStack标志设置为false
commitAllowingStateLoss
同commit一样调用内部的commitInternal()方法,只不过传递的参数不同,commitAllowStateLoss的allowStateLoss是true,允许丢失状态不做检查,所以不会抛异常
代码
@Override
public int commit() {
return commitInternal(false);
}
//允许转台丢失
@Override
public int commitAllowingStateLoss() {
return commitInternal(true);
}
可以看到两方法都调用commitInternal方法,参数是allowStateLoss的boolean类型
int commitInternal(boolean allowStateLoss) {
if (mCommitted) throw new IllegalStateException("commit already called");
if (FragmentManager.isLoggingEnabled(Log.VERBOSE)) {
Log.v(TAG, "Commit: " + this);
LogWriter logw = new LogWriter(TAG);
PrintWriter pw = new PrintWriter(logw);
dump(" ", pw);
pw.close();
}
mCommitted = true;
if (mAddToBackStack) {
mIndex = mManager.allocBackStackIndex();
} else {
mIndex = -1;
}
//加入队列
mManager.enqueueAction(this, allowStateLoss);
return mIndex;
}
可以看到是通过 mManager.enqueueAction(this, allowStateLoss);来执行
void enqueueAction(@NonNull OpGenerator action, boolean allowStateLoss) {
if (!allowStateLoss) {
if (mHost == null) {
if (mDestroyed) {
throw new IllegalStateException("FragmentManager has been destroyed");
} else {
throw new IllegalStateException("FragmentManager has not been attached to a "
+ "host.");
}
}
checkStateLoss();
}
synchronized (mPendingActions) {
if (mHost == null) {
if (allowStateLoss) {
// This FragmentManager isn't attached, so drop the entire transaction.
return;
}
throw new IllegalStateException("Activity has been destroyed");
}
mPendingActions.add(action);
scheduleCommit();
}
}
allowStateLoss参数是来执行检测操作的 checkStateLoss();这就是报错的地方
private void checkStateLoss() {
if (isStateSaved()) {
throw new IllegalStateException(
"Can not perform this action after onSaveInstanceState");
}
}
如果activity的状态被保存了,这里再提交就会检查这个状态,符合这个条件就抛出一个异常来终止应用进程。也就是说在activity调用了onSaveInstanceState()之后,再commit一个事务就会出现该异常。那如果不想抛出异常,也可以很简单调用commitAllowingStateLoss()方法来略过这个检查就可以了,但是这是危险的,如果activity随后需要从它保存的状态中恢复,这个commit是会丢失的。因此它仅仅适用在ui状态的改变对用户来说是可以接受的,允许丢失一部分状态。
总结
1.在Activity的生命周期方法中提交事务要小心,越早越好,比如onCreate。尽量避免在onActivityResult()方法中提交。
2.避免在异步的回调方法中执行commit,因为他们感知不到当前Activity生命周期的状态。
3.使用commitAllowingStateLoss()代替commit()。相比于异常crash,UI状态的改变对用户来说是可以接受的。
4.如果你需要在Activity执行完onSaveInstanceState()之后还要进行提交,而且不关心恢复时是否会丢失此次提交,那么可以使用commitAllowingStateLoss()或commitNowAllowingStateLoss()。
commitNow以及commitNowAllowingstateLoss()
在API_24版本FragmentTranslation里添加了该两个方法:
该方法不支持加入BackStack回退栈中disallowAddToBackStack()。
官方更推荐使用commitNow()和commitNowAllowingStateLoss()来代替先执行commit()/commitAllowingStateLoss()。我们在解决的时候看看最小支持版本
这个问题也会出现在dialog的show和dismiss中(DialogFragment)
解决,在重写show和dismiss
override fun show(manager: FragmentManager, tag: String?) {
if (isAdded) {
return
}
try {
val mDismissed = DialogFragment::class.java.getDeclaredField("mDismissed")
mDismissed.isAccessible = true
mDismissed.setBoolean(this, false)
} catch (e: Exception) {
e.printStackTrace()
}
try {
val mShownByMe = DialogFragment::class.java.getDeclaredField("mShownByMe")
mShownByMe.isAccessible = true
mShownByMe.setBoolean(this, true)
} catch (e: Exception) {
e.printStackTrace()
}
val ft = manager.beginTransaction()
ft.add(this, tag)
ft.commitAllowingStateLoss()
}
override fun onDismiss(dialog: DialogInterface) {
super.onDismiss(dialog)
dismissAllowingStateLoss()
网友评论