Фрагмент дважды инициализируется при перезагрузке активности с вкладками при изменении ориентации

У меня проблема с перезагрузкой активности с вкладками и фрагментами, когда я меняю ориентацию своего устройства.

Вот ситуация:

У меня есть активность, которая имеет 3 вкладки на панели действий. Каждая вкладка загружает другой фрагмент в FrameLayout в главном представлении. Все работает нормально, если я не меняю ориентацию устройства. Но когда я это делаю, Android дважды пытается инициализировать текущий выбранный фрагмент, что приводит к следующей ошибке:

E/AndroidRuntime(2022): Caused by: android.view.InflateException: Binary XML file line #39: Error inflating class fragment

Вот последовательность шагов, которые приводят к ошибке:

  1. Я загружаю активность, выбираю вкладку № 2 и меняю ориентацию устройства.
  2. Android уничтожает активность и экземпляр фрагмента, загруженного вкладкой № 2 (далее «Фрагмент 2»). Затем он переходит к созданию новых экземпляров действия и фрагмента.
  3. Внутри Activity.onCreate() я добавляю первую вкладку на панель действий. Когда я это делаю, эта вкладка автоматически выбирается. Это может стать проблемой в будущем, но сейчас я не возражаю против этого. onTabSelected вызывается, и создается и загружается новый экземпляр первого фрагмента (см. код ниже).
  4. Я добавляю все остальные вкладки без запуска каких-либо событий, и это нормально.
  5. Я звоню ActionBar.selectTab(myTab), чтобы выбрать вкладку № 2.
  6. onTabUnselected() вызывается для первой вкладки, а затем onTabSelected() для второй вкладки. Эта последовательность заменяет текущий фрагмент экземпляром фрагмента 2 (см. код ниже).
  7. Затем вызывается Fragment.onCreateView() для экземпляра Фрагмента 2, и макет фрагмента увеличивается.
  8. Вот в чем проблема. Android вызывает onCreate(), а затем onCreateView() для экземпляра фрагмента ЕЩЕ РАЗ, что создает исключение, когда я пытаюсь раздуть (второй раз) макет.

Очевидно, проблема в том, что Android дважды инициализирует фрагмент, но я не знаю, почему.

Я пробовал НЕ выбирать вторую вкладку при перезагрузке действия, но второй фрагмент все равно инициализируется и не отображается (поскольку я не выбрал его вкладку).

Я нашел этот вопрос: Фрагменты Android воссозданы при изменении ориентации

Пользователь спрашивает в основном то же самое, что и я, но мне не нравится выбранный ответ (это всего лишь обходной путь). Должен быть какой-то способ заставить это работать без трюка android:configChanges.

В случае, если не ясно, что я хочу знать, как предотвратить воссоздание фрагмента или избежать его двойной инициализации. Было бы неплохо узнать, почему так происходит. :П

Вот соответствующий код:

public class MyActivity extends Activity  implements ActionBar.TabListener {

    private static final String TAG_FRAGMENT_1 = "frag1";
    private static final String TAG_FRAGMENT_2 = "frag2";
    private static final String TAG_FRAGMENT_3 = "frag3";

    Fragment frag1;
    Fragment frag2;
    Fragment frag3;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // my_layout contains a FragmentLayout inside
        setContentView(R.layout.my_layout); 

        // Get a reference to the fragments created automatically by Android
        // when reloading the activity
        FragmentManager fm = getFragmentManager();
        this.frag1 = fm.findFragmentByTag(MyActivity.TAG_FRAGMENT_1);
        this.frag2 = fm.findFragmentByTag(MyActivity.TAG_FRAGMENT_2);
        this.frag3 = fm.findFragmentByTag(MyActivity.TAG_FRAGMENT_3)


        ActionBar actionBar = getActionBar();

        // snip...

        // This triggers onTabSelected for the first tab
        actionBar.addTab(actionBar.newTab()
                .setText("Tab1").setTabListener(this)
                .setTag(MyActivity.TAG_FRAGMENT_1));

        actionBar.addTab(actionBar.newTab()
                .setText("Tab2").setTabListener(this)
                .setTag(MyActivity.TAG_FRAGMENT_2));
        actionBar.addTab(actionBar.newTab()
                .setText("Tab3").setTabListener(this)
                .setTag(MyActivity.TAG_FRAGMENT_3));

