Дочерний фрагмент уничтожается без уважительной причины

Информация: у меня есть двухпанельный макет (2 дочерних Fragments) внутри ParentFragment, который, конечно же, находится внутри FragmentActivity. У меня setRetainInstance(true) на ParentFragment. При изменении ориентации левый дочерний фрагмент не уничтожается (onCreate() не вызывается), что нормально (из-за того, что родительский объект сохранил свой экземпляр).

Проблема: при изменении ориентации правый фрагмент уничтожается (вызывается onCreate()). Какого черта правый фрагмент разрушен, а левый - нет?

РЕДАКТИРОВАТЬ: если я удалю setRetainInstance(true), тогда onCreate() левого фрагмента будет вызван дважды (lol wtf), а правый 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, ответ приведен ниже. Единственные изменения, необходимые для выполнения, находятся внутри onCreateView () родительского Fragment.

@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;
}

person Bogdan Zurac    schedule 04.03.2013    source источник
comment
На мой взгляд, вызов ParentFragment.setRetainState(true) не сохраняет состояние его дочерних фрагментов, независимо от того, установлен ли этот фрагмент из макета или динамически в ParentFragment.onCreateView().   -  person riwnodennyk    schedule 05.03.2013
comment
Простите, я не понял, что вы имели в виду. Ни один из фрагментов не установлен в XMl. Файл макета XML содержит только FrameLayouts.   -  person Bogdan Zurac    schedule 05.03.2013


Ответы (1)


Насколько я понимаю, если у вас есть setRetainInstance(true) на родительском фрагменте с приведенным выше кодом, ваш левый фрагмент должен быть воссоздан, но ваш правый фрагмент не должен быть создан при изменении ориентации. Это обратное тому, что вы написали выше, но я все равно объясню, почему это так. Если у вас есть setRetainInstance(false) на родительском фрагменте, вы действительно должны увидеть, что левый фрагмент создается дважды, а правый - один раз.

Случай 1: setRetainInstance(true)

Ваш родительский фрагмент не будет уничтожен при вращении. Однако он все равно будет каждый раз воссоздавать свои представления (onDestroyView и onCreateView будут вызываться в указанном порядке). В onCreateView у вас есть код для добавления вашего левого фрагмента при определенных условиях. getChildFragmentManager().findFragmentById(R.id.fragment_schedule_framelayout_left) не должен быть нулевым, поскольку фрагмент был добавлен в этот контейнер ранее. getChildFragmentManager().findFragmentById(R.id.fragment_schedule_framelayout_left).isInLayout() должно быть ложным, поскольку только фрагменты, добавленные через XML, будут вызывать это вернуть true. Общее условие истинно, и поэтому будет создан новый экземпляр вашего левого фрагмента, который заменит старый. Ваш правый фрагмент создается только во время события щелчка, поэтому особого поведения не происходит.

Резюме: остается родительский фрагмент, создается новый левый фрагмент, остается правый фрагмент.

Случай 2: setRetainInstance(false)

Ваш родительский фрагмент уничтожен, как и левый и правый фрагменты. Все три фрагмента автоматически воссоздаются Android. Затем ваш родительский фрагмент получит возможность создать свое представление, и он создаст новый экземпляр левого фрагмента в соответствии с объяснением выше. Только что созданный левый фрагмент будет заменен этим новым экземпляром. Вы увидите, что левый фрагмент будет уничтожен и будет создан другой левый фрагмент. Для правильного фрагмента особого поведения не происходит.

Резюме: создается новый родительский фрагмент, создаются два новых левых фрагмента, создается новый правый фрагмент.

Если вы уверены, что в случае setRetainInstance(true) уничтожается ваш правый фрагмент, а не левый, опубликуйте образец проекта в github / etc. что демонстрирует это.

Обновление: почему удаляется правый фрагмент, если вы используете FragmentTransaction.replace() для левого фрагмента

Из-за внутреннего условия ваш код попытается заменить ваш левый фрагмент самим собой в том же контейнере.

Вот фрагмент кода из исходного кода Android 4.1, который обрабатывает замену:

...
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 имеет значение null, и мы все еще продолжаем перебирать наши фрагменты, это, похоже, имеет побочный эффект удаления каждого последующего фрагмента из FragmentManager. Я не думаю, что это сделано намеренно, но, по крайней мере, объясняет, почему ваш правый фрагмент разрушается. Отказ от замены / без замены одного и того же фрагмента самим собой может решить ваши проблемы.

Интересно, что это было недавнее изменение, которого не было в предыдущих версиях Android. https://github.com/android/platform_frameworks_support6c10780a1508c8c08c08c08c8c08c8c8c8c8c06

Отчет об ошибке: https://code.google.com/p/android/issues/detail?id=43265

person antonyt    schedule 25.07.2013
comment
Случай 1. Общее условие истинно, и поэтому будет создан новый экземпляр вашего левого фрагмента. Нет. В этом случае ты забыл о внутреннем если. если (mPresentationsListFragment == null) mPresentationsListFragment = PresentationsListFragment.newInstance (PresentationsListFragment.TYPE_SCHEDULE, mScheduleDate); Если этого if не было, то да, должна была сработать функция onCreate () для левого фрагмента, я полностью согласен. Но, к сожалению, внутреннее «если» существует. - person Bogdan Zurac; 26.07.2013
comment
Ага, я думаю, что понял ваш второй случай, и я также согласен с вами в этом. Так что для случая нет. 2 (если я хочу его использовать), мне нужно только удалить создание левого фрагмента из родительского onCreateView (), если saveInstanceState равен! = Null, и это должно решить эту проблему в этом случае. Но мои первоначальные вопросы остаются в силе; как насчет первого случая? - person Bogdan Zurac; 26.07.2013
comment
Мне удалось воспроизвести это на примере приложения. Вот ссылка filehostfree.com/?d=51F26D5E1 Все работает нормально, до смены ориентации . Там вы можете увидеть в журналах родительский фрагмент onCreateView (), слева onCreateView (), справа onCreateView (), а потом вдруг прямо onDestroy (). Так что случилось с вызовом onDestroy ()? - person Bogdan Zurac; 26.07.2013
comment
Ах да, я забыл о вашем втором внутреннем условном утверждении. В любом случае я бы удалил внешнюю проверку isInLayout и внутреннюю условную. Я не знаю точно, почему фрагмент детали презентации разрушается, но могу сказать вам, что изменение «заменить» на «добавить» заставляет его работать правильно (правильный фрагмент не разрушается при повороте). Я подробнее рассмотрю, почему это происходит. - person antonyt; 26.07.2013
comment
@ Андрей Я обновил свой ответ тем, что мне удалось найти. - person antonyt; 26.07.2013
comment
Вот это да. Я полностью впечатлен вашим исследованием и в то же время совершенно поражен ошибкой во фреймворке. Однажды я сказал, что фрагментная часть фреймворка действительно содержит ошибки, но это ... ох, это чертовски раздражает. Большое спасибо за ваши усилия. Я проверю это позже сегодня в коде, если у меня возникнет перерыв. Замечательная находка, кстати. - person Bogdan Zurac; 31.07.2013
comment
Если я попытаюсь использовать add вместо replace, он попытается добавить фрагмент, даже если он уже добавлен. Так что это не работает. Как это сработало для вас? ЛЭ: Да ладно, я понял. Я также дополню исходный вопрос соответствующим кодом, чтобы он также был виден другим. Большое спасибо за все. - person Bogdan Zurac; 01.08.2013