使用DialogFragment如果在没有调用过show方法时调用dismiss方法,就会出现异常
java.lang.IllegalStateException: Fragment DialogFragmentName(你自己的类名) not associated with a fragment manager.
提示就是没有FragmentManager
public class DialogFragment extends Fragment{
public void dismiss() {
dismissInternal(false, false);
//可以看到直接调用了dismissInternal方法,这也是DialogFragment中的方法
}
void dismissInternal(boolean allowStateLoss, boolean fromOnDismiss) {
//...
//上面有一部分代码,和我们遇到的异常无关
//下面进入了一个if语句,不管是进入if还是else
//都要调用getParentFragmentManager方法
if (mBackStackId >= 0) {
getParentFragmentManager().popBackStack(mBackStackId,
FragmentManager.POP_BACK_STACK_INCLUSIVE);
//...
} else {
FragmentTransaction ft = getParentFragmentManager().beginTransaction();
//...
}
//...
}
getParentFragmentManager()
是Fragment的方法,会返回Fragment的mFragmentManager对象,当mFragmentManager为null时,就会抛出我们遇到的异常
@NonNull
public final FragmentManager getParentFragmentManager() {
FragmentManager fragmentManager = mFragmentManager;
if (fragmentManager == null) {
throw new IllegalStateException(
"Fragment " + this + " not associated with a fragment manager.");
}
return fragmentManager;
}
DialogFragment.dismiss->DialogFragment.dismissInternal->Fragment.getParentFragmentManager,最终因为Fragment的mFragmentManager变量是null导致抛出异常。
怎么才能让这个变量不是null呢,答案是在调用DialogFragment.show方法时需要传入一个FragmentManager对象作为参数,最终这个对象会被赋值给当前DialogFragment的mFragmentManager变量,之后再调用dismiss方法也不会抛出异常了
解决方法:
避免在没有DialogFramgent没有show过的情况下调用dismiss
或是重写dismiss方法,手动判断在mFragmentManager不为null时才dismiss
@Override
public void dismiss() {
if (getFragmentManager()==null){
Log.w(TAG, "dismiss: "+this+" not associated with a fragment manager." );
}else {
super.dismiss();
}
}
番外:show方法是怎么给Fragment的mFragmentManager变量赋值的?
顺便分享一下自己追代码的方式。
先试着正着跟了一下,FragmentManager和BackStackRecord来回跳,基本追了几层就放弃了,选择先倒着看一下,也就是看下Fragment的mFragmentManager在什么地方赋值,来找点线索或者目标,我们不用弄清楚每个赋值的方法具体做了什么,只要往上追两层,对相关方法的名字或者相应的动作有印象就好了。
可以看到,Fragment的mFragmentManager的有效赋值只在BackStackRecord
、FragmentManager
和FragmentLayoutInflaterFactory
三个类中
然后我们重新从show方法开始追下去,如果追到这三个类之外的方法,并且方法参数不是这三个类的话(如果是这三个类,可能还会转回来),基本就可以放弃了,可以少追很多无效代码
DialogFragment.show(FragmentManager manager)
FragmentTransaction ft = manager.beginTransaction();//直接返回一个BackStackRecord对象,它是抽象类FragmentTransaction的唯一实现类
ft.add(this, tag);//把当前的DialogFragment(是Fragment的子类)封装到一个对象里,并添加到ft对象持有的集合中
ft.commit();//跟进去
BackStateRecord.commit()&commitInteral()
commit是FragmentTranslation中定义的抽象方法,BackStateRecord对它的实现是直接调用自己的commitInteral方法
int commitInternal(boolean allowStateLoss) {
//...
mManager.enqueueAction(this, allowStateLoss);//跟进去,有经验的话,看到这个方法名字就觉得很可能是它。可能接收的参数就是一个类似Runnable的实现类,在调用 类似run()的方法时,还是要去这个实现类中看逻辑的
//but!很可惜,这里给Fragemnt设置FragemtnManager和这个this参数无关哈哈
//...
}
FragmentManager.enqueueAction(@NonNull OpGenerator action, boolean allowStateLoss)
void enqueueAction(@NonNull OpGenerator action, boolean allowStateLoss) {
synchronized (mPendingActions) {
mPendingActions.add(action);//与Fragment设置FragmentManager无关,大概看了下,是用来打印log的
scheduleCommit();//跟进去
}
}
FragmentManager.scheduleCommit()
void scheduleCommit() {
//...
mHost.getHandler().removeCallbacks(mExecCommit);//看名字就不用管
mHost.getHandler().post(mExecCommit);//post,这个参数是一个Runnable,看下它的run方法
updateOnBackPressedCallbackEnabled();//点进去看一眼,是设置了某个回调是否启用,跟这个方法的话需要继续去看这个回调的逻辑,优先级比较低,没办法了再去看它
/...
}
private Runnable mExecCommit = new Runnable() {
@Override
public void run() {
execPendingActions(true);
}
};
FragmentManager.execPendingActions
boolean execPendingActions(boolean allowStateLoss) {
ensureExecReady(allowStateLoss);//由于之前倒着追的时候,对这个方法名或类似的名字有印象,所以我选择直接跟这个方法,如果无效的话再看后面的
//...
}
FragmentManager.ensureExecReady()
private void ensureExecReady(boolean allowStateLoss) {
//...
executePostponedTransaction(null, null);
//...
}
FragmentManager.executePostponedTransaction
private void executePostponedTransaction(@Nullable ArrayList<BackStackRecord> records,
@Nullable ArrayList<Boolean> isRecordPop) {
//...
if (records != null && !listener.mIsBack
&& (index = records.indexOf(listener.mRecord)) != -1
&& isRecordPop != null
&& isRecordPop.get(index)) {
// This is popping a postponed transaction
listener.cancelTransaction();//这是倒着追的时候看到过的方法
} else {
listener.completeTransaction();//这也是倒着追的时候看到过的方法
}
}
}
}
看到这里,基本可以确定就是这个方法里进行的赋值了
他们两个方法都调用了mRecord.mManager.completeExecute
这个方法
void completeExecute(@NonNull BackStackRecord record, boolean isPop, boolean runTransitions,
boolean moveToState) {
if (isPop) {
record.executePopOps(moveToState);//倒着追看到过
} else {
record.executeOps();//也看到过
}
}
这两个BackStackRecord类中的方法点进去,都是进行了一个类似的switch语句,其中会遍历DialogFragment.show()-
> ft.add()
中的集合,这个集合中的对象是持有一个Fragment的;
最后一步冲刺!!
BackStackRecord.executeOps()
switch (op.mCmd) {
case OP_ADD:
f.setAnimations(op.mEnterAnim, op.mExitAnim, op.mPopEnterAnim, op.mPopExitAnim);
mManager.setExitAnimationOrder(f, false);
mManager.addFragment(f);//就是它
break;
case OP_REMOVE:
f.setAnimations(op.mEnterAnim, op.mExitAnim, op.mPopEnterAnim, op.mPopExitAnim);
mManager.removeFragment(f);
break;
//...