这里记录下在Navigation中,DialogFragment执行 popBackStack() 和 dismiss() 的比较:
popBackStack()
Attempt to pop this navigator's back stack, performing the appropriate navigation.
> 尝试弹出此导航器的后堆栈,执行适当的导航。
dismiss()
Dismiss the fragment and its dialog.
>关闭片段及其对话框。
#1 执行后的表现:
两种方法执行后,DialogFragment都随之消失;所以如果在只是需要关闭当前的DialogFragment,二者的效果都一样。
#2 各自源码:
# popBackStack()
/**
* Attempts to pop the controller's back stack. Analogous to when the user presses
* the system {@link android.view.KeyEvent#KEYCODE_BACK Back} button when the associated
* navigation host has focus.
*
* @return true if the stack was popped and the user has been navigated to another
* destination, false otherwise
*/
public boolean popBackStack() {
if (mBackStack.isEmpty()) {
// Nothing to pop if the back stack is empty
return false;
}
// Pop just the current destination off the stack
return popBackStack(getCurrentDestination().getId(), true);
}
还看不出什么,在继续往下看:
/**
* Attempts to pop the controller's back stack back to a specific destination.
*
* @param destinationId The topmost destination to retain
* @param inclusive Whether the given destination should also be popped.
*
* @return true if the stack was popped at least once and the user has been navigated to
* another destination, false otherwise
*/
public boolean popBackStack(@IdRes int destinationId, boolean inclusive) {
boolean popped = popBackStackInternal(destinationId, inclusive);
// Only return true if the pop succeeded and we've dispatched
// the change to a new destination
return popped && dispatchOnDestinationChanged();
}
看到了 popBackStackInternal(destinationId, inclusive) 这里看下这个方法:
/**
* Attempts to pop the controller's back stack back to a specific destination. This does
* <strong>not</strong> handle calling {@link #dispatchOnDestinationChanged()}
*
* @param destinationId The topmost destination to retain
* @param inclusive Whether the given destination should also be popped.
*
* @return true if the stack was popped at least once, false otherwise
*/
@SuppressWarnings("WeakerAccess") /* synthetic access */
boolean popBackStackInternal(@IdRes int destinationId, boolean inclusive) {
if (mBackStack.isEmpty()) {
// Nothing to pop if the back stack is empty
return false;
}
... 省略部分代码
boolean popped = false;
for (Navigator<?> navigator : popOperations) {
if (navigator.popBackStack()) {
NavBackStackEntry entry = mBackStack.removeLast();
entry.setMaxLifecycle(Lifecycle.State.DESTROYED);
if (mViewModel != null) {
mViewModel.clear(entry.mId);
}
popped = true;
} else {
// The pop did not complete successfully, so stop immediately
break;
}
}
updateOnBackPressedCallbackEnabled();
return popped;
}
于是乎看到了自己想要的代码:(截图贴一下)
所以通俗点的理解就是 将该对话框(DialogFragment)的 destination_Id 从后堆栈(BackStack)中删除,然后将该生命周期移至给定状态(DESTROYED),并向DialogFragment观察者调度必要的事件(onStop, onDestroyView...)。
# dismiss()
/**
* Dismiss the fragment and its dialog. If the fragment was added to the
* back stack, all back stack state up to and including this entry will
* be popped. Otherwise, a new transaction will be committed to remove
* the fragment.
*/
public void dismiss() {
dismissInternal(false, false);
}
注释:关闭片段及其对话框。 如果将片段添加到后堆栈,则将弹出直至该条目的所有后堆栈状态。 否则,将提交一个新事务以删除该片段。
这不就是和 popBackStack() 一样啊!那继续看 dismissInternal(false, false):
private void dismissInternal(boolean allowStateLoss, boolean fromOnDismiss) {
if (mDismissed) {
return;
}
... 省略部分代码
if (mBackStackId >= 0) {
getParentFragmentManager().popBackStack(mBackStackId,
FragmentManager.POP_BACK_STACK_INCLUSIVE);
mBackStackId = -1;
} else {
... 省略部分代码
}
}
会看到会执行 getParentFragmentManager().popBackStack(mBackStackId, FragmentManager.POP_BACK_STACK_INCLUSIVE),再继续往下看:
/**
* Pop all back stack states up to the one with the given identifier.
* This function is asynchronous -- it enqueues the
* request to pop, but the action will not be performed until the application
* returns to its event loop.
*
* @param id Identifier of the stated to be popped. If no identifier exists,
* false is returned.
* The identifier is the number returned by
* {@link FragmentTransaction#commit() FragmentTransaction.commit()}. The
* {@link #POP_BACK_STACK_INCLUSIVE} flag can be used to control whether
* the named state itself is popped.
* @param flags Either 0 or {@link #POP_BACK_STACK_INCLUSIVE}.
*/
public void popBackStack(final int id, final int flags) {
if (id < 0) {
throw new IllegalArgumentException("Bad id: " + id);
}
enqueueAction(new PopBackStackState(null, id, flags), false);
}
这里有两个方法需要说下:一个很眼熟的方法:PopBackStackState() ,这和 popBackStack() 相似从backStack中删掉最新的(它本身),这里我直接贴上代码:
还一个方法不熟悉: enqueueAction(new PopBackStackState(null, id, flags), false) ,没关系,看上面那么一堆注释:
popBackStack(final int id, final int flags)的注释: 将所有后退堆栈状态弹出到具有给定标识符的状态。 此函数是异步的-它将请求弹出,但只有在应用程序返回到其事件循环之前,该操作才会执行。
enqueueAction()的注释:在尚未安排执行时间时安排执行时间。这应该在第一次调用enqueueAction(FragmentManager.OpGenerator,boolean)或使用Fragment.startPostponedEnterTransition()启动已延迟的事务时发生
⚠️⚠️⚠️ 是异步的,所以通俗点的理解就是 它最终会将该对话框(DialogFragment)的 destination_Id 从后堆栈(BackStack)中删除,但是呢,在执行 dismiss() 后,此时的对话框(DialogFragment)的 destination_Id 还在后堆栈(BackStack)中,并没有 及时 删除,而是等enqueueAction 安排执行时间后再等执行该操作后才后删除。
#3 二者区别:
区别在与 BackStack,直接用代码来说明:
// 从 ATestFragment 页面 导航到 TestDialogFragment 后,再操作 popBack()方法:
private fun popBack() {
val navController = findNavController()
with(navController) {
popBackStack()
currentBackStackEntry?.destination // 这时候 currentBackStackEntry 的 destination 则是 ATestFragment
}
}
>>> 当执行完 popBackStack(),此时的 currentBackStackEntry 的 destination 会变成之前的Fragment 即是 ATestFragment
private fun popBack() {
val navController = findNavController()
with(navController) {
dismiss()
currentBackStackEntry?.destination // 这时候 currentBackStackEntry 的 destination 仍然是 TestDialogFragment
}
}
>>> 当执行完 dismiss(),此时的 currentBackStackEntry 对应的 destination 仍然还是 TestDialogFragment ,previousBackStackEntry 对应的 destination 才是上一个页面 ATestFragment。
⚠️⚠️⚠️ 如果需要使用 currentBackStackEntry?.savedStateHandle / previousBackStackEntry?.savedStateHandle 传值时,则需要注意当前的destination是不是你需要监听值的地方!!
贴上示例:Navigation:DialogFragment执行dismiss/popBackStack 后使用previousBackStackEntry传值在跳转报错