213

当我尝试从一个 Fragment 导航到另一个 Fragment 时,我遇到了新的 Android Navigation Architecture 组件的问题,我收到了这个奇怪的错误:

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

除了这个特定的导航之外,其他所有导航都可以正常工作。

我使用findNavController()Fragment 的功能来访问NavController.

任何帮助将不胜感激。

4

40 回答 40

116

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

您可以在此处阅读有关防止此问题的更多信息:Android 防止双击按钮

编辑 2019 年 3 月 19 日:为了进一步澄清,这种崩溃并非完全可以通过“非常快速地两次单击同一视图”来重现。或者,您可以只用两根手指同时单击两个(或更多)视图,每个视图都有自己的导航,它们将执行。当您有一个项目列表时,这尤其容易做到。以上关于防止多次点击的信息将处理这种情况。

编辑 2020年 4 月 16 日:以防您对阅读上面的 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
    }
}
于 2018-09-20T00:17:18.920 回答
99

在调用导航之前检查currentDestination可能会有所帮助。

例如,如果您在导航图fragmentA和上有两个片段目的地fragmentB,并且只有一个动作 fromfragmentAfragmentB。呼叫navigate(R.id.action_fragmentA_to_fragmentB)将导致IllegalArgumentException您已经在fragmentB. currentDestination因此,您应该在导航之前始终检查。

if (navController.currentDestination?.id == R.id.fragmentA) {
    navController.navigate(R.id.action_fragmentA_to_fragmentB)
}
于 2018-12-12T06:52:33.040 回答
64

您可以在导航控制器的当前目的地检查请求的操作。

UPDATE 添加了全局操作的使用以实现安全导航。

fun NavController.navigateSafe(
        @IdRes resId: Int,
        args: Bundle? = null,
        navOptions: NavOptions? = null,
        navExtras: Navigator.Extras? = null
) {
    val action = currentDestination?.getAction(resId) ?: graph.getAction(resId)
    if (action != null && currentDestination?.id != action.destinationId) {
        navigate(resId, args, navOptions, navExtras)
    }
}
于 2019-01-11T12:34:33.200 回答
43

我为防止崩溃所做的事情如下:

我有一个 BaseFragment,我在其中添加了它fun以确保它destination是已知的currentDestination

fun navigate(destination: NavDirections) = with(findNavController()) {
    currentDestination?.getAction(destination.actionId)
        ?.let { navigate(destination) }
}

值得注意的是,我正在使用SafeArgs插件。

于 2019-06-19T22:52:21.653 回答
21

如果您有一个带有片段 B 的 ViewPager 的片段 A 并且您尝试从 B 导航到 C

由于在 ViewPager 中,片段不是 A 的目的地,因此您的图表不会知道您在 B 上。

解决方案可以是使用 B 中的 ADirections 导航到 C

于 2019-04-16T09:49:39.473 回答
14

就我而言,我使用自定义后退按钮进行向上导航。我打电话onBackPressed()而不是下面的代码

findNavController(R.id.navigation_host_fragment).navigateUp()

这导致IllegalArgumentException发生。在我将其更改为使用该navigateUp()方法后,我没有再次崩溃。

于 2018-07-25T07:15:37.083 回答
14

试试看

  1. 创建这个扩展函数(或普通函数):

已更新(没有反射且更具可读性)

import androidx.fragment.app.Fragment
import androidx.navigation.NavController
import androidx.navigation.NavDirections
import androidx.navigation.fragment.DialogFragmentNavigator
import androidx.navigation.fragment.FragmentNavigator

fun Fragment.safeNavigateFromNavController(directions: NavDirections) {
    val navController = findNavController()
    when (val destination = navController.currentDestination) {
        is FragmentNavigator.Destination -> {
            if (javaClass.name == destination.className) {
                navController.navigate(directions)
            }
        }
        is DialogFragmentNavigator.Destination -> {
            if (javaClass.name == destination.className) {
                navController.navigate(directions)
            }
        }
    }
}

旧(带反射)

import androidx.fragment.app.Fragment
import androidx.navigation.NavController
import androidx.navigation.NavDirections
import androidx.navigation.fragment.FragmentNavigator

