【问题标题】:Child fragment gets destroyed for no good reason子片段无缘无故被破坏
【发布时间】:2013-03-04 14:24:49
【问题描述】:

信息: 我在 ParentFragment 内有一个 2 窗格布局(2 个子 Fragments),当然,在 FragmentActivity 内。我在ParentFragment 上有setRetainInstance(true)。在方向更改时,left 子片段 不会 被破坏(onCreate() 不会被调用),这是正常的(因为父片段保留了它的实例) .

问题:在方向改变时,片段破坏(onCreate()被调用)。为什么右边的碎片被破坏了而左边的碎片没有?

编辑:如果我删除 setRetainInstance(true),那么 left 片段的 onCreate() 会被调用 两次 (lol wtf) 并且 right 片段的 onCreate() 被调用一次。所以这也不好……

ParentFragment 的代码如下:

@Override
public void onCreate(Bundle savedInstanceState)
{
    super.onCreate(savedInstanceState);
    this.setRetainInstance(true);
}

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
        Bundle savedInstanceState)
{
    View view = inflater.inflate(R.layout.fragment_schedule, container, false);
    setHasOptionsMenu(true);


    if (getChildFragmentManager().findFragmentById(R.id.fragment_schedule_framelayout_left) == null || 
            !getChildFragmentManager().findFragmentById(R.id.fragment_schedule_framelayout_left).isInLayout())
    {
        if (mPresentationsListFragment == null)
            mPresentationsListFragment = PresentationsListFragment.newInstance(PresentationsListFragment.TYPE_SCHEDULE, mScheduleDate);
        getChildFragmentManager().beginTransaction()
                                     .replace(R.id.fragment_schedule_framelayout_left, mPresentationsListFragment)
                                     .commit();
    }
    mPresentationsListFragment.setOnPresentationClickListener(this);


    return view;
}


@Override
    public void onPresentationClick(int id)
    {
        if (Application.isDeviceTablet(getActivity()))
        {
            if (getChildFragmentManager().findFragmentById(R.id.fragment_schedule_framelayout_right) == null)
            {
                if (mPresentationDetailFragment == null)
                    mPresentationDetailFragment = PresentationDetailFragment.newInstance(id);
                else
                    mPresentationDetailFragment.loadPresentation(id);
                getChildFragmentManager().beginTransaction()
                                           .replace(R.id.fragment_schedule_framelayout_right, mPresentationDetailFragment)
                                           .commit();
            }
            else
                mPresentationDetailFragment.loadPresentation(id);
        }
        else
        {
            Intent presentationDetailIntent = new Intent(getActivity(), PresentationDetailActivity.class);
            presentationDetailIntent.putExtra(PresentationDetailActivity.KEY_PRESENTATION_ID, id);
            startActivity(presentationDetailIntent);
        }
    }

LE 解决方案: 非常感谢 antonyt ,答案如下。 pe 执行所需的唯一更改位于父 Fragment 的 onCreateView() 中。

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
        Bundle savedInstanceState)
{
    View view = inflater.inflate(R.layout.fragment_schedule, container, false);
    setHasOptionsMenu(true);


    if (getChildFragmentManager().findFragmentById(R.id.fragment_presentations_framelayout_left) == null)
    {
        mPresentationsListFragment = PresentationsListFragment.newInstance();
        mPresentationsListFragment.setOnPresentationClickListener(this);
        getChildFragmentManager().beginTransaction()
                .add(R.id.fragment_presentations_framelayout_left, mPresentationsListFragment)
                .commit();
    }


    return view;
}

【问题讨论】:

  • 对我来说,调用ParentFragment.setRetainState(true) 不会保留它的子片段的状态,这与这个片段是从布局中设置还是在ParentFragment.onCreateView() 中动态设置无关。跨度>
  • 对不起,我没明白你的意思。 XM1 中没有设置任何片段。 XML 布局文件仅包含 FrameLayouts

标签: android android-fragments android-fragmentactivity android-nested-fragment


【解决方案1】:

据我了解,如果您在上述代码的父片段上有setRetainInstance(true),则应该重新创建您的 left 片段,但不应重新创建您的 right 片段, 改变方向时。这与您上面写的内容相反,但我会解释为什么会这样。如果您在父片段上有setRetainInstance(false),您确实应该看到左侧片段被创建了两次,而右侧片段被创建了一次。

案例一:setRetainInstance(true)

您的父片段不会在旋转时被销毁。但是,它每次仍会重新创建其视图(onDestroyViewonCreateView 将按此顺序调用)。在onCreateView 中,您有在特定条件下添加左侧片段的代码。 getChildFragmentManager().findFragmentById(R.id.fragment_schedule_framelayout_left) 应该不为空,因为之前已将片段添加到该容器中。 getChildFragmentManager().findFragmentById(R.id.fragment_schedule_framelayout_left).isInLayout() 应该是假的,因为 only fragments added via XML will cause it to return true。整体条件为真,因此将创建左侧片段的新实例,并将替换旧实例。您的右侧片段仅在单击事件期间实例化,因此不会发生特殊行为。

