前提
为解决DialogFragment
的内存泄漏,使用了此篇博客的处理方法 DialogFragment 内存泄露,简单说就是给 dialog 设置
getDialog().setOnCancelListener(null);
getDialog().setOnDismissListener(null);
但发现了一个问题,当用户返回Activity时,会再次显示对话框!!
之前也有人反馈:
后来调试发现不能设置此监听
getDialog().setOnDismissListener(null);
究竟源码里面做了什么操作,导致会再次显示呢???
带着这个问题来看一下源码
过程分析
setOnDismissListener
/**
* Set a listener to be invoked when the dialog is dismissed.
* @param listener The {@link DialogInterface.OnDismissListener} to use.
*/
public void setOnDismissListener(@Nullable OnDismissListener listener) {
if (mCancelAndDismissTaken != null) {
throw new IllegalStateException(
"OnDismissListener is already taken by "
+ mCancelAndDismissTaken + " and can not be replaced.");
}
if (listener != null) {
mDismissMessage = mListenersHandler.obtainMessage(DISMISS, listener);
} else {
mDismissMessage = null;
}
}
可以看出 设置与不设置监听的差别在于是否有 mDismissMessage
的存在,mDismissMessage
起到了什么样的作用?
检查发现它在此方法会使用:
private void sendDismissMessage() {
if (mDismissMessage != null) {
// Obtain a new message so this dialog can be re-used
Message.obtain(mDismissMessage).sendToTarget();
}
}
sendDismissMessage
此方法会发送一个消息,这个消息就是设置监听时定义的那个消息,系统给我们的标识是DISMISS
。即消失dialog时如果设置了此监听,就会发送。
系统是默认创建时就设置了此监听:
dismissDialog
继续跟踪会发现,sendDismissMessage();
会在dismissDialog()
里调用,
void dismissDialog() {
if (mDecor == null || !mShowing) {
return;
}
if (mWindow.isDestroyed()) {
Log.e(TAG, "Tried to dismissDialog() but the Dialog's window was already destroyed!");
return;
}
try {
mWindowManager.removeViewImmediate(mDecor);
} finally {
if (mActionMode != null) {
mActionMode.finish();
}
mDecor = null;
mWindow.closeAllPanels();
onStop();
mShowing = false;
sendDismissMessage();
}
}
而 dismissDialog()
一定会在dialog消失的时候调用:
Dialog.java
片段一:
@Override
public void dismiss() {
if (Looper.myLooper() == mHandler.getLooper()) {
dismissDialog();
} else {
mHandler.post(mDismissAction);
}
}
片段二:
private final Runnable mDismissAction = this::dismissDialog;
所以现在是知道了,在dialog结束时,系统会发送DISMISS
消息来做一些事情,如果将监听设置为null,则系统就不会处理那些事情!
((OnDismissListener) msg.obj).onDismiss(mDialog.get());
我们找到处理消息的地方,Dialog.java
private static final class ListenersHandler extends Handler {
private final WeakReference<DialogInterface> mDialog;
public ListenersHandler(Dialog dialog) {
mDialog = new WeakReference<>(dialog);
}
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case DISMISS:
((OnDismissListener) msg.obj).onDismiss(mDialog.get());
break;
case CANCEL:
((OnCancelListener) msg.obj).onCancel(mDialog.get());
break;
case SHOW:
((OnShowListener) msg.obj).onShow(mDialog.get());
break;
}
}
}
继续看 onDismiss 做了哪些处理:
onDismiss
进入父类 PreferenceDialogFragment.java:
@Override
public void onDismiss(DialogInterface dialog) {
// 点击
super.onDismiss(dialog);
onDialogClosed(mWhichButtonClicked == DialogInterface.BUTTON_POSITIVE);
}
点击后到达 DialogFragment.java:
public void onDismiss(DialogInterface dialog) {
if (!mViewDestroyed) {
// Note: we need to use allowStateLoss, because the dialog
// dispatches this asynchronously so we can receive the call
// after the activity is paused. Worst case, when the user comes
// back to the activity they see the dialog again.
dismissInternal(true);
}
}
通过注释我们可以(通过有道翻译)知道:
注意:我们需要使用allowStateLoss,因为对话框异步地分派这个调用,这样我们就可以在activity paused后接收调用。最坏的情况是,当用户返回到activity时,他们再次看到对话框。
mViewDestroyed 初始化的时候被设置为 false,所以默认是触发 dismissInternal(true); 这个方法的
@Override
public void onStart() {
super.onStart();
if (mDialog != null) {
mViewDestroyed = false;
mDialog.show();
}
}
void dismissInternal(boolean allowStateLoss)
void dismissInternal(boolean allowStateLoss) {
if (mDismissed) {
return;
}
mDismissed = true;
mShownByMe = false;
if (mDialog != null) {
mDialog.dismiss();
mDialog = null;
}
mViewDestroyed = true;
if (mBackStackId >= 0) {
getFragmentManager().popBackStack(mBackStackId,
FragmentManager.POP_BACK_STACK_INCLUSIVE);
mBackStackId = -1;
} else {
FragmentTransaction ft = getFragmentManager().beginTransaction();
ft.remove(this);
if (allowStateLoss) {
ft.commitAllowingStateLoss();
} else {
ft.commit();
}
}
}
显示的时候:
FragmentTransaction ft = manager.beginTransaction();
ft.add(this, tag);
ft.commit();
dialog消失的时候:
FragmentTransaction ft = getFragmentManager().beginTransaction();
ft.remove(this);
if (allowStateLoss) {
ft.commitAllowingStateLoss();
} else {
ft.commit();
}
总结
如果设置了 dialog.setOnDismissListener(null)
那么 点击空白区的时候(不调用dismiss()
),不会执行 dismissInternal(true),从而FragmentTransaction 不会remove这个dialogfragment;而注释写的很明白,当用户返回到activity时,会再次看到对话框。
如果点击按钮,执行了dismiss()
,则不会再出现对话框!!
共同学习,写下你的评论
评论加载中...
作者其他优质文章