首页 > 解决方案 > NavHostFragment 在 onDestroyView 中崩溃

问题描述

我有一个Activity带有 aFragmentContainerView和一个BottomNavigationView设置的应用程序:

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <LinearLayout
        android:id="@+id/layout"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">

        <androidx.fragment.app.FragmentContainerView
            android:id="@+id/nav_host_fragment"
            android:name="androidx.navigation.fragment.NavHostFragment"
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:layout_weight="1"
            app:defaultNavHost="true"
            app:navGraph="@navigation/splash" />

        <com.google.android.material.bottomnavigation.BottomNavigationView
            android:id="@+id/bottom_navigation"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:background="@android:color/white"
            android:visibility="gone"
            app:elevation="12dp"
            app:labelVisibilityMode="unlabeled"
            app:menu="@menu/menu_bottom_nav_main" />
    </LinearLayout>
</layout>

当用户进入应用程序时,我需要显示一个启动页面(如果需要,还需要启动),然后将其导航到主页。所以我创建了 4 个导航图,1 个用于启动,3 个用于底部导航。我正在使用android 样本中的这种消光来设置底部导航和 3 个图表。这是我的MainActivity代码:

@AndroidEntryPoint
class MainActivity : BaseActivity<ActivityMainBinding, MainViewModel>(
    R.layout.activity_main,
    MainViewModel::class,
) {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        if (!vm.splashPassed) {
            val navHostFragment = supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment
            currentNavController = MutableLiveData(navHostFragment.navController)
        } else if (savedInstanceState == null && vm.splashPassed) {
            naviagateToHome()
        }
    }

    override fun onRestoreInstanceState(savedInstanceState: Bundle) {
        super.onRestoreInstanceState(savedInstanceState)
        if (vm.splashPassed) {
            naviagateToHome()
        }
    }

    fun naviagateToHome() {
        vm.splashPassed = true
        val fragment = supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment
        supportFragmentManager.beginTransaction()
            .remove(fragment)
            .commitNow()

        setupBottomNavigationBar()
    }

    private lateinit var currentNavController: LiveData<NavController>

    private fun setupBottomNavigationBar() {
        binding.bottomNavigation.isVisible = true
        val navGraphIds = arrayListOf(R.navigation.explore, R.navigation.plan, R.navigation.profile)

        // Setup the bottom navigation view with a list of navigation graphs
        currentNavController = binding.bottomNavigation.setupWithNavController(
            navGraphIds = navGraphIds,
            fragmentManager = supportFragmentManager,
            containerId = R.id.nav_host_fragment,
            intent = intent,
        )
    }

    override fun onBackPressed() {
        val navController = currentNavController.value ?: return super.onBackPressed()

        if (navController.graph.id == R.id.splash) return super.onBackPressed()

        val destination = navController.currentDestination?.id
        val rootDestinations = listOf(R.id.planFragment, R.id.profileFragment, R.id.exploreFragment, R.id.panelFragment)
        if (destination !in rootDestinations && navController.navigateUp()) return

        super.onBackPressed()
    }
}

这就是我打电话naviagateToHome的方式SplashFragment

vm.navigateHome.observe(viewLifecycleOwner) {
    (requireActivity() as? MainActivity)?.naviagateToHome()
}

一切正常,直到Activity执行onDestroy(当它被推到后台时)并且它崩溃并出现以下堆栈跟踪:

2020-09-13 13:17:38.064 28092-28092/com.abc.dev E/AndroidRuntime:致命异常:主进程:com.abc.dev,PID:28092 java.lang.RuntimeException:无法销毁活动 {com .abc.dev/com.abc.ui.activity.MainActivity}:java.lang.IllegalStateException:查看 androidx.fragment.app.FragmentContainerView{e7a7304 VE..... ......ID 0,0-1080 ,1868 #7f0a026a app:id/nav_host_fragment} 没有在 android.app.ActivityThread.handleDestroyActivity(ActivityThread.java:4970) 在 android.app.ActivityThread.performDestroyActivity(ActivityThread.java:4941) 处设置 NavController .servertransaction.DestroyActivityItem.execute(DestroyActivityItem.java:44) 在 android.app.servertransaction.TransactionExecutor.executeLifecycleState(TransactionExecutor.java:176) 在 android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:97) 在 android.app.ActivityThread$H.handleMessage(ActivityThread.java:2016) 在 android.os.Handler.dispatchMessage(Handler.java:107) 在 android.os.Looper.loop(Looper.java:214) 在android.app.ActivityThread.main(ActivityThread.java:7356) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:492) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:930) 引起:java.lang.IllegalStateException:查看androidx.fragment.app.FragmentContainerView{e7a7304 VE...........ID 0,0-1080,1868 #7f0a026a app:id/nav_host_fragment} 没有在 androidx.navigation.Navigation.findNavController(Navigation.java:84) 在 androidx.navigation.fragment.NavHostFragment.onDestroyView(NavHostFragment.java) 设置 NavController :388) 在 androidx.fragment.app.Fragment。performDestroyView(Fragment.java:3171) 在 androidx.fragment.app.FragmentStateManager.destroyFragmentView(FragmentStateManager.java:726) 在 androidx.fragment.app.FragmentStateManager.moveToExpectedState(FragmentStateManager.java:360) 在 androidx.fragment.app.FragmentStore .moveToExpectedState(FragmentStore.java:112) 在 androidx.fragment.app.FragmentManager.moveToState(FragmentManager.java:1632) 在 androidx.fragment.app.FragmentManager.dispatchStateChange(FragmentManager.java:3109) 在 androidx.fragment.app。 FragmentManager.dispatchDestroy(FragmentManager.java:3088) 在 androidx.fragment.app.FragmentController.dispatchDestroy(FragmentController.java:334) 在 androidx.fragment.app.FragmentActivity.onDestroy(FragmentActivity.java:322) 在 androidx.appcompat.app .AppCompatActivity.onDestroy(AppCompatActivity.java:278) 在 android.app.Activity。performDestroy(Activity.java:8048) at android.app.Instrumentation.callActivityOnDestroy(Instrumentation.java:1334) at android.app.ActivityThread.performDestroyActivity(ActivityThread.java:4926) at android.app.ActivityThread.handleDestroyActivity(ActivityThread.java :4970) 在 android.app.servertransaction.DestroyActivityItem.execute(DestroyActivityItem.java:44) 在 android.app.servertransaction.TransactionExecutor.executeLifecycleState(TransactionExecutor.java:176) 在 android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor. java:97) 在 android.os.Handler.dispatchMessage(Handler.java:107) 在 android.os.Looper.loop(Looper.java:214) 的 android.app.ActivityThread$H.handleMessage(ActivityThread.java:2016) ) 在 android.app.ActivityThread.main(ActivityThread.java:7356) 在 java.lang.reflect.Method.invoke(Native Method) 在 com。android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:492) 在 com.android.internal.os.ZygoteInit.main(ZygoteInit.java:930)

在花了一些时间调试后,我意识到androidx.navigation.fragment.NavHostFragment.onDestroyView尝试使用的Navigation.findNavController方法有一些问题。我还在 android/architecture-components-samples GitHub 存储库上找到了一个拉取请求,它没有提供任何明确的解决方案。

注意:onCreate如果我通过调用 设置底部导航,setupBottomNavigationBar我不会收到错误消息,并且一切顺利。

我已经坚持了一个多星期,我不知道如何解决它。

tnx 阅读我的长问题。

更新 1: 我注意到如果我至少更改一次选定的底部导航项,则不会发生崩溃。

此外,如果我在想要分离前NavHostFragment一个(即启动画面)时添加自定义动画,如下所示,同样的错误会一直发生:

fun naviagateToHome() {
    vm.splashPassed = true
    val fragment = supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment
    supportFragmentManager.beginTransaction()
        .setCustomAnimations(R.anim.nav_default_enter_anim, R.anim.nav_default_exit_anim)
        .remove(fragment)
        .commitNow()

    setupBottomNavigationBar()
}

2020-09-14 00:18:37.705 3434-3434/com.abc.dev E/AndroidRuntime:致命异常:主进程:com.abc.dev,PID:3434 java.lang.IllegalStateException:查看 androidx.fragment.app .FragmentContainerView{1414b18 VE..... ......ID 0,0-1080,1868 #7f0a026a app:id/nav_host_fragment} 没有在 androidx.navigation.Navigation.findNavController(Navigation.java :84) 在 androidx.navigation.fragment.NavHostFragment.onDestroyView(NavHostFragment.java:388) 在 androidx.fragment.app.Fragment.performDestroyView(Fragment.java:3171) 在 androidx.fragment.app.FragmentStateManager.destroyFragmentView(FragmentStateManager. java:726) 在 androidx.fragment.app.FragmentStateManager.moveToExpectedState(FragmentStateManager.java:360) 在 androidx.fragment.app.SpecialEffectsController$FragmentStateManagerOperation.complete(SpecialEffectsController.java:512) 在 androidx.fragment.app.DefaultSpecialEffectsController$4$1.run(DefaultSpecialEffectsController.java:255) 在 android.os.Handler.handleCallback(Handler.java) 的 androidx.fragment.app.DefaultSpecialEffectsController.removeCancellationSignal(DefaultSpecialEffectsController.java:81) :883) 在 android.os.Handler.dispatchMessage(Handler.java:100) 在 android.os.Looper.loop(Looper.java:214) 在 android.app.ActivityThread.main(ActivityThread.java:7356) 在 java .lang.reflect.Method.invoke(Native Method) 在 com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:492) 在 com.android.internal.os.ZygoteInit.main(ZygoteInit.java: 930)在 android.os.Handler.dispatchMessage(Handler.java:100) 在 android.os.Looper.loop(Looper.java) 的 android.os.Handler.handleCallback(Handler.java:883) 运行(DefaultSpecialEffectsController.java:255) :214) 在 android.app.ActivityThread.main(ActivityThread.java:7356) 在 java.lang.reflect.Method.invoke(Native Method) 在 com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java :492) 在 com.android.internal.os.ZygoteInit.main(ZygoteInit.java:930)在 android.os.Handler.dispatchMessage(Handler.java:100) 在 android.os.Looper.loop(Looper.java) 的 android.os.Handler.handleCallback(Handler.java:883) 运行(DefaultSpecialEffectsController.java:255) :214) 在 android.app.ActivityThread.main(ActivityThread.java:7356) 在 java.lang.reflect.Method.invoke(Native Method) 在 com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java :492) 在 com.android.internal.os.ZygoteInit.main(ZygoteInit.java:930)ZygoteInit.main(ZygoteInit.java:930)ZygoteInit.main(ZygoteInit.java:930)

标签: androidkotlinandroid-fragmentsandroid-navigationview

解决方案


这是导航示例中的代码部分,用于决定附加和分离其他片段的片段。

    // Attach or detach nav host fragment depending on whether it's the selected item.
    if (this.selectedItemId == graphId) {
        selectedNavController.value = navHostFragment.navController
            attachNavHostFragment(fragmentManager, navHostFragment, index == 0)
    } else {
        detachNavHostFragment(fragmentManager, navHostFragment)
    }

这里发生了什么?
attach:当 NavHostFragment 与容器连接时,它 onViewCreated被调用,这也为父视图设置了 NavController,即在这种情况下是 FragmentContainerView。

detach : 当 NavHostFragment 从容器中分离时onDestroyView被调用。onDestroyView将父视图的 NavController 设置为 null。

attach仅调用selectedFragmentId,并且所有其他片段都被分离。

以下是您的日志,将第一个片段视为选定片段:

2020-09-14 09:34:58.602 12023-12023/com.beetlestance.testingnavigation D/navHostLifecycle: onDestroyView
2020-09-14 09:34:58.602 12023-12023/com.beetlestance.testingnavigation D/navController: SplashFragment removing navController from parent
2020-09-14 09:34:58.611 12023-12023/com.beetlestance.testingnavigation D/bottomSheet: obtain navHostFragment 1
2020-09-14 09:34:58.616 12023-12023/com.beetlestance.testingnavigation D/navHostLifecycle: onCreate
2020-09-14 09:34:58.620 12023-12023/com.beetlestance.testingnavigation D/navHostLifecycle: onViewCreated
2020-09-14 09:34:58.620 12023-12023/com.beetlestance.testingnavigation D/navController: FirstBottomFragment setting navController to parent
2020-09-14 09:34:58.633 12023-12023/com.beetlestance.testingnavigation D/navHostLifecycle: onResume
2020-09-14 09:34:58.634 12023-12023/com.beetlestance.testingnavigation D/bottomsheet: attach 1
2020-09-14 09:34:58.635 12023-12023/com.beetlestance.testingnavigation D/bottomSheet: obtain navHostFragment 2
2020-09-14 09:34:58.640 12023-12023/com.beetlestance.testingnavigation D/navHostLifecycle: onCreate
2020-09-14 09:34:58.643 12023-12023/com.beetlestance.testingnavigation D/navHostLifecycle: onViewCreated
2020-09-14 09:34:58.643 12023-12023/com.beetlestance.testingnavigation D/navController: SecondBottomFragment setting navController to parent
2020-09-14 09:34:58.654 12023-12023/com.beetlestance.testingnavigation D/navHostLifecycle: onResume
2020-09-14 09:34:58.656 12023-12023/com.beetlestance.testingnavigation D/bottomsheet: detach 2
2020-09-14 09:34:58.660 12023-12023/com.beetlestance.testingnavigation D/navHostLifecycle: onDestroyView
2020-09-14 09:34:58.660 12023-12023/com.beetlestance.testingnavigation D/navController: SecondBottomFragment removing navController from parent
2020-09-14 09:34:58.661 12023-12023/com.beetlestance.testingnavigation D/bottomSheet: obtain navHostFragment 3
2020-09-14 09:34:58.666 12023-12023/com.beetlestance.testingnavigation D/navHostLifecycle: onCreate
2020-09-14 09:34:58.668 12023-12023/com.beetlestance.testingnavigation D/navHostLifecycle: onViewCreated
2020-09-14 09:34:58.668 12023-12023/com.beetlestance.testingnavigation D/navController: ThirdBottomFragment setting navController to parent
2020-09-14 09:34:58.678 12023-12023/com.beetlestance.testingnavigation D/navHostLifecycle: onResume
2020-09-14 09:34:58.680 12023-12023/com.beetlestance.testingnavigation D/bottomsheet: detach 3
2020-09-14 09:34:58.684 12023-12023/com.beetlestance.testingnavigation D/navHostLifecycle: onDestroyView
2020-09-14 09:34:58.684 12023-12023/com.beetlestance.testingnavigation D/navController: ThirdBottomFragment removing navController from parent

从日志中可以清楚地看出,分离是最后一次调用而不是附加,这导致父 NavController 为空。

解决方案: 最后附上选中的片段,这样就解决了这里的问题。如果您在默认情况下为底部导航选择了不同的 id,您可以将片段附加到 forEach 循环之外,而不是反转列表。

另一种方法是将底部工作表附加到片段内,这将简化大多数情况。飞溅片段将始终是导航到您的底部导航容器片段的起始目的地,该片段将在其中设置底部工作表onViewCreated。检查https://github.com/beetlestance/android-extensions

为什么设置时它会起作用onCreate

仅当至少启动活动时才附加或分离片段。因此,当您在 onCreate 中设置底部导航时,NavHostFragment 的生命周期是不同的。

底部导航设置的日志onCreate

2020-09-14 10:07:17.659 15260-15260/com.beetlestance.testingnavigation D/bottomSheet: obtain navHostFragment 1
2020-09-14 10:07:18.011 15260-15260/com.beetlestance.testingnavigation D/navHostLifecycle: onCreate
2020-09-14 10:07:18.017 15260-15260/com.beetlestance.testingnavigation D/bottomsheet: attach 1
2020-09-14 10:07:18.041 15260-15260/com.beetlestance.testingnavigation D/bottomSheet: obtain navHostFragment 2
2020-09-14 10:07:18.069 15260-15260/com.beetlestance.testingnavigation D/navHostLifecycle: onCreate
2020-09-14 10:07:18.073 15260-15260/com.beetlestance.testingnavigation D/bottomsheet: detach 2
2020-09-14 10:07:18.077 15260-15260/com.beetlestance.testingnavigation D/bottomSheet: obtain navHostFragment 3
2020-09-14 10:07:18.098 15260-15260/com.beetlestance.testingnavigation D/navHostLifecycle: onCreate
2020-09-14 10:07:18.108 15260-15260/com.beetlestance.testingnavigation D/bottomsheet: detach 3
2020-09-14 10:07:18.173 15260-15260/com.beetlestance.testingnavigation D/mainActivityLifecycle: onStart
2020-09-14 10:07:18.208 15260-15260/com.beetlestance.testingnavigation D/navHostLifecycle: onViewCreated
2020-09-14 10:07:18.208 15260-15260/com.beetlestance.testingnavigation D/navController: FirstBottomFragment setting navController to parent
2020-09-14 10:07:18.292 15260-15260/com.beetlestance.testingnavigation D/mainActivityLifecycle: onResume
2020-09-14 10:07:18.304 15260-15260/com.beetlestance.testingnavigation D/navHostLifecycle: onResume

您可以检查onDestroyView是否未调用 NavHostFragment。所以 NavController 设置正确。

希望这会有所帮助。


推荐阅读