        Tab t = null;
        // here I get a reference to the tab that must be selected
        // snip...

        // This triggers onTabUnselected/onTabSelected
        ab.selectTab(t);

    }

    @Override
    protected void onDestroy() {
        // Not sure if this is necessary
        this.frag1 = null;
        this.frag2 = null;
        this.frag3 = null;
        super.onDestroy();
    }

    @Override  
    public void onTabSelected(Tab tab, FragmentTransaction ft) {  

        Fragment curFrag = getFragmentInstanceForTag(tab.getTag().toString());
        if (curFrag == null) {
            curFrag = createFragmentInstanceForTag(tab.getTag().toString());
            if(curFrag == null) { 
                // snip... 
                return;
            }
        }
        ft.replace(R.id.fragment_container, curFrag, tab.getTag().toString());
    }

    @Override  
    public void onTabUnselected(Tab tab, FragmentTransaction ft) 
    {  
        Fragment curFrag = getFragmentInstanceForTag(tab.getTag().toString());
        if (curFrag == null) {
            // snip... 
            return;
        }

        ft.remove(curFrag);
    }

    private Fragment getFragmentInstanceForTag(String tag) 
    {
        // Returns this.frag1, this.frag2 or this.frag3
        // depending on which tag was passed as parameter
    }

    private Fragment createFragmentInstanceForTag(String tag) 
    {
        // Returns a new instance of the fragment requested by tag
        // and assigns it to this.frag1, this.frag2 or this.frag3
    }
}

Код фрагмента не имеет значения, он просто возвращает завышенное представление при переопределении метода onCreateView().


person Gerardo Contijoch    schedule 25.06.2012    source источник
comment
Просто предложение ... у вас больше шансов получить ответы, если вы сократите размер своего поста: P. Хотя я, безусловно, ценю, как много подробностей вы включили... Я предлагаю вам включить какое-то краткое изложение, которое легко читается и привлекает внимание людей (где-то в начале вашего поста).   -  person Alex Lockwood    schedule 25.06.2012
comment
Кроме того, использование inline code, жирного шрифта (но не слишком много) и даже html-заголовков (‹h3›‹/h3›) может помочь организовать вашу публикацию... но не злоупотребляйте этим слишком сильно. :П   -  person Alex Lockwood    schedule 25.06.2012
comment
Нужен какой-то код, предпочтительно методы onTabSelected и onTabUnselected   -  person StuStirling    schedule 25.06.2012
comment
Спасибо за предложения. Я думал о длине поста, но хотел как можно больше подробностей, потому что все работает нормально, кроме последней части.   -  person Gerardo Contijoch    schedule 25.06.2012


Ответы (9)


На это я получил простой ответ:

Просто добавьте setRetainInstance(true); к onAttach(Activity activity) или onActivityCreated(Bundle savedInstanceState) фрагмента. Эти два являются обратными вызовами в классе фрагментов.

Итак, в основном, что делает setRetainInstance(true): он поддерживает состояние вашего фрагмента как есть, когда он проходит:

  • onPause();
  • onStop();

Он поддерживает экземпляр фрагмента независимо от того, через что проходит действие. Проблема с этим может заключаться в том, что если фрагментов слишком много, это может создать нагрузку на Систему.

Надеюсь, поможет.

@Override
public void onAttach(Activity activity) {
super.onAttach(activity);

setRetainInstance(true);

}

Как всегда открыт для исправления. С уважением, Эдвард Кихот.

person Edward Quixote    schedule 14.09.2014
comment
У меня не работает, фрагмент продолжает создаваться дважды, поэтому, даже если я сохраняю статус виджетов пакетом, он теряется при второй перезагрузке. - person Miguel El Merendero; 23.10.2014
comment
Я решил свою проблему, хотя не знаю, применимо ли мое решение к этому вопросу: stackoverflow.com/a/26523274/986188 - person Miguel El Merendero; 29.10.2014

Кажется, что когда экран поворачивается и приложение перезапускается, оно воссоздает каждый фрагмент, вызывая конструктор по умолчанию для класса фрагмента.

person Martin Ellison    schedule 10.01.2013
comment
да.. но как ты это преодолеваешь? какова правильная практика? - person ghosh; 26.05.2018

