這裡記錄下在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傳值在跳轉報錯