Фрагмент инициализируется дважды при перезагрузке с помощью вкладок при изменении ориентации - программирование
Подтвердить что ты не робот

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

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

Здесь ситуация:

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

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

Здесь последовательность шагов, которые вызывают ошибку:

  • Я загружаю действие, выбираю tab nr 2. и меняю ориентацию устройства.
  • Android уничтожает активность и экземпляр фрагмента, загруженного вкладкой nr 2 (отныне, "Фрагмент 2" ). Затем он начинает создавать новые экземпляры активности и фрагмента.
  • Внутри Activity.onCreate() Я добавляю первую вкладку в панель действий. Когда я это сделаю, эта вкладка автоматически выбирается. Это может представлять проблему в будущем, но я сейчас об этом не возражаю. onTabSelected вызывается и создается и загружается новый экземпляр первого фрагмента (см. код ниже).
  • Я добавляю все остальные вкладки без запуска какого-либо события, и это нормально.
  • Я вызываю ActionBar.selectTab(myTab), чтобы выбрать Tab nr 2.
  • onTabUnselected() вызывается для первой вкладки, а затем onTabSelected() для второй вкладки. Эта последовательность заменяет текущий фрагмент для экземпляра фрагмента 2 (см. Код ниже).
  • Далее, Fragment.onCreateView() вызывается на экземпляр Fragment 2, а макет фрагмента раздувается.
  • Вот проблема. Android Calls onCreate(), а затем onCreateView() в экземпляре фрагмента ONCE AGAIN, который создает исключение, когда я пытаюсь раздуть (второй раз) макет.

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

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

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

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

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

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

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().

4b9b3361

Ответ 1

Я получил для этого простой ответ:

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

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

  • onPause();
  • onStop();

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

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

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

setRetainInstance(true);

}

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

Ответ 2

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

Ответ 3

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

В onTabSelected я не использовал replace, я использую add, когда в первый раз создаю фрагмент и присоединяем if is not. В onTabUnselected я использую detach.

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

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

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

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

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

Ответ 4

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

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

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

Ответ 5

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

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
}

Ответ 6

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

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

Ответ 7

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

Ответ 8

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

android:configChanges="keyboardHidden|orientation|screenSize"