天天看点

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传值在跳转报错

继续阅读