总结:父片段保留,新的左片段创建,右片段保留。

案例 2:setRetainInstance(false)

您的父片段被销毁,左右片段也被销毁。所有三个片段都由 Android 自动重新创建。然后,您的父片段将有机会创建其视图,并且它将按照上面的说明创建左侧片段的新实例。刚刚创建的左片段将被这个新实例替换。您会观察到一个左片段将被销毁,另一个左片段将被创建。正确的片段不会发生特殊行为。

总结:创建新的父片段,创建两个新的左侧片段,创建新的右侧片段。

如果您确定在setRetainInstance(true) 的情况下,您的右侧片段被破坏而不是您的左侧片段,请在 github/etc 上发布一个示例项目。这证明了这一点。

更新:如果您在左侧片段上使用 FragmentTransaction.replace(),为什么右侧片段会被删除

由于内部条件,您的代码将尝试在同一个容器上将您的左侧片段替换为自身。

以下是 Android 4.1 源代码中处理替换的代码 sn-p:

...
case OP_REPLACE: {
    Fragment f = op.fragment;
    if (mManager.mAdded != null) {
        for (int i=0; i<mManager.mAdded.size(); i++) {
            Fragment old = mManager.mAdded.get(i);
            if (FragmentManagerImpl.DEBUG) Log.v(TAG,
                    "OP_REPLACE: adding=" + f + " old=" + old);
            if (f == null || old.mContainerId == f.mContainerId) {
                if (old == f) {
                    op.fragment = f = null;
                } else {
                    if (op.removed == null) {
                        op.removed = new ArrayList<Fragment>();
                    }
                    op.removed.add(old);
                    old.mNextAnim = op.exitAnim;
                    if (mAddToBackStack) {
                        old.mBackStackNesting += 1;
                        if (FragmentManagerImpl.DEBUG) Log.v(TAG, "Bump nesting of "
                                + old + " to " + old.mBackStackNesting);
                    }
                    mManager.removeFragment(old, mTransition, mTransitionStyle);
                }
            }
        }
    }
    if (f != null) {
        f.mNextAnim = op.enterAnim;
        mManager.addFragment(f, false);
    }
} break;
...

如果你尝试用自己替换同一个片段,有一些代码可以尝试忽略这个操作:

if (old == f) {
    op.fragment = f = null;
}

由于f 为空,并且我们仍在继续迭代我们的片段,这似乎具有从 FragmentManager 中删除每个后续片段的副作用。我不认为这是故意的,但至少可以解释为什么你的正确片段被破坏了。不使用替换/不替换同一个片段本身可以解决您的问题。

有趣的是,这是最近发生的变化,在以前的 Android 版本中不存在。 https://github.com/android/platform_frameworks_support/commit/5506618c80a292ac275d8b0c1046b446c7f58836

错误报告:https://code.google.com/p/android/issues/detail?id=43265

【讨论】:

  • 案例 1,“整体条件为真,因此将创建左侧片段的新实例”。不,你忘记了那个盒子的内部如果。 if (mPresentationsListFragment == null) mPresentationsListFragment = PresentationsListFragment.newInstance(PresentationsListFragment.TYPE_SCHEDULE, mScheduleDate);如果这个 if 不存在,那么是的,左侧片段的 onCreate() 应该已经启动,我完全同意。但不幸的是,内部 if 存在。
  • 啊哈,我想我理解了你的第二个案例,我也同意你的观点。所以对于案例号。 2(如果我想使用它),我只需要从父的 onCreateView() 中删除左片段的创建,如果 savedInstanceState 是 != null 并且在这种情况下应该可以解决它。但我最初的问题仍然存在;第一种情况呢?
  • 我已经设法在一个示例应用程序中重现了它。这是链接filehostfree.com/?d=51F26D5E1 一切正常,直到方向改变。你可以在日志中看到,父片段 onCreateView(),左边 onCreateView(),右边 onCreateView(),然后突然右边 onDestroy()。那么 onDestroy() 调用是怎么回事?
  • 啊,是的,我忘记了你的第二个内部条件语句。在任何情况下,我都会删除外部 isInLayout 检查和内部条件。我不知道演示详细信息片段被破坏的确切原因,但我可以告诉您,将“替换”更改为“添加”会使其正常工作(正确的片段不会在旋转时被破坏)。我将进一步研究为什么会发生这种情况。
  • @Andrew 我已经用我能找到的内容更新了我的答案。
猜你喜欢
  • 2023-03-10
  • 2020-09-15
  • 1970-01-01
  • 2020-08-17
  • 2018-03-24
  • 1970-01-01
  • 1970-01-01
  • 2018-02-15
  • 1970-01-01
相关资源
最近更新 更多