inline fun <reified T : Fragment> NavController.safeNavigate(directions: NavDirections) {
    val destination = this.currentDestination as FragmentNavigator.Destination
    if (T::class.java.name == destination.className) {
        navigate(directions)
    }
}
  1. 并从您的片段中这样使用:
val direction = FragmentOneDirections.actionFragmentOneToFragmentTwo()
// new usage
safeNavigateFromNavController(direction)

// old usage
// findNavController().safeNavigate<FragmentOne>(action)

我的问题是

我有一个片段(FragmentOne),它指向另外两个片段(FragmentTwo 和 FragmentThree)。在一些低端设备中,用户按下重定向到 FragmentTwo 的按钮,但在用户按下重定向到 FragmentThree 的按钮后几毫秒。结果是:

致命异常:java.lang.IllegalArgumentException Navigation action/destination action_fragmentOne_to_fragmentTwo 无法从当前目的地 Destination(fragmentThree) class=FragmentThree

我的解决办法是:

我检查当前目的地是否属于当前片段。如果为真,我执行导航动作。

就这些!

于 2021-01-29T17:54:10.233 回答
12

TL;DR用(简单的方式)包裹你的navigate电话try-catch,或者确保navigate在短时间内只有一个电话。这个问题很可能不会消失。在您的应用中复制更大的代码片段并尝试。

你好。基于上面的几个有用的回答,我想分享我可以扩展的解决方案。

这是导致我的应用程序崩溃的代码:

@Override
public void onListItemClicked(ListItem item) {
    Bundle bundle = new Bundle();
    bundle.putParcelable(SomeFragment.LIST_KEY, item);
    Navigation.findNavController(recyclerView).navigate(R.id.action_listFragment_to_listItemInfoFragment, bundle);
}

一种轻松重现错误的方法是用多个手指点击项目列表,单击每个项目会在导航到新屏幕时解析(基本上与人们指出的相同 - 在很短的时间内点击两次或多次)。我注意到:

  1. 第一次navigate调用总是可以正常工作;
  2. 该方法的第二次和所有其他调用都navigate解析为IllegalArgumentException.

在我看来,这种情况可能会经常出现。由于重复代码是一种不好的做法,而且有一点影响总是好的,我想到了下一个解决方案:

public class NavigationHandler {

public static void navigate(View view, @IdRes int destination) {
    navigate(view, destination, /* args */null);
}

/**
 * Performs a navigation to given destination using {@link androidx.navigation.NavController}
 * found via {@param view}. Catches {@link IllegalArgumentException} that may occur due to
 * multiple invocations of {@link androidx.navigation.NavController#navigate} in short period of time.
 * The navigation must work as intended.
 *
 * @param view        the view to search from
 * @param destination destination id
 * @param args        arguments to pass to the destination
 */
public static void navigate(View view, @IdRes int destination, @Nullable Bundle args) {
    try {
        Navigation.findNavController(view).navigate(destination, args);
    } catch (IllegalArgumentException e) {
        Log.e(NavigationHandler.class.getSimpleName(), "Multiple navigation attempts handled.");
    }
}

}

因此,上面的代码仅在一行中发生了变化:

Navigation.findNavController(recyclerView).navigate(R.id.action_listFragment_to_listItemInfoFragment, bundle);

对此:

NavigationHandler.navigate(recyclerView, R.id.action_listFragment_to_listItemInfoFragment, bundle);

它甚至变得有点短。该代码在发生崩溃的确切位置进行了测试。没有再体验过,其他导航也会使用相同的解决方案,以进一步避免同样的错误。

欢迎任何想法!

究竟是什么导致了崩溃

请记住,当我们使用方法时,我们在这里使用相同的导航图、导航控制器和后台堆栈Navigation.findNavController

我们总是在这里得到相同的控制器和图形。当UI 尚未更新时navigate(R.id.my_next_destination),图形和 back-stack几乎立即发生变化。只是不够快,但没关系。在回栈改变后,导航系统接收到第二个navigate(R.id.my_next_destination)调用。由于 back-stack 发生了变化,我们现在相对于堆栈中的顶部片段进行操作。顶部片段是您使用 导航到的片段R.id.my_next_destination,但它不包含下一个带有 ID 的其他目的地R.id.my_next_destination。因此,您会IllegalArgumentException因为片段一无所知的 ID 而得到。

这个确切的错误可以在NavController.javamethod中找到findDestination

于 2019-06-01T21:04:13.260 回答
6

就我而言,当我在一个片段中重新使用我的一个viewpager片段作为viewpager. 在viewpagerNavigation xml 中添加了 Fragment(它是父 Fragment),但未在父 Fragment 中添加该操作viewpager

nav.xml
//reused fragment
<fragment
    android:id="@+id/navigation_to"
    android:name="com.package.to_Fragment"
    android:label="To Frag"
    tools:layout="@layout/fragment_to" >
    //issue got fixed when i added this action to the viewpager parent also
    <action android:id="@+id/action_to_to_viewall"
        app:destination="@+id/toViewAll"/>
</fragment>
....
// viewpager parent fragment
<fragment
    android:id="@+id/toViewAll"
    android:name="com.package.ViewAllFragment"
    android:label="to_viewall_fragment"
    tools:layout="@layout/fragment_view_all">

通过将操作添加到父 viewpager 片段来修复问题,如下所示:

nav.xml
//reused fragment
<fragment
    android:id="@+id/navigation_to"
    android:name="com.package.to_Fragment"
    android:label="To Frag"
    tools:layout="@layout/fragment_to" >
    //issue got fixed when i added this action to the viewpager parent also
    <action android:id="@+id/action_to_to_viewall"
        app:destination="@+id/toViewAll"/>
</fragment>
....
// viewpager parent fragment
<fragment
    android:id="@+id/toViewAll"
    android:name="com.package.ViewAllFragment"
    android:label="to_viewall_fragment"
    tools:layout="@layout/fragment_view_all"/>
    <action android:id="@+id/action_to_to_viewall"
        app:destination="@+id/toViewAll"/>
</fragment>
于 2019-10-12T19:10:19.267 回答
6

今天

def 导航版本 = "2.2.1"

问题依然存在。我对 Kotlin 的做法是:

// To avoid "java.lang.IllegalArgumentException: navigation destination is unknown to this NavController", se more https://stackoverflow.com/q/51060762/6352712
fun NavController.navigateSafe(
    @IdRes destinationId: Int,
    navDirection: NavDirections,
    callBeforeNavigate: () -> Unit
) {
    if (currentDestination?.id == destinationId) {
        callBeforeNavigate()
        navigate(navDirection)
    }
}

fun NavController.navigateSafe(@IdRes destinationId: Int, navDirection: NavDirections) {
    if (currentDestination?.id == destinationId) {
        navigate(navDirection)
    }
}
于 2020-04-13T12:44:23.907 回答
4
于 2020-05-05T09:03:06.380 回答
4

就我而言,我有多个导航图文件,我试图从 1 个导航图位置移动到另一个导航图中的目的地。

为此,我们必须像这样在第一个导航图中包含第二个导航图

<include app:graph="@navigation/included_graph" />

并将其添加到您的操作中:

<action
        android:id="@+id/action_fragment_to_second_graph"
        app:destination="@id/second_graph" />

哪里second_graph是:

<navigation xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/second_graph"
    app:startDestination="@id/includedStart">

在第二张图中。

更多信息在这里

于 2019-12-10T08:41:23.103 回答
4

我通过在导航之前进行检查而不是样板代码来单击即时控制来解决了同样的问题

 if (findNavController().currentDestination?.id == R.id.currentFragment) {
        findNavController().navigate(R.id.action_current_next)}
/* Here R.id.currentFragment is the id of current fragment in navigation graph */

根据这个答案

https://stackoverflow.com/a/56168225/7055259

于 2020-05-04T08:47:40.830 回答
3

我通过检查当前目的地中是否存在下一个操作来解决此问题

public static void launchFragment(BaseFragment fragment, int action) {
    if (fragment != null && NavHostFragment.findNavController(fragment).getCurrentDestination().getAction(action) != null) {       
        NavHostFragment.findNavController(fragment).navigate(action);
    }
}

public static void launchFragment(BaseFragment fragment, NavDirections directions) {
    if (fragment != null && NavHostFragment.findNavController(fragment).getCurrentDestination().getAction(directions.getActionId()) != null) {       
        NavHostFragment.findNavController(fragment).navigate(directions);
    }
}

如果用户快速单击 2 个不同按钮,这将解决问题

于 2020-08-12T10:36:12.290 回答
3

如其他答案所述,此异常通常发生在用户

  1. 同时点击多个处理导航的视图
  2. 在处理导航的视图上单击多次。

使用计时器禁用点击不是处理此问题的合适方法。如果在计时器到期后用户还没有被导航到目的地,那么应用程序无论如何都会崩溃,并且在许多情况下导航不是要执行的操作,快速点击是必要的。

在情况 1 中,android:splitMotionEvents="false"在 xml 或setMotionEventSplittingEnabled(false)源文件中应该有帮助。将此属性设置为 false 将只允许一个视图进行点击。你可以在这里阅读

在情况 2 中,会出现一些延迟导航过程的情况,从而允许用户多次单击视图(API 调用、动画等)。如果可能,应该解决根本问题,以便立即进行导航,不允许用户单击视图两次。如果延迟是不可避免的,例如在 API 调用的情况下,禁用视图或使其不可点击将是合适的解决方案。

于 2020-08-25T13:47:01.140 回答
3

为了避免这种崩溃,我的一位同事编写了一个小型库,该库公开了一个SafeNavController, 一个包装器,NavController并处理由于同时多个导航命令而发生此崩溃的情况。

这是一篇关于整个问题和解决方案的简短文章。

你可以在这里找到图书馆。

于 2020-09-10T14:19:14.583 回答
3

在考虑了 Ian Lake 在这个 twitter 线程中的建议之后,我想出了以下方法。定义NavControllerWrapper如下:

class NavControllerWrapper constructor(
  private val navController: NavController
) {

  fun navigate(
    @IdRes from: Int,
    @IdRes to: Int
  ) = navigate(
    from = from,
    to = to,
    bundle = null
  )

  fun navigate(
    @IdRes from: Int,
    @IdRes to: Int,
    bundle: Bundle?
  ) = navigate(
    from = from,
    to = to,
    bundle = bundle,
    navOptions = null,
    navigatorExtras = null
  )

  fun navigate(
    @IdRes from: Int,
    @IdRes to: Int,
    bundle: Bundle?,
    navOptions: NavOptions?,
    navigatorExtras: Navigator.Extras?
  ) {
    if (navController.currentDestination?.id == from) {
      navController.navigate(
        to,
        bundle,
        navOptions,
        navigatorExtras
      )
    }
  }

  fun navigate(
    @IdRes from: Int,
    directions: NavDirections
  ) {
    if (navController.currentDestination?.id == from) {
      navController.navigate(directions)
    }
  }

  fun navigateUp() = navController.navigateUp()

  fun popBackStack() = navController.popBackStack()
}

然后在导航代码中:

val navController = navControllerProvider.getNavController()
navController.navigate(from = R.id.main, to = R.id.action_to_detail)

于 2020-03-20T18:53:46.783 回答
3

在我的情况下,出现错误是因为我在启动屏幕后启用了导航操作Single Top和选项。Clear Task

于 2018-06-28T00:16:42.493 回答
2

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

例如,原始的 CameraX 基本示例使用了 fragmentManager backstack navigation,如下所示,它看起来好像没有正确地与 Navigation 交互:

// 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

之后:D/CameraXBasic: currentDest?: androidx.navigation.fragment.FragmentNavigator$Destination@9807d8f

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

 // 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

之后:D/CameraXBasic: currentDest?: androidx.navigation.fragment.FragmentNavigator$Destination@b713195

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

于 2019-12-31T13:53:11.477 回答
2

我写了这个扩展

fun Fragment.navigateAction(action: NavDirections) {
    val navController = this.findNavController()
    if (navController.currentDestination?.getAction(action.actionId) == null) {
        return
    } else {
        navController.navigate(action)
    }
}
于 2020-03-16T12:07:44.687 回答
2

更新@AlexNuts 答案以支持导航到嵌套图。当一个操作使用嵌套图作为目标时,如下所示:

<action
    android:id="@+id/action_foo"
    android:destination="@id/nested_graph"/>

此操作的目标 ID 无法与当前目标进行比较,因为当前目标不能是图。必须解析嵌套图的起始目的地。

fun NavController.navigateSafe(directions: NavDirections) {
    // Get action by ID. If action doesn't exist on current node, return.
    val action = (currentDestination ?: graph).getAction(directions.actionId) ?: return
    var destId = action.destinationId
    val dest = graph.findNode(destId)
    if (dest is NavGraph) {
        // Action destination is a nested graph, which isn't a real destination.
        // The real destination is the start destination of that graph so resolve it.
        destId = dest.startDestination
    }
    if (currentDestination?.id != destId) {
        navigate(directions)
    }
}

但是,这将防止导航到同一目的地两次,这有时是需要的。为此,您可以添加检查action.navOptions?.shouldLaunchSingleTop()并添加app:launchSingleTop="true"到您不希望重复目标的操作。

于 2020-08-16T21:38:59.423 回答
2

我得到了同样的错误,因为我使用了导航抽屉,getSupportFragmentManager().beginTransaction().replace( )同时在我的代码中的某个地方。

我通过使用这个条件(测试目标)摆脱了错误:

if (Navigation.findNavController(v).getCurrentDestination().getId() == R.id.your_destination_fragment_id)
Navigation.findNavController(v).navigate(R.id.your_action);

在我的情况下,当我单击导航抽屉选项时触发了先前的错误。基本上上面的代码确实隐藏了错误,因为在我的代码中我使用getSupportFragmentManager().beginTransaction().replace( )了条件导航 -

 if (Navigation.findNavController(v).getCurrentDestination().getId() ==
  R.id.your_destination_fragment_id) 

从未到达,因为(Navigation.findNavController(v).getCurrentDestination().getId()总是指向家庭片段。您只能Navigation.findNavController(v).navigate(R.id.your_action)对所有导航操作使用或导航图形控制器功能。

于 2020-01-31T01:25:33.283 回答
2

一种荒谬但非常强大的方法是:简单地调用它:

view?.findNavController()?.navigateSafe(action)

只需创建此扩展:

fun NavController.navigateSafe(
    navDirections: NavDirections? = null
) {
    try {
        navDirections?.let {
            this.navigate(navDirections)
        }
    }
    catch (e:Exception)
    {
        e.printStackTrace()
    }
}
于 2020-02-21T17:30:46.903 回答
2

这个问题可能有很多原因。在我的情况下,我使用的是 MVVM 模型,当布尔值为真时,我正在观察一个用于导航的布尔值 - > 导航否则不做任何事情,这工作正常,但这里有一个错误

当从目标片段按下后退按钮时,我遇到了同样的问题。问题是布尔对象,因为我忘记将布尔值更改为 false,这造成了混乱。我刚刚在 viewModel 中创建了一个函数来将其值更改为 false 和在 findNavController() 之后调用它

于 2020-03-13T06:11:51.680 回答
1

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

我们可以使用一个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)
}
于 2021-07-04T08:11:32.477 回答
1

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

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

GitHub

文章详细说明问题

于 2020-03-13T13:37:38.603 回答
1

我正在调用 2.3.1 导航,并且在应用程序配置更改时发生相同的错误。当通过Debug找到问题原因时,GaphIdinNavHostFragment没有生效,因为当前调用设置的ID navController.setGraph()GraphIdofNavHostFragment只能从<androidx.fragment.app.FragmentContainerView/>标签中获得。GraphId这时候如果你的代码中动态设置了多个s就会出现这个问题。接口恢复后,缓存中找不到Destination GraphId。可以在切换Graph的时候通过反射手动指定mGraphIdin的值来解决这个问题。NavHostFragment

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();
}
于 2020-11-29T06:53:54.443 回答
1

当我按两次后退按钮时,它发生在我身上。起初,我拦截KeyListener并覆盖KeyEvent.KEYCODE_BACK. 我在为 Fragment 命名的函数中添加了下面的代码OnResume,然后这个问题/问题就解决了。

  override fun onResume() {
        super.onResume()
        view?.isFocusableInTouchMode = true
        view?.requestFocus()
        view?.setOnKeyListener { v, keyCode, event ->
            if (event.action == KeyEvent.ACTION_DOWN && keyCode == KeyEvent.KEYCODE_BACK) {
                activity!!.finish()
                true
            }
            false
        }
    }

