简体   繁体   English

指定的孩子已经有一个父母。 背面压片

[英]The specified child already has a parent. Fragment on back press

I know this question seems to be asked and answered, but it is not true. 我知道这个问题似乎已经被提出并回答了,但事实并非如此。 Because I haven't met a solution yet. 因为我还没有找到解决方案。

I have Activity with a lot of fragments. 我有很多碎片的活动。 And I store all transitions history (can return to each fragment in LIFO order with the back press - because each adds to back stack) 并且我存储了所有转换历史记录(可以按后退按LIFO顺序返回每个片段-因为每个片段都添加到了后退堆栈中)

I want to reach the next feature: When I press back - view of bottom fragment must not be re-creating. 我想达到下一个功能:当我按返回时-不能重新创建底部片段的视图。

I do next 我下一步

1) Use android navigation components and transitions like 1)使用android导航组件和过渡

fun navigate(@IdRes resId: Int, bundle: Bundle? = null, navOptions: NavOptions? = null, sharedElements: List<View>? = null) {
        navController.navigate(resId, bundle, navOptions, sharedElements?.let {
            if (it.isEmpty()) null else
                FragmentNavigatorExtras(
                        *it.map { view -> Pair(view, view.transitionNameCompat.safe) }
                                .filter { pair -> pair.second.isNotEmpty() }
                                .toTypedArray()
                )
        })
    }

where idRes is destination id (not transition id) 其中idRes是目的地ID(不是过渡ID)

2) hold the content view in fragment and detach it from the parent in onCreateView view method. 2)将内容视图保持在片段中,然后在onCreateView视图方法中将其与父视图分离。 Because getView() returns null even fragment appear from back stack. 因为getView()返回null,所以即使从后堆栈中也出现了片段。

private var contentView: View? = null

override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? =
        prepareView(contentView) ?: createView(inflater, container)

private fun prepareView(view: View?): View? {
    val parent = view?.parent
    val viewGroup = parent as? ViewGroup
    viewGroup?.removeView(view)
    return view
}

protected open fun createView(inflater: LayoutInflater, container: ViewGroup?): View? {
    val layout = layout()
    if (layout == -1)
        throw IllegalArgumentException("You need to override \"layout()\" fun or override \"createView\" fun")
    return inflater.inflate(layout(), container, false)
}

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    if (contentView == null) {
        onFirstInitialization(view, savedInstanceState)
    } else {
        onNextInitialization(view, savedInstanceState)
    }
    onEachInitialization(view, savedInstanceState)
    contentView = view
}

I've got exception ONLY IF I transit to next fragment and press "back" rapidly! 仅当我过渡到下一个片段并快速按“返回”时,我才例外! In normal mode all good and correct. 在正常模式下,一切正常。

Exception: 例外:

 java.lang.IllegalStateException: The specified child already has a parent. You must call removeView() on the child's parent first.
        at android.view.ViewGroup.addViewInner(ViewGroup.java:5050)
        at android.view.ViewGroup.addView(ViewGroup.java:4881)
        at android.view.ViewGroup.addView(ViewGroup.java:4821)
        at android.view.ViewGroup.addView(ViewGroup.java:4794)
        at androidx.fragment.app.FragmentManagerImpl.moveToState(FragmentManagerImpl.java:890)
        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.execPendingActions(FragmentManagerImpl.java:1723)
        at androidx.fragment.app.FragmentManagerImpl.dispatchStateChange(FragmentManagerImpl.java:2624)
        at androidx.fragment.app.FragmentManagerImpl.dispatchActivityCreated(FragmentManagerImpl.java:2580)
        at androidx.fragment.app.Fragment.performActivityCreated(Fragment.java:2571)
        at androidx.fragment.app.FragmentManagerImpl.moveToState(FragmentManagerImpl.java:907)
        at androidx.fragment.app.FragmentManagerImpl.moveFragmentToExpectedState(FragmentManagerImpl.java:1235)
        at androidx.fragment.app.FragmentManagerImpl.moveToState(FragmentManagerImpl.java:1301)
        at androidx.fragment.app.FragmentManagerImpl.dispatchStateChange(FragmentManagerImpl.java:2620)
        at androidx.fragment.app.FragmentManagerImpl.dispatchActivityCreated(FragmentManagerImpl.java:2580)
        at androidx.fragment.app.FragmentController.dispatchActivityCreated(FragmentController.java:246)
        at androidx.fragment.app.FragmentActivity.onStart(FragmentActivity.java:541)
        at androidx.appcompat.app.AppCompatActivity.onStart(AppCompatActivity.java:201)
        at android.app.Instrumentation.callActivityOnStart(Instrumentation.java:1470)
        at android.app.Activity.performStart(Activity.java:7176)
        at android.app.ActivityThread.handleStartActivity(ActivityThread.java:3086)
        at android.app.servertransaction.TransactionExecutor.performLifecycleSequence(TransactionExecutor.java:180)
        at android.app.servertransaction.TransactionExecutor.cycleToPath(TransactionExecutor.java:165)
        at android.app.servertransaction.TransactionExecutor.executeLifecycleState(TransactionExecutor.java:142)
        at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:70)
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1926)
        at android.os.Handler.dispatchMessage(Handler.java:106)
        at android.os.Looper.loop(Looper.java:193)
        at android.app.ActivityThread.main(ActivityThread.java:6923)
        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)