Я столкнулся с той же проблемой и использовал следующий обходной путь:

в начале фрагмента onCreateView:

if (mView != null) {
    // Log.w(TAG, "Fragment initialized again");
    ((ViewGroup) mView.getParent()).removeView(mView);
    return mView;
}
// normal onCreateView
mView = inflater.inflate(R.layout...)
person Iftah    schedule 09.03.2014

Я думаю, что это надежный способ избежать повторного раздувания корневого представления фрагмента:

private WeakReference<View> mRootView;
private LayoutInflater mInflater;

/**
 * inflate the fragment layout , or use a previous one if already stored <br/>
 * WARNING: do not use in any function other than onCreateView
 * */
private View inflateRootView() {
    View rootView = mRootView == null ? null : mRootView.get();
    if (rootView != null) {
        final ViewParent parent = rootView.getParent();
        if (parent != null && parent instanceof ViewGroup)
            ((ViewGroup) parent).removeView(rootView);
        return rootView;
    }
    rootView = mFadingHelper.createView(mInflater);
    mRootView = new WeakReference<View>(rootView);
    return rootView;
}


@Override
public View onCreateView(final LayoutInflater inflater, final ViewGroup container, final Bundle savedInstanceState) {
    mInflater=inflater!=null?inflater:LayoutInflater.from(getActivity());
    final View view = inflateRootView();
    ... //update your data on the views if needed
}
person android developer    schedule 19.03.2014

добавить android:configChanges="orientation|screenSize" в файл манифеста

person zacharia    schedule 20.11.2014

Чтобы защитить воссоздание действия, попробуйте добавить configChanges в свой тег действия (в манифесте), например:

android:configChanges="keyboardHidden|orientation|screenSize"
person rasfarrf5    schedule 02.12.2014

Мой код был немного другим, но я считаю, что наша проблема такая же.

В onTabSelected я не использовал замену, я использую добавление, когда впервые создаю фрагмент, и прикрепляю, если нет. В onTabUnselected я использую отсоединение.

Проблема в том, что когда представление уничтожено, мой фрагмент был прикреплен к FragmentManager и никогда не уничтожался. Чтобы решить эту проблему, я реализовал в onSaveInstanceBundle отсоединение фрагмента от файла FragmentManager.

Код был примерно таким:

FragmentTransition ft = getSupportFragmentManager().begin();
ft.detach(myFragment);
ft.commit();

При первой попытке я поместил этот код в onDestroy, но получил исключение, говорящее мне, что я не могу сделать это после onSaveInstanceBundle, поэтому я переместил код в onSaveInstanceBundle, и все заработало.

Извините, но место, где я работаю, не позволяет мне размещать код здесь, в StackOverflow. Это то, что я помню из кода. Не стесняйтесь редактировать ответ, чтобы добавить код.

person jonathanrz    schedule 03.06.2013

Я думаю, вы столкнулись с тем, с чем столкнулся я. У меня был загрузчик потока для json, который начинается в onCreate() , каждый раз, когда я меняю ориентацию, вызывается поток и запускается загрузка. Я исправил это, используя onSaveInstance() и onRestoreInstance() для передачи ответа json в список, в сочетании с проверкой того, не пуст ли список, поэтому дополнительная загрузка не требуется.

Надеюсь, это даст вам подсказку.

person Antoine Baqain    schedule 04.08.2014

Я решил эту проблему, используя приведенный ниже код.

private void loadFragment(){
     LogUtil.l(TAG,"loadFragment",true);
     fm = getSupportFragmentManager();
     Fragment hf = fm.findFragmentByTag("HOME");
     Fragment sf = fm.findFragmentByTag("SETTING");
     if(hf==null) {
         homeFragment = getHomeFragment();// new HomeFragment();
         settingsFragment = getSettingsFragment();// new Fragment();
         fm.beginTransaction().add(R.id.fm_place, settingsFragment, "SETTING").hide(settingsFragment).commit();
         fm.beginTransaction().add(R.id.fm_place, homeFragment, "HOME").commit();
         activeFragment = homeFragment;
     }else{
         homeFragment = hf;
         settingsFragment = sf;
         activeFragment = sf;
     }
 }

Инициируйте этот метод в OnCreate();

person Sharn25    schedule 24.04.2021