Android Navcontroller 硬件后退按钮崩溃

Android Navcontroller hardware back button crash

我正在使用新的 android 故事板来创建应用程序。流程需要如下所示:

SplashFragment -> Fragment1 -> Fragment2

故事板如下(navigation_main.xml):

<?xml version="1.0" encoding="utf-8"?>
<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/launch_navigation_graph"
            app:startDestination="@id/splashFragment">

    <fragment android:id="@+id/splashFragment" android:name="com.myapp.android.SplashFragment"
              android:label="fragment_splash" tools:layout="@layout/fragment_splash">
        <action android:id="@+id/action_splashFragment_to_fragment1"
                app:destination="@id/fragment1"/>
    </fragment>
    <fragment android:id="@+id/fragment1"
              android:name="com.myapp.android.Fragment1"
              android:label="fragment1" tools:layout="@layout/fragment_register_msisdn">
        <action android:id="@+id/action_fragment1_to_fragment2"
                app:destination="@id/fragment2" app:popUpTo="@+id/fragment1"
                app:enterAnim="@anim/nav_default_pop_enter_anim" app:exitAnim="@anim/nav_default_pop_exit_anim"/>
    </fragment>
    <fragment android:id="@+id/fragment2"
              android:name="com.myapp.android.Fragment2"
              android:label="fragment_fragment2" tools:layout="@layout/fragment_fragment2"/>
</navigation>

activity_main.xml:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context="MainActivity">

    <fragment
            android:id="@+id/mainNavigationHostFragment"
            android:name="androidx.navigation.fragment.NavHostFragment"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            app:defaultNavHost="true"
            app:navGraph="@navigation/navigation_main" />

</androidx.constraintlayout.widget.ConstraintLayout>

应用主题没有操作栏,因为我不想显示操作栏:

<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">

基本上我需要一个 Fragment1Fragment2 的导航,然后在硬件上按下后退按钮,返回到 Fragment1。要从 Fragment1 导航到 Fragment2,我在 Fragment1 中有以下代码:

findNavController().navigate(R.id.action_fragment1_to_fragment2)

SplashFragment 不应保留在堆栈中,因为在启动时首次显示后不需要它。这就是为什么我只在 Fragment1Fragment2 操作中有 popTo。但是在 运行 之后,从 Fragment2 返回,第一次什么都不做(不弹出),第二次,它崩溃并出现以下异常:

2019-04-25 16:52:43.841 28598-28598/com.selfcare.safaricom E/InputEventSender: Exception dispatching finished signal.
2019-04-25 16:52:43.842 28598-28598/com.selfcare.safaricom E/MessageQueue-JNI: Exception in MessageQueue callback: handleReceiveCallback
2019-04-25 16:52:43.846 28598-28598/com.selfcare.safaricom E/MessageQueue-JNI: java.lang.IllegalArgumentException: navigation destination com.selfcare.safaricom:id/action_splashFragment_to_registerMSISDNFragment is unknown to this NavController
        at androidx.navigation.NavController.navigate(NavController.java:803)
        at androidx.navigation.NavController.navigate(NavController.java:744)
        at androidx.navigation.NavController.navigate(NavController.java:730)
        at androidx.navigation.NavController.navigate(NavController.java:718)
        at com.myapp.android.SplashFragment.handleLaunchStatus(SplashFragment.kt:51)
        at com.myapp.android.SplashFragment.access$handleLaunchStatus(SplashFragment.kt:16)
        at com.myapp.android.SplashFragment$attachLaunchObserver.onChanged(SplashFragment.kt:44)
        at com.myapp.android.SplashFragment$attachLaunchObserver.onChanged(SplashFragment.kt:16)
        at androidx.lifecycle.LiveData.considerNotify(LiveData.java:113)
        at androidx.lifecycle.LiveData.dispatchingValue(LiveData.java:126)
        at androidx.lifecycle.LiveData$ObserverWrapper.activeStateChanged(LiveData.java:424)
        at androidx.lifecycle.LiveData$LifecycleBoundObserver.onStateChanged(LiveData.java:376)
        at androidx.lifecycle.LifecycleRegistry$ObserverWithState.dispatchEvent(LifecycleRegistry.java:361)
        at androidx.lifecycle.LifecycleRegistry.addObserver(LifecycleRegistry.java:188)
        at androidx.lifecycle.LiveData.observe(LiveData.java:185)
        at com.myapp.android.SplashFragment.attachLaunchObserver(SplashFragment.kt:43)
        at com.myapp.android.SplashFragment.onViewCreated(SplashFragment.kt:35)
        at androidx.fragment.app.FragmentManagerImpl.moveToState(FragmentManagerImpl.java:895)
        at androidx.fragment.app.FragmentManagerImpl.addAddedFragments(FragmentManagerImpl.java:2092)
        at androidx.fragment.app.FragmentManagerImpl.executeOpsTogether(FragmentManagerImpl.java:1866)
        at androidx.fragment.app.FragmentManagerImpl.removeRedundantOperationsAndExecute(FragmentManagerImpl.java:1822)
        at androidx.fragment.app.FragmentManagerImpl.popBackStackImmediate(FragmentManagerImpl.java:298)
        at androidx.fragment.app.FragmentManagerImpl.popBackStackImmediate(FragmentManagerImpl.java:241)
        at androidx.fragment.app.FragmentManagerImpl.popBackStackImmediate(FragmentManagerImpl.java:288)
        at androidx.fragment.app.FragmentManagerImpl.popBackStackImmediate(FragmentManagerImpl.java:241)
        at androidx.fragment.app.FragmentActivity.handleOnBackPressed(FragmentActivity.java:144)
        at androidx.activity.OnBackPressedDispatcher.onBackPressed(OnBackPressedDispatcher.java:136)
        at androidx.activity.ComponentActivity.onBackPressed(ComponentActivity.java:283)
        at android.app.Activity.onKeyUp(Activity.java:3083)
        at android.view.KeyEvent.dispatch(KeyEvent.java:2716)
        at android.app.Activity.dispatchKeyEvent(Activity.java:3366)
        at androidx.core.app.ComponentActivity.superDispatchKeyEvent(ComponentActivity.java:80)
        at androidx.core.view.KeyEventDispatcher.dispatchKeyEvent(KeyEventDispatcher.java:84)
        at androidx.core.app.ComponentActivity.dispatchKeyEvent(ComponentActivity.java:98)
        at androidx.appcompat.app.AppCompatActivity.dispatchKeyEvent(AppCompatActivity.java:558)
        at androidx.appcompat.view.WindowCallbackWrapper.dispatchKeyEvent(WindowCallbackWrapper.java:59)
        at androidx.appcompat.app.AppCompatDelegateImpl$AppCompatWindowCallback.dispatchKeyEvent(AppCompatDelegateImpl.java:2736)
        at com.android.internal.policy.DecorView.dispatchKeyEvent(DecorView.java:342)
        at android.view.ViewRootImpl$ViewPostImeInputStage.processKeyEvent(ViewRootImpl.java:5037)
        at android.view.ViewRootImpl$ViewPostImeInputStage.onProcess(ViewRootImpl.java:4905)
        at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:4426)
        at android.view.ViewRootImpl$InputStage.onDeliverToNext(ViewRootImpl.java:4479)
        at android.view.ViewRootImpl$InputStage.forward(ViewRootImpl.java:4445)
        at android.view.ViewRootImpl$AsyncInputStage.forward(ViewRootImpl.java:4585)
        at android.view.ViewRootImpl$InputStage.apply(Vie
2019-04-25 16:52:43.849 28598-28598/com.selfcare.safaricom E/AndroidRuntime: FATAL EXCEPTION: main
    Process: com.selfcare.safaricom, PID: 28598
    java.lang.IllegalArgumentException: navigation destination com.selfcare.safaricom:id/action_splashFragment_to_registerMSISDNFragment is unknown to this NavController
        at androidx.navigation.NavController.navigate(NavController.java:803)
        at androidx.navigation.NavController.navigate(NavController.java:744)
        at androidx.navigation.NavController.navigate(NavController.java:730)
        at androidx.navigation.NavController.navigate(NavController.java:718)
        at com.myapp.android.SplashFragment.handleLaunchStatus(SplashFragment.kt:51)
        at com.myapp.android.SplashFragment.access$handleLaunchStatus(SplashFragment.kt:16)
        at com.myapp.android.SplashFragment$attachLaunchObserver.onChanged(SplashFragment.kt:44)
        at com.myapp.android.SplashFragment$attachLaunchObserver.onChanged(SplashFragment.kt:16)
        at androidx.lifecycle.LiveData.considerNotify(LiveData.java:113)
        at androidx.lifecycle.LiveData.dispatchingValue(LiveData.java:126)
        at androidx.lifecycle.LiveData$ObserverWrapper.activeStateChanged(LiveData.java:424)
        at androidx.lifecycle.LiveData$LifecycleBoundObserver.onStateChanged(LiveData.java:376)
        at androidx.lifecycle.LifecycleRegistry$ObserverWithState.dispatchEvent(LifecycleRegistry.java:361)
        at androidx.lifecycle.LifecycleRegistry.addObserver(LifecycleRegistry.java:188)
        at androidx.lifecycle.LiveData.observe(LiveData.java:185)
        at com.myapp.android.SplashFragment.attachLaunchObserver(SplashFragment.kt:43)
        at com.myapp.android.SplashFragment.onViewCreated(SplashFragment.kt:35)
        at androidx.fragment.app.FragmentManagerImpl.moveToState(FragmentManagerImpl.java:895)
        at androidx.fragment.app.FragmentManagerImpl.addAddedFragments(FragmentManagerImpl.java:2092)
        at androidx.fragment.app.FragmentManagerImpl.executeOpsTogether(FragmentManagerImpl.java:1866)
        at androidx.fragment.app.FragmentManagerImpl.removeRedundantOperationsAndExecute(FragmentManagerImpl.java:1822)
        at androidx.fragment.app.FragmentManagerImpl.popBackStackImmediate(FragmentManagerImpl.java:298)
        at androidx.fragment.app.FragmentManagerImpl.popBackStackImmediate(FragmentManagerImpl.java:241)
        at androidx.fragment.app.FragmentManagerImpl.popBackStackImmediate(FragmentManagerImpl.java:288)
        at androidx.fragment.app.FragmentManagerImpl.popBackStackImmediate(FragmentManagerImpl.java:241)
        at androidx.fragment.app.FragmentActivity.handleOnBackPressed(FragmentActivity.java:144)
        at androidx.activity.OnBackPressedDispatcher.onBackPressed(OnBackPressedDispatcher.java:136)
        at androidx.activity.ComponentActivity.onBackPressed(ComponentActivity.java:283)
        at android.app.Activity.onKeyUp(Activity.java:3083)
        at android.view.KeyEvent.dispatch(KeyEvent.java:2716)
        at android.app.Activity.dispatchKeyEvent(Activity.java:3366)
        at androidx.core.app.ComponentActivity.superDispatchKeyEvent(ComponentActivity.java:80)
        at androidx.core.view.KeyEventDispatcher.dispatchKeyEvent(KeyEventDispatcher.java:84)
        at androidx.core.app.ComponentActivity.dispatchKeyEvent(ComponentActivity.java:98)
        at androidx.appcompat.app.AppCompatActivity.dispatchKeyEvent(AppCompatActivity.java:558)
        at androidx.appcompat.view.WindowCallbackWrapper.dispatchKeyEvent(WindowCallbackWrapper.java:59)
        at androidx.appcompat.app.AppCompatDelegateImpl$AppCompatWindowCallback.dispatchKeyEvent(AppCompatDelegateImpl.java:2736)
        at com.android.internal.policy.DecorView.dispatchKeyEvent(DecorView.java:342)
        at android.view.ViewRootImpl$ViewPostImeInputStage.processKeyEvent(ViewRootImpl.java:5037)
        at android.view.ViewRootImpl$ViewPostImeInputStage.onProcess(ViewRootImpl.java:4905)
        at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:4426)
        at android.view.ViewRootImpl$InputStage.onDeliverToNext(ViewRootImpl.java:4479)
        at android.view.ViewRootImpl$InputStage.forward(ViewRootImpl.java:4445)
