NavHostFragment 在 onDestroyView 中崩溃
NavHostFragment crashes in onDestroyView
我有一个 Activity
应用程序,具有 FragmentContainerView
和 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 样本中的 this 消光来设置带有 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()
}
}
这是我从 SplashFragment
调用 naviagateToHome
的方式:
vm.navigateHome.observe(viewLifecycleOwner) {
(requireActivity() as? MainActivity)?.naviagateToHome()
}
一切正常,直到 Activity
执行 onDestroy
(当它被推送到后台时)并且崩溃并显示以下堆栈跟踪:
2020-09-13 13:17:38.064 28092-28092/com.abc.dev E/AndroidRuntime:
FATAL EXCEPTION: main
Process: com.abc.dev, PID: 28092
java.lang.RuntimeException: Unable to destroy activity {com.abc.dev/com.abc.ui.activity.MainActivity}:
java.lang.IllegalStateException: View
androidx.fragment.app.FragmentContainerView{e7a7304 V.E...... ......ID
0,0-1080,1868 #7f0a026a app:id/nav_host_fragment} does not have a
NavController set
at android.app.ActivityThread.performDestroyActivity(ActivityThread.java:4941)
at android.app.ActivityThread.handleDestroyActivity(ActivityThread.java:4970)
at android.app.servertransaction.DestroyActivityItem.execute(DestroyActivityItem.java:44)
at android.app.servertransaction.TransactionExecutor.executeLifecycleState(TransactionExecutor.java:176)
at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:97)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2016)
at android.os.Handler.dispatchMessage(Handler.java:107)
at android.os.Looper.loop(Looper.java:214)
at 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)
Caused by: java.lang.IllegalStateException: View androidx.fragment.app.FragmentContainerView{e7a7304 V.E...... ......ID
0,0-1080,1868 #7f0a026a app:id/nav_host_fragment} does not have a
NavController set
at androidx.navigation.Navigation.findNavController(Navigation.java:84)
at androidx.navigation.fragment.NavHostFragment.onDestroyView(NavHostFragment.java:388)
at androidx.fragment.app.Fragment.performDestroyView(Fragment.java:3171)
at androidx.fragment.app.FragmentStateManager.destroyFragmentView(FragmentStateManager.java:726)
at androidx.fragment.app.FragmentStateManager.moveToExpectedState(FragmentStateManager.java:360)
at androidx.fragment.app.FragmentStore.moveToExpectedState(FragmentStore.java:112)
at androidx.fragment.app.FragmentManager.moveToState(FragmentManager.java:1632)
at androidx.fragment.app.FragmentManager.dispatchStateChange(FragmentManager.java:3109)
at androidx.fragment.app.FragmentManager.dispatchDestroy(FragmentManager.java:3088)
at androidx.fragment.app.FragmentController.dispatchDestroy(FragmentController.java:334)
at androidx.fragment.app.FragmentActivity.onDestroy(FragmentActivity.java:322)
at androidx.appcompat.app.AppCompatActivity.onDestroy(AppCompatActivity.java:278)
at 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)
at android.app.servertransaction.DestroyActivityItem.execute(DestroyActivityItem.java:44)
at android.app.servertransaction.TransactionExecutor.executeLifecycleState(TransactionExecutor.java:176)
at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:97)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2016)
at android.os.Handler.dispatchMessage(Handler.java:107)
at android.os.Looper.loop(Looper.java:214)
at 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)
在花了一些时间调试后,我意识到 androidx.navigation.fragment.NavHostFragment.onDestroyView
试图使用 Navigation.findNavController
方法,该方法在 android/architecture-components-samples GitHub 上描述了 here. I also find a pull request 的一些问题不提供任何明确解决方案的存储库。
注:
如果我通过调用 setupBottomNavigationBar
在 onCreate
中设置底部导航,我不会收到错误并且一切顺利。
我已经卡了一个多星期了,不知道怎么解决。
感谢阅读我的长问题。
更新 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: FATAL
EXCEPTION: main
Process: com.abc.dev, PID: 3434
java.lang.IllegalStateException: View androidx.fragment.app.FragmentContainerView{1414b18 V.E...... ......ID
0,0-1080,1868 #7f0a026a app:id/nav_host_fragment} does not have a
NavController set
at androidx.navigation.Navigation.findNavController(Navigation.java:84)
at androidx.navigation.fragment.NavHostFragment.onDestroyView(NavHostFragment.java:388)
at androidx.fragment.app.Fragment.performDestroyView(Fragment.java:3171)
at androidx.fragment.app.FragmentStateManager.destroyFragmentView(FragmentStateManager.java:726)
at androidx.fragment.app.FragmentStateManager.moveToExpectedState(FragmentStateManager.java:360)
at androidx.fragment.app.SpecialEffectsController$FragmentStateManagerOperation.complete(SpecialEffectsController.java:512)
at androidx.fragment.app.DefaultSpecialEffectsController.removeCancellationSignal(DefaultSpecialEffectsController.java:81)
at androidx.fragment.app.DefaultSpecialEffectsController.run(DefaultSpecialEffectsController.java:255)
at android.os.Handler.handleCallback(Handler.java:883)
at android.os.Handler.dispatchMessage(Handler.java:100)
at android.os.Looper.loop(Looper.java:214)
at 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)
这个问题是在最后调用 detach 而不是 attach 时引起的,这会强制 navController 在父 NavHostFragment 上为 null。
这是导航示例中的代码部分,它决定附加和分离其他片段的片段。
// 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 循环之外,而不是反转列表。
另一种方法是将底部 sheet 附加到片段内,这将简化大多数情况。 Splash 片段将始终是导航到底部导航容器片段的起始目的地,它将在 onViewCreated
中设置底部 sheet。检查 https://github.com/beetlestance/android-extensions
为什么在 onCreate
中设置时有效?
片段仅在 activity 至少启动时才附加或分离。因此,当您在 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 已正确设置。
希望这会有所帮助。
我有一个 Activity
应用程序,具有 FragmentContainerView
和 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 样本中的 this 消光来设置带有 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()
}
}
这是我从 SplashFragment
调用 naviagateToHome
的方式:
vm.navigateHome.observe(viewLifecycleOwner) {
(requireActivity() as? MainActivity)?.naviagateToHome()
}
一切正常,直到 Activity
执行 onDestroy
(当它被推送到后台时)并且崩溃并显示以下堆栈跟踪:
2020-09-13 13:17:38.064 28092-28092/com.abc.dev E/AndroidRuntime: FATAL EXCEPTION: main Process: com.abc.dev, PID: 28092 java.lang.RuntimeException: Unable to destroy activity {com.abc.dev/com.abc.ui.activity.MainActivity}: java.lang.IllegalStateException: View androidx.fragment.app.FragmentContainerView{e7a7304 V.E...... ......ID 0,0-1080,1868 #7f0a026a app:id/nav_host_fragment} does not have a NavController set at android.app.ActivityThread.performDestroyActivity(ActivityThread.java:4941) at android.app.ActivityThread.handleDestroyActivity(ActivityThread.java:4970) at android.app.servertransaction.DestroyActivityItem.execute(DestroyActivityItem.java:44) at android.app.servertransaction.TransactionExecutor.executeLifecycleState(TransactionExecutor.java:176) at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:97) at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2016) at android.os.Handler.dispatchMessage(Handler.java:107) at android.os.Looper.loop(Looper.java:214) at 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) Caused by: java.lang.IllegalStateException: View androidx.fragment.app.FragmentContainerView{e7a7304 V.E...... ......ID 0,0-1080,1868 #7f0a026a app:id/nav_host_fragment} does not have a NavController set at androidx.navigation.Navigation.findNavController(Navigation.java:84) at androidx.navigation.fragment.NavHostFragment.onDestroyView(NavHostFragment.java:388) at androidx.fragment.app.Fragment.performDestroyView(Fragment.java:3171) at androidx.fragment.app.FragmentStateManager.destroyFragmentView(FragmentStateManager.java:726) at androidx.fragment.app.FragmentStateManager.moveToExpectedState(FragmentStateManager.java:360) at androidx.fragment.app.FragmentStore.moveToExpectedState(FragmentStore.java:112) at androidx.fragment.app.FragmentManager.moveToState(FragmentManager.java:1632) at androidx.fragment.app.FragmentManager.dispatchStateChange(FragmentManager.java:3109) at androidx.fragment.app.FragmentManager.dispatchDestroy(FragmentManager.java:3088) at androidx.fragment.app.FragmentController.dispatchDestroy(FragmentController.java:334) at androidx.fragment.app.FragmentActivity.onDestroy(FragmentActivity.java:322) at androidx.appcompat.app.AppCompatActivity.onDestroy(AppCompatActivity.java:278) at 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) at android.app.servertransaction.DestroyActivityItem.execute(DestroyActivityItem.java:44) at android.app.servertransaction.TransactionExecutor.executeLifecycleState(TransactionExecutor.java:176) at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:97) at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2016) at android.os.Handler.dispatchMessage(Handler.java:107) at android.os.Looper.loop(Looper.java:214) at 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)
在花了一些时间调试后,我意识到 androidx.navigation.fragment.NavHostFragment.onDestroyView
试图使用 Navigation.findNavController
方法,该方法在 android/architecture-components-samples GitHub 上描述了 here. I also find a pull request 的一些问题不提供任何明确解决方案的存储库。
注:
如果我通过调用 setupBottomNavigationBar
在 onCreate
中设置底部导航,我不会收到错误并且一切顺利。
我已经卡了一个多星期了,不知道怎么解决。
感谢阅读我的长问题。
更新 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: FATAL EXCEPTION: main Process: com.abc.dev, PID: 3434 java.lang.IllegalStateException: View androidx.fragment.app.FragmentContainerView{1414b18 V.E...... ......ID 0,0-1080,1868 #7f0a026a app:id/nav_host_fragment} does not have a NavController set at androidx.navigation.Navigation.findNavController(Navigation.java:84) at androidx.navigation.fragment.NavHostFragment.onDestroyView(NavHostFragment.java:388) at androidx.fragment.app.Fragment.performDestroyView(Fragment.java:3171) at androidx.fragment.app.FragmentStateManager.destroyFragmentView(FragmentStateManager.java:726) at androidx.fragment.app.FragmentStateManager.moveToExpectedState(FragmentStateManager.java:360) at androidx.fragment.app.SpecialEffectsController$FragmentStateManagerOperation.complete(SpecialEffectsController.java:512) at androidx.fragment.app.DefaultSpecialEffectsController.removeCancellationSignal(DefaultSpecialEffectsController.java:81) at androidx.fragment.app.DefaultSpecialEffectsController.run(DefaultSpecialEffectsController.java:255) at android.os.Handler.handleCallback(Handler.java:883) at android.os.Handler.dispatchMessage(Handler.java:100) at android.os.Looper.loop(Looper.java:214) at 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)
这个问题是在最后调用 detach 而不是 attach 时引起的,这会强制 navController 在父 NavHostFragment 上为 null。
这是导航示例中的代码部分,它决定附加和分离其他片段的片段。
// 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 循环之外,而不是反转列表。
另一种方法是将底部 sheet 附加到片段内,这将简化大多数情况。 Splash 片段将始终是导航到底部导航容器片段的起始目的地,它将在 onViewCreated
中设置底部 sheet。检查 https://github.com/beetlestance/android-extensions
为什么在 onCreate
中设置时有效?
片段仅在 activity 至少启动时才附加或分离。因此,当您在 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 已正确设置。
希望这会有所帮助。