天天看點

Navigation: DialogFragment popBackStack() 和 dismiss() 比較currentBackStackEntry/previousBackStackEntry

這裡記錄下在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;
    }
           

于是乎看到了自己想要的代碼:(截圖貼一下)

Navigation: DialogFragment popBackStack() 和 dismiss() 比較currentBackStackEntry/previousBackStackEntry

是以通俗點的了解就是 将該對話框(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中删掉最新的(它本身),這裡我直接貼上代碼:

Navigation: DialogFragment popBackStack() 和 dismiss() 比較currentBackStackEntry/previousBackStackEntry

還一個方法不熟悉: 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傳值在跳轉報錯

繼續閱讀