2019-04-25 16:52:43.851 28598-28598/com.selfcare.safaricom E/AndroidRuntime:     at android.view.ViewRootImpl$AsyncInputStage.forward(ViewRootImpl.java:4585)
        at android.view.ViewRootImpl$InputStage.apply(ViewRootImpl.java:4453)
        at android.view.ViewRootImpl$AsyncInputStage.apply(ViewRootImpl.java:4642)
        at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:4426)
        at android.view.ViewRootImpl$InputStage.onDeliverToNext(ViewRootImpl.java:4479)
        at android.view.ViewRootImpl$InputStage.forward(ViewRootImpl.java:4445)
        at android.view.ViewRootImpl$InputStage.apply(ViewRootImpl.java:4453)
        at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:4426)
        at android.view.ViewRootImpl$InputStage.onDeliverToNext(ViewRootImpl.java:4479)
        at android.view.ViewRootImpl$InputStage.forward(ViewRootImpl.java:4445)
        at android.view.ViewRootImpl$AsyncInputStage.forward(ViewRootImpl.java:4618)
        at android.view.ViewRootImpl$ImeInputStage.onFinishedInputEvent(ViewRootImpl.java:4779)
        at android.view.inputmethod.InputMethodManager$PendingEvent.run(InputMethodManager.java:2571)
        at android.view.inputmethod.InputMethodManager.invokeFinishedInputEventCallback(InputMethodManager.java:2081)
        at android.view.inputmethod.InputMethodManager.finishedInputEvent(InputMethodManager.java:2072)
        at android.view.inputmethod.InputMethodManager$ImeInputEventSender.onInputEventFinished(InputMethodManager.java:2548)
        at android.view.InputEventSender.dispatchInputEventFinished(InputEventSender.java:141)
        at android.os.MessageQueue.nativePollOnce(Native Method)
        at android.os.MessageQueue.next(MessageQueue.java:326)
        at android.os.Looper.loop(Looper.java:160)
        at android.app.ActivityThread.main(ActivityThread.java:6718)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858)