当它第二次发生在我身上时,它的状态与第一次相同,我发现我可能会使用该adsurd功能。让我们分析一下这些情况。

  1. 首先, FragmentA 导航到 FragmentB ,然后 FragmentB 导航到 FragmentA,然后按返回按钮...出现崩溃。

  2. 其次,FragmentA 导航到 FragmentB,然后 FragmentB 导航到 FragmentC,FragmentC 导航到 FragmentA,然后按返回按钮……出现崩溃。

所以我认为当按下返回按钮时,FragmentA 会返回到 FragmentB 或 FragmentC,然后它会导致登录混乱。最后我发现命名的函数popBackStack可以用于返回而不是导航。

  NavHostFragment.findNavController(this@TeacherCloudResourcesFragment).
                        .popBackStack(
                            R.id.teacher_prepare_lesson_main_fragment,false
                        )

至此,问题真的解决了。

于 2019-08-29T08:51:40.060 回答
1

如果点击太快,会导致null和crash。

我们可以使用 RxBinding 库来帮助解决这个问题。您可以在点击发生之前添加油门和持续时间。

 RxView.clicks(view).throttleFirst(duration, TimeUnit.MILLISECONDS)
            .subscribe(__ -> {
            });

这些关于 Android 节流的文章可能会有所帮助干杯!

于 2020-04-17T16:04:29.017 回答
1

在对类进行了一些重命名后,我发现了这个异常。例如:我在导航图中使用 with 和FragmentAwith调用了类。然后我删除并重命名为. 所以在那个节点之后仍然停留在导航图中,并且的节点被重命名了。我有两个具有相同和不同的节点,并且我需要的操作是在已删除类的节点上定义的。@+is/fragment_aFragmentB@+id/fragment_bFragmentAFragmentBFragmentAFragmentAandroid:nameFragmentBpath.to.FragmentAandroid:nameandroid:id

于 2019-01-30T15:52:36.613 回答
1

看来您正在清除任务。应用程序可能具有一次性设置或一系列登录屏幕。这些条件屏幕不应被视为您的应用程序的起始目的地。

https://developer.android.com/topic/libraries/architecture/navigation/navigation-conditional

于 2018-06-29T08:59:33.567 回答
1

同样的快速点击导航崩溃问题的另一种解决方案:

fun NavController.doIfCurrentDestination(@IdRes destination: Int, action: NavController.()-> Unit){
    if(this.currentDestination?.id == destination){action()}
}

然后像这样使用:

findNavController().doIfCurrentDestination(R.id.my_destination){ navigate(...) }

此解决方案的好处是您可以轻松地naviagte()使用您已经使用的任何签名包装任何现有调用,无需进行一百万次重载

于 2020-12-15T15:41:01.763 回答
0

这发生在我身上,我的问题是我在tab item fragment. 我试图从选项卡项片段之一导航到another fragment.

但是根据Ian Lake这个答案中的说法,我们必须使用tablayout并且viewpager没有导航组件支持。因此,没有从包含片段的选项卡布局到选项卡项片段的导航路径。

前任:

containing fragment -> tab layout fragment -> tab item fragment -> another fragment

解决方案是创建一个从包含片段的选项卡布局到预期片段的路径:路径:container fragment -> another fragment

坏处:

  • 导航图不再准确表示用户流。
于 2019-07-02T04:58:46.757 回答
0

更新了@Alex Nuts 解决方案

如果没有针对特定片段的操作并且想要导航到片段

fun NavController.navigateSafe(
@IdRes actionId: Int, @IdRes fragmentId: Int, args: Bundle? = null,
navOptions: NavOptions? = null, navExtras: Navigator.Extras? = null) 
{
  if (actionId != 0) {
      val action = currentDestination?.getAction(actionId) ?: graph.getAction(actionId)
      if (action != null && currentDestination?.id != action.destinationId) {
          navigate(actionId, args, navOptions, navExtras)
    }
    } else if (fragmentId != 0 && fragmentId != currentDestination?.id)
        navigate(fragmentId, args, navOptions, navExtras)
}
于 2020-01-15T04:15:49.993 回答
0

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

