Фрагмент инициализируется дважды при перезагрузке с помощью вкладок при изменении ориентации
У меня есть проблема с перезагрузкой активности с вкладками и фрагментами при изменении ориентации моего устройства.
Здесь ситуация:
У меня есть активность, которая имеет 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()
.
Ответы
Ответ 1
Я получил для этого простой ответ:
Просто добавьте setRetainInstance(true);
в фрагмент onAttach(Activity activity)
или onActivityCreated(Bundle savedInstanceState)
.
Эти два являются обратными вызовами в классе фрагментов.
Итак, в основном, что setRetainInstance(true)
делает:
Он сохраняет состояние вашего фрагмента как есть, когда он проходит:
Он поддерживает экземпляр Фрагмента независимо от того, что происходит в Деятельности.
Проблема с ним может быть, если слишком много фрагментов, это может вызвать нагрузку на систему.
Надеюсь, что это поможет.
@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"