SplashFragment 第 51 行是:

findNavController().navigate(R.id.action_splashFragment_to_fragment1)

如果我从 Fragment1Fragment2 操作中删除 popTo,这个异常就消失了,但是后退按钮也不起作用。我在这里做错了什么?

编辑 1:

根据 Stavro Xhardha 的评论,我对导航做了一些修改 XML:

<fragment android:id="@+id/splashFragment" android:name="com.myapp.android.SplashFragment"
          android:label="fragment_splash" tools:layout="@layout/fragment_splash">
    <action android:id="@+id/action_splashFragment_to_fragment1"
            app:destination="@id/fragment1"
    app:popUpToInclusive="true" app:popUpTo="@+id/splashFragment"/> <!--Added this line -->
</fragment>
<fragment android:id="@+id/fragment1"
          android:name="com.myapp.android.Fragment1"
          android:label="fragment1" tools:layout="@layout/fragment_register_msisdn">
    <action android:id="@+id/action_fragment1_to_fragment2"
            app:destination="@id/fragment2" app:popUpTo="@+id/fragment1"/>
</fragment>
<fragment android:id="@+id/fragment2"
          android:name="com.myapp.android.Fragment2"
          android:label="fragment_fragment2" tools:layout="@layout/fragment_fragment2"/>

并在 MainActivity 中重写 onBackPressed 如下:

override fun onBackPressed() {
    super.onBackPressed()
    if (!findNavController(R.id.launchNavigationHostFragment).navigateUp()) {
        finish()
    }
}

现在 Fragment2 弹出到 Fragment1,但随后在 Fragment1 上的后退键继续使 Fragment1 进入循环。我无法退出应用程序。

您的错误日志显示您正在使用操作 ID action_splashFragment_to_registerMSISDNFragment 的 navcontroller 调用操作。尝试找到该操作 ID 并检查它是否有效。在 action_splashFragment_to_fragment1 中也使用 app:popUpTo="@id/splashFragment" app:popUpToInclusive="true" 而不是 action_fragment1_to_fragment2。这将从后台堆栈中删除启动片段。这是代码片段:

<?xml version="1.0" encoding="utf-8"?>
    <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/launch_navigation_graph"
                app:startDestination="@id/splashFragment">

        <fragment android:id="@+id/splashFragment" android:name="com.myapp.android.SplashFragment"
                  android:label="fragment_splash" tools:layout="@layout/fragment_splash">
            <action android:id="@+id/action_splashFragment_to_fragment1"
                    app:destination="@id/fragment1"
                app:popUpTo="@id/splashFragment"
                app:popUpToInclusive="true" />
        </fragment>
        <fragment android:id="@+id/fragment1"
                  android:name="com.myapp.android.Fragment1"
                  android:label="fragment1" tools:layout="@layout/fragment_register_msisdn">
            <action android:id="@+id/action_fragment1_to_fragment2"
                    app:destination="@id/fragment2"
                    app:enterAnim="@anim/nav_default_pop_enter_anim" app:exitAnim="@anim/nav_default_pop_exit_anim"/>
        </fragment>
        <fragment android:id="@+id/fragment2"
                  android:name="com.myapp.android.Fragment2"
                  android:label="fragment_fragment2" tools:layout="@layout/fragment_fragment2"/>
    </navigation>

我终于找到问题并解决了。

问题是,我正在观察来自 ViewModelMutableLiveData,并根据其值进行导航。但是我不知道片段的生命周期所有者倾向于销毁观察者并根据视图生命周期重新实例化它以避免泄漏。因此,一旦导航发生,观察者就不再存在,导航代码就在观察者中。返回时需要相同的代码,因此在尝试访问它时,代码崩溃。

我通过使用接口在需要完成导航时向片段提供回调来解决问题。

导航到新 fragment.Like 后我刚​​刚删除了观察者:

myLiveData.removeObservers(this)