于 2020-04-18T12:45:48.227 回答
0

在链接中回答:https ://stackoverflow.com/a/67614469/5151336

为导航器添加了扩展功能,以实现安全导航。

于 2021-05-21T09:17:43.530 回答
0

为防止崩溃,我建议使用 safeargs 插件,否则您需要每次检查目标 ID 是否存在。另一件事是在底部导航视图的菜单文件中,每个项目 id 必须与在导航图 xml 文件中添加的片段 id 相同。

如果使用 kotlin,则需要在 build.gradle 中添加一些依赖项。

android{
compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }

    kotlinOptions {
        jvmTarget = JavaVersion.VERSION_1_8.toString()
    }
}

dependencies {
implementation 'androidx.navigation:navigation-fragment-ktx:2.2.1'
implementation 'androidx.navigation:navigation-ui-ktx:2.2.1'
}

对于 SafeArgs

dependencies {
classpath  'android.arch.navigation:navigation-safe-args-gradle-plugin:1.0.0'
}

要导航到目的地,首先您必须通过导航图 xml 文件中的操作将它们连接起来。

<fragment
        android:id="@+id/employeesListFragment"
        android:name="com.example.navigationjetpacksample.fragments.EmployeesListFragment"
        android:label="EmployeesListFragment"
        tools:layout="@layout/frag_employees_list">
        <action
            android:id="@+id/action_employeesListFragment_to_addEmployeeFragment"
            app:destination="@id/addEmployeeFragment" />
    </fragment>

构建/清理您的项目后,

 fab_add.setOnClickListener {   it.findNavController().navigate(EmployeesListFragmentDirections.actionEmployeesListFragmentToAddEmployeeFragment())
 }

更多参考:Jetpack 导航示例

于 2020-06-07T06:36:17.067 回答
0

我为 Fragment 创建了这个扩展函数:

fun Fragment.safeNavigate(
    @IdRes actionId: Int,
    @Nullable args: Bundle? = null,
    @Nullable navOptions: NavOptions? = null,
    @Nullable navigatorExtras: Navigator.Extras? = null
) {
    NavHostFragment.findNavController(this).apply {
        if (currentDestination?.label == this@safeNavigate::class.java.simpleName) {
            navigate(actionId, args, navOptions, navigatorExtras)
        }
    }
}
于 2020-04-01T01:33:13.657 回答
0

我在我的项目中遇到了同样的问题,首先我尝试去抖动触发导航操作的视图上的点击,但经过一些实验后,我发现在非常慢的设备上去抖动应该是一个非常高的值,导致应用程序让使用快速设备的用户感觉很慢。

所以我想出了以下对 NavController 的扩展,我认为它符合原始 API 并且易于使用:

fun NavController.safeNavigate(directions: NavDirections) {
    try {
        currentDestination?.getAction(directions.actionId) ?: return
        navigate(directions.actionId, directions.arguments, null)
    } catch (e : Exception) {
        logError("Navigation error", e)
    }
}

fun NavController.safeNavigate(directions: NavDirections, navOptions: NavOptions?) {
    try {
        currentDestination?.getAction(directions.actionId) ?: return
        navigate(directions.actionId, directions.arguments, navOptions)
    } catch (e : Exception) {
        logError("Navigation error", e)
    }
}

fun NavController.safeNavigate(directions: NavDirections, navigatorExtras: Navigator.Extras) {
    try {
        currentDestination?.getAction(directions.actionId) ?: return
        navigate(directions.actionId, directions.arguments, null, navigatorExtras)
    } catch (e : Exception) {
        logError("Navigation error", e)
    }
}

请注意,我使用的是 SafeArgs 和 NavDirections。这些函数检查当前目的地的操作是否有效,并且仅在操作不为空时导航。如果导航库每次都返回正确的操作,则不需要 try catch 部分,但我想消除所有可能的崩溃。

于 2021-11-30T10:58:32.230 回答
-1

发生此错误的原因可能是您可能将目标屏幕分配给了错误的图表

于 2020-06-02T06:14:47.670 回答