导航组件:IllegalStateException,片段未与片段管理器关联。

11

我正在使用导航组件版本2.1.0-rc01,并使用以下代码在3个屏幕之间来回导航:

Navigation.findNavController(it).navigate(R.id.action_participants)

第二次浏览相同的屏幕后,我可以看到第二个片段,但是我收到了异常。 我在FragmentManager上启用了日志,并且似乎存在同一片段的另一个未附加的实例,导致此错误。

有什么想法,为什么导航组件会创建另一个未附加的片段实例吗? 有什么解决方法可以获取已附加的片段而不是新的实例吗?

    2019-08-15 16:59:30.895 30041-30041/com.app.debug D/FragmentManager:   mName=3-2131361912 mIndex=-1 mCommitted=false
2019-08-15 16:59:30.895 30041-30041/com.app.debug D/FragmentManager:   mEnterAnim=#7f01001e mExitAnim=#7f01001f
2019-08-15 16:59:30.896 30041-30041/com.app.debug D/FragmentManager:   mPopEnterAnim=#7f010020 mPopExitAnim=#7f010021
2019-08-15 16:59:30.896 30041-30041/com.app.debug D/FragmentManager:   Operations:
2019-08-15 16:59:30.896 30041-30041/com.app.debug D/FragmentManager:     Op #0: REPLACE StaffBookingDetailsFragment{82e8301 (97f79b28-d8c1-432a-9e1c-3a781dd42434) id=0x7f0a01c5}
2019-08-15 16:59:30.896 30041-30041/com.app.debug D/FragmentManager:   enterAnim=#7f01001e exitAnim=#7f01001f
2019-08-15 16:59:30.896 30041-30041/com.app.debug D/FragmentManager:   popEnterAnim=#7f010020 popExitAnim=#7f010021
2019-08-15 16:59:30.896 30041-30041/com.app.debug D/FragmentManager:     Op #1: SET_PRIMARY_NAV StaffBookingDetailsFragment{82e8301 (97f79b28-d8c1-432a-9e1c-3a781dd42434) id=0x7f0a01c5}
2019-08-15 16:59:30.897 30041-30041/com.app.debug D/FragmentManager:   enterAnim=#7f01001e exitAnim=#7f01001f
2019-08-15 16:59:30.897 30041-30041/com.app.debug D/FragmentManager:   popEnterAnim=#7f010020 popExitAnim=#7f010021
2019-08-15 16:59:31.935 30041-30041/com.app.debug D/FragmentManager:   mName=4-2131362286 mIndex=-1 mCommitted=false
2019-08-15 16:59:31.935 30041-30041/com.app.debug D/FragmentManager:   Operations:
2019-08-15 16:59:31.936 30041-30041/com.app.debug D/FragmentManager:     Op #0: REPLACE ParticipantsFragment{fdd9ef9 (b7317713-b150-44a2-8b1c-47a0f8c52781) id=0x7f0a01c5}
2019-08-15 16:59:31.936 30041-30041/com.app.debug D/FragmentManager:     Op #1: SET_PRIMARY_NAV ParticipantsFragment{fdd9ef9 (b7317713-b150-44a2-8b1c-47a0f8c52781) id=0x7f0a01c5}
2019-08-15 16:59:55.266 30041-30041/com.app.debug E/AndroidRuntime: FATAL EXCEPTION: main
    Process: com.app.debug, PID: 30041
    java.lang.IllegalStateException: Fragment ParticipantsFragment{b6e8bc7 (aa204a1e-5f3a-40c0-86f0-b5edab4b07eb)} not associated with a fragment manager.
        at androidx.fragment.app.Fragment.requireFragmentManager(Fragment.java:910)
        at com.app.bookings.participants.ParticipantsFragment.onParticipantActionClicked(ParticipantsFragment.kt:88)
        at com.app.databinding.ItemBindParticipantBindingImpl._internalCallbackOnClick(ItemBindParticipantBindingImpl.java:218)
        at com.app.generated.callback.OnClickListener.onClick(OnClickListener.java:11)
        at android.view.View.performClick(View.java:6669)
        at android.view.View.performClickInternal(View.java:6638)
        at android.view.View.access$3100(View.java:789)
        at android.view.View$PerformClick.run(View.java:26145)
        at android.os.Handler.handleCallback(Handler.java:873)
        at android.os.Handler.dispatchMessage(Handler.java:99)
        at android.os.Looper.loop(Looper.java:193)
        at android.app.ActivityThread.main(ActivityThread.java:6863)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:537)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858)
7个回答

16

确保 Fragment 能被垃圾回收/销毁。如果在 onCreateView/onViewCreated 等方法中注册了任何不支持 androidx.lifecycle.Lifecycle 的监听器(即生命周期不感知的注册监听器),Fragment 将不会被垃圾回收/销毁。请确保在 Fragment 的 onDestroyView() 方法中注销此类监听器。

例如:OnBackPressedDispatcher 不支持生命周期管理。因此,它期望你在销毁该 Fragment 时取消注册。如果未注销,则会保留引用,并在其他某个 Fragment 中按返回键时调用它。

我正在调用 findNavController().navigateUp(),位于:


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

             val onBackPressedCallback = object : OnBackPressedCallback(true) {
                override fun handleOnBackPressed() {
                    //some logic that needs to be run before fragment is destroyed
                    findNavController().navigateUp()
                }
            }
            requireActivity().onBackPressedDispatcher.addCallback(
                  onBackPressedCallback
            )
        }

如果您查看findNavController()的文档

在不是[NavHostFragment]或不在[NavHostFragment]内部的片段上调用此函数将导致[IllegalStateException]

这就是为什么我会得到

IllegalStateException Fragment not associated with a fragment manager

解决方案

在onDestroyView中注销侦听器

override fun onDestroyView() {
    super.onDestroyView()
    //unregister listener here
    onBackPressedCallback.isEnabled = false
    onBackPressedCallback.remove()
}

1
非常感谢您详细的解释。这应该被标记为正确答案。 - Pietrek
2
在最新版本中不再适用。 - i30mb1
@i30mb1 文档中提到“提供了访问可取消对象的方法,该对象可以在不依赖于OnBackPressedCallback#remove()的情况下从调度程序中特定地移除此回调函数”,但没有提及回调函数何时被移除。 - Ramakrishna Joshi
event == Lifecycle.Event.ON_DESTROY 时,请查看下面。 - i30mb1

8
如果添加了生命周期所有者,您就不需要在 onDestroy 中删除回调。文档
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
             super.onCreate(savedInstanceState)

             val onBackPressedCallback = object : OnBackPressedCallback(true) {
                override fun handleOnBackPressed() {
                    findNavController().navigateUp()
                }
            }
            // ADD LIFECYCLE OWNER
            requireActivity().onBackPressedDispatcher.addCallback(this,
                  onBackPressedCallback
            )
        }

0

@Ramakrishna Joshi 是正确的。在我的情况下,问题是我正在使用

val menuHost: MenuHost = requireActivity()    
menuHost.addMenuProvider(menuProvider)

所以我必须在onDestroyView中像这样将其删除

override fun onDestroyView() {
    val menuHost: MenuHost = requireActivity()
    menuHost.removeMenuProvider(menuProvider)
    super.onDestroyView()
}

0

您可以使用导航操作的 XML 属性 popUpToInclusive="true" 来指定是否应弹出相同目标的旧实例。 另请参阅文档


0

Navigation.findNavController(it).navigate(R.id.action_participants)

与其放在上述位置

Navigation.findNavController(context).navigate(R.id.action_participants)


这样做有什么优势?为什么第二种方式比第一种更好? - 11m0

0
我发现你使用了ViewBinding。这个类是在Adapter中创建的,不能直接使用findNavController。你需要将当前Fragment传递给Adapter,然后将过去的Fragment传递给需要的类。如果需要Activity,则同样如此。将requireActivity传递给Adapter,然后再传递给需要的类。

-1
经过进一步调查,我验证了这只是当片段没有正确处理时的副作用。现在已经解决了。

你如何正确地处理碎片(Fragment)的释放? - Eren Tüfekçi
@ErenTüfekçi 这个片段泄漏了内存,而且仍然在片段管理器上。这使得导航库在仍有另一个未连接的实例时创建了一个新的片段。 - Leonardo Deleon

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接