当我试图从一个片段导航到另一个片段时,我遇到了新的Android导航架构组件的问题,我得到了这个奇怪的错误:

java.lang.IllegalArgumentException: navigation destination XXX
is unknown to this NavController

其他导航都很好,除了这个。

我使用Fragment的findNavController()函数来访问NavController。

任何帮助都将不胜感激。


当前回答

如果你使用的是recyclerview,只需在你的点击上添加一个点击监听器冷却时间,并在你的recyclerview xml文件中使用android:splitMotionEvents="false"

其他回答

在我的例子中,如果用户非常非常快地点击同一个视图两次,这个崩溃就会发生。所以你需要实现某种逻辑来防止多次快速点击……这很烦人,但似乎是必要的。

你可以在这里阅读更多关于防止这种情况的内容:Android防止双击按钮

编辑3/19/2019:为了进一步澄清一点,这个崩溃不是仅仅通过“非常非常快地单击同一个视图两次”就可以完全重现的。或者,您可以使用两个手指同时单击两个(或更多)视图,其中每个视图都有自己的导航。当你有一个项目列表时,这尤其容易做到。以上关于多次点击预防的信息将处理这种情况。

编辑4/16/2020:以防你对上面的Stack Overflow帖子不太感兴趣,我包括了我自己的(Kotlin)解决方案,我已经使用了很长一段时间了。

OnSingleClickListener.kt

class OnSingleClickListener : View.OnClickListener {

    private val onClickListener: View.OnClickListener

    constructor(listener: View.OnClickListener) {
        onClickListener = listener
    }

    constructor(listener: (View) -> Unit) {
        onClickListener = View.OnClickListener { listener.invoke(it) }
    }

    override fun onClick(v: View) {
        val currentTimeMillis = System.currentTimeMillis()

        if (currentTimeMillis >= previousClickTimeMillis + DELAY_MILLIS) {
            previousClickTimeMillis = currentTimeMillis
            onClickListener.onClick(v)
        }
    }

    companion object {
        // Tweak this value as you see fit. In my personal testing this
        // seems to be good, but you may want to try on some different
        // devices and make sure you can't produce any crashes.
        private const val DELAY_MILLIS = 200L

        private var previousClickTimeMillis = 0L
    }

}

ViewExt.kt

fun View.setOnSingleClickListener(l: View.OnClickListener) {
    setOnClickListener(OnSingleClickListener(l))
}

fun View.setOnSingleClickListener(l: (View) -> Unit) {
    setOnClickListener(OnSingleClickListener(l))
}

HomeFragment.kt

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)

    settingsButton.setOnSingleClickListener {
        // navigation call here
    }
}

I am calling the 2.3.1 Navigation and the same error occurs when the application configuration changes. When the cause of the problem was found through Debug, the GaphId in NavHostFragment did not take effect as the ID currently set by calling navController.setGraph(). The GraphId of NavHostFragment can only be obtained from the <androidx.fragment.app.FragmentContainerView/> tag. At this time, this problem will occur if there are multiple GraphIds dynamically set in your code. When the interface is restored, the Destination cannot be found in the cached GraphId. You can solve this problem by manually specifying the value of mGraphId in NavHostFragment through reflection when switching Graph.

navController.setGraph(R.navigation.home_book_navigation);
try {
    Field graphIdField = hostFragment.getClass().getDeclaredField("mGraphId");
    graphIdField.setAccessible(true);
    graphIdField.set(navHostFragment, R.navigation.home_book_navigation);
} catch (NoSuchFieldException | IllegalAccessException e) {
    e.printStackTrace();
}

似乎混合fragmentManager控件的backstack和Navigation Architecture控件的backstack也会导致这个问题。

例如,最初的CameraX基本示例使用fragmentManager后台导航,如下所示,它似乎没有正确地与导航交互:

// Handle back button press
        view.findViewById<ImageButton>(R.id.back_button).setOnClickListener {
            fragmentManager?.popBackStack()
        }

如果你在从主片段(在本例中是相机片段)移动之前记录这个版本的“当前目的地”,然后当你返回主片段时再次记录它,你可以从日志中的id中看到id是不相同的。据猜测,导航在移动到片段时更新了它,而fragmntManager在移动回来时没有再次更新它。日志显示:

修改前:D/CameraXBasic: currentDest?: androidx.navigation.fragment.FragmentNavigator Destination@b713195美元 After: D/CameraXBasic: currentDest?: androidx.navigation.fragment.FragmentNavigator Destination@9807d8f美元

更新版的CameraX基本示例使用导航返回如下:

 // Handle back button press
        view.findViewById<ImageButton>(R.id.back_button).setOnClickListener {
            Navigation.findNavController(requireActivity(), R.id.fragment_container).navigateUp()
        }

这可以正常工作,当返回到主片段时,日志显示相同的id。

修改前:D/CameraXBasic: currentDest?: androidx.navigation.fragment.FragmentNavigator Destination@b713195美元 After: D/CameraXBasic: currentDest?: androidx.navigation.fragment.FragmentNavigator Destination@b713195美元

我怀疑这个故事的寓意,至少在这个时候,是要非常小心地混合导航和fragmentManager导航。

将我的答案优雅地扔到处理两种情况(双击,同时点击两个按钮)的环中,但尽量不掩盖真正的错误。

我们可以使用navigateSafe()函数来检查我们试图导航到的目的地从当前目的地是否是无效的,但从前一个目的地是否是有效的。如果是这种情况,代码假设用户双击或同时点击两个按钮。

然而,这个解决方案并不完美,因为它可能会掩盖一些小众情况下的实际问题,即我们试图导航到恰好是父端的目的地。但据推测,这种情况不太可能发生。

代码:

fun NavController.navigateSafe(directions: NavDirections) {
    val navigateWillError = currentDestination?.getAction(directions.actionId) == null

    if (navigateWillError) {
        if (previousBackStackEntry?.destination?.getAction(directions.actionId) != null) {
            // This is probably some user tapping two different buttons or one button twice quickly
            // Ignore...
            return
        }

        // This seems like a programming error. Proceed and let navigate throw.
    }

    navigate(directions)
}

通常当这种情况发生在我身上时,我遇到了Charles Madere所描述的问题:在同一个ui上触发了两个导航事件,一个改变了currentDestination,另一个失败是因为currentDestination被改变了。 如果双击或单击两个视图,就会发生这种情况,其中有一个点击侦听器调用findNavController.navigate。

所以要解决这个问题,你可以使用if-checks, try-catch,或者如果你感兴趣,有一个findSafeNavController(),它在导航之前为你做这个检查。它还有一个检查,以确保您不会忘记这个问题。

GitHub

详细说明问题的文章