I can solve this by place prepareView to onDestroyView lifecycle method and everything work correctly, but it produces memory leak: I find it out - when the view has not a parent after onDestroyView then onDestroy never called and fragments hold in memory even after the back press. 我可以通过将prepareView放置到onDestroyView生命周期方法中来解决此问题,并且一切正常,但是会产生内存泄漏:我发现了-当视图在onDestroyView之后没有父级时,则永远不会调用onDestroy并且即使在按回键之后,片段也会保留在内存中。

You can say "Hey dude, don't hurry, use your app slowly and the exception will never be thrown, as you say. But app in production and I met crashes with this bug" :( How I can handle this case? 您可以说:“嘿,伙计,不要着急,慢慢使用您的应用程序,就像您说的那样,永远不会抛出异常。但是在生产中,我遇到了此bug导致崩溃的bug” :(我如何处理这种情况?

UPDATE: I researched a little and found out something. 更新:我研究了一下,发现了一些东西。 Exception thrown only when view not detached from window. 仅当视图未从窗口分离时抛出异常。 And view detach from window only after 300-400 ms after new Fragment opened 并且仅在新片段打开后300-400毫秒后才从窗口中分离视图

Yes! 是! It's true. 这是真的。 You can't create again its object because it has a parent. 您无法再次创建其对象,因为它有一个父对象。 The main problem may you haven't noticed is whenever you called your fragment again then onCreateView() is called. 您可能没有注意到的主要问题是,每当您再次调用片段时,就会调用onCreateView()

So, in-sort I suggest you move your code from onCreateView() to onCreate() method of the fragment. 因此,在排序中,我建议您将代码从片段的onCreateView()移至onCreate()方法。

I also struggled with this type of problem. 我也为这类问题而苦恼。 So, inflate your view in onCreate() method and return main view in onCreateView() method. 因此,膨胀在您看来onCreate()方法,并返回主视图onCreateView()方法。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 指定的孩子已经有一个父母。 - The specified child already has a parent. 快速返回会抛出指定的孩子已经有一个父母。 您必须调用 removeView() - Navigating back quickly throws The specified child already has a parent. You must call removeView() 指定的孩子已经有一个父母。 你必须首先对孩子的父母打电话removeView()(与片段和ViewPager活动) - The specified child already has a parent. You must call removeView() on the child's parent first (Activity with fragment and ViewPager) 片段中的PopupWindow显示-指定的子代已经有一个父代。 您必须先在孩子的父母上调用removeView() - PopupWindow show from fragment - The specified child already has a parent. You must call removeView() on the child's parent first 反应原生异常:指定的孩子已经有一个父母。 - React native exception: The specified child already has a parent. 指定的孩子已经有一个父母。 我想念什么 - The specified child already has a parent. What am I missing 在片段中使用数据绑定时出现异常:“指定的子节点已经有父节点。你必须首先在孩子的父母上调用removeView()“ - Exception when using data binding in fragment: “The specified child already has a parent. You must call removeView() on the child's parent first” Android Fragment API 26“指定的子代已经有一个父代。 您必须先在孩子的父母上调用removeView()。” - Android Fragment API 26 “ The specified child already has a parent. You must call removeView() on the child's parent first.” Fragments - 指定的孩子已经有一个父母。 在片段导航时,您必须先在孩子的父级上调用 removeView() - Fragments - The specified child already has a parent. You must call removeView() on the child's parent first while fragment navigation 指定的孩子已经有一个父母。 您必须调用removeView() - The specified child already has a parent. You must call removeView()
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM