Жизненный цикл Android Fragment при изменении ориентации


120

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

После перекодирования действия для использования фрагментов в приложении я не смог заставить работать изменение ориентации / управление состоянием, поэтому я создал небольшое тестовое приложение с одним FragmentActivity и одним фрагментом.

Журналы от изменения ориентации странные, с множественными вызовами фрагментов OnCreateView.

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

Кто-нибудь может пролить свет на то, что я делаю здесь не так, пожалуйста. Спасибо

Бревно выглядит следующим образом после изменения ориентации.

Initial creation
12-04 11:57:15.808: D/FragmentTest.FragmentTestActivity(3143): onCreate
12-04 11:57:15.945: D/FragmentTest.FragmentOne(3143): OnCreateView
12-04 11:57:16.081: D/FragmentTest.FragmentOne(3143): OnCreateView->SavedInstanceState null


Orientation Change 1
12-04 11:57:39.031: D/FragmentTest.FragmentOne(3143): onSaveInstanceState
12-04 11:57:39.031: D/FragmentTest.FragmentTestActivity(3143): onCreate
12-04 11:57:39.031: D/FragmentTest.FragmentOne(3143): OnCreateView
12-04 11:57:39.031: D/FragmentTest.FragmentOne(3143): OnCreateView->SavedInstanceState not null
12-04 11:57:39.031: D/FragmentTest.FragmentOne(3143): OnCreateView
12-04 11:57:39.167: D/FragmentTest.FragmentOne(3143): OnCreateView->SavedInstanceState null


Orientation Change 2
12-04 11:58:32.162: D/FragmentTest.FragmentOne(3143): onSaveInstanceState
12-04 11:58:32.162: D/FragmentTest.FragmentOne(3143): onSaveInstanceState
12-04 11:58:32.361: D/FragmentTest.FragmentTestActivity(3143): onCreate
12-04 11:58:32.361: D/FragmentTest.FragmentOne(3143): OnCreateView
12-04 11:58:32.361: D/FragmentTest.FragmentOne(3143): OnCreateView->SavedInstanceState not null
12-04 11:58:32.361: D/FragmentTest.FragmentOne(3143): OnCreateView
12-04 11:58:32.361: D/FragmentTest.FragmentOne(3143): OnCreateView->SavedInstanceState not null
12-04 11:58:32.498: D/FragmentTest.FragmentOne(3143): OnCreateView
12-04 11:58:32.498: D/FragmentTest.FragmentOne(3143): OnCreateView->SavedInstanceState null

Основная деятельность (FragmentActivity)

public class FragmentTestActivity extends FragmentActivity {
/** Called when the activity is first created. */

private static final String TAG = "FragmentTest.FragmentTestActivity";


FragmentManager mFragmentManager;

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);

    Log.d(TAG, "onCreate");

    mFragmentManager = getSupportFragmentManager();
    FragmentTransaction fragmentTransaction = mFragmentManager.beginTransaction();

    FragmentOne fragment = new FragmentOne();

    fragmentTransaction.add(R.id.fragment_container, fragment);
    fragmentTransaction.commit();
}

И фрагмент

public class FragmentOne extends Fragment {

private static final String TAG = "FragmentTest.FragmentOne";

EditText mEditText;

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
        Bundle savedInstanceState) {

    Log.d(TAG, "OnCreateView");

    View v = inflater.inflate(R.layout.fragmentonelayout, container, false);

    // Retrieve the text editor, and restore the last saved state if needed.
    mEditText = (EditText)v.findViewById(R.id.editText1);

    if (savedInstanceState != null) {

        Log.d(TAG, "OnCreateView->SavedInstanceState not null");

        mEditText.setText(savedInstanceState.getCharSequence("text"));
    }
    else {
        Log.d(TAG,"OnCreateView->SavedInstanceState null");
    }
    return v;
}

@Override
public void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);

    Log.d(TAG, "FragmentOne.onSaveInstanceState");

    // Remember the current text, to restore if we later restart.
    outState.putCharSequence("text", mEditText.getText());
}

манифест

<uses-sdk android:minSdkVersion="8" />

<application
    android:icon="@drawable/ic_launcher"
    android:label="@string/app_name" >
    <activity
        android:label="@string/app_name"
        android:name=".activities.FragmentTestActivity" 
        android:configChanges="orientation">
        <intent-filter >
            <action android:name="android.intent.action.MAIN" />

            <category android:name="android.intent.category.LAUNCHER" />
        </intent-filter>
    </activity>
</application>

Я не знаю, правильный ли это ответ, но попробуйте использовать тег при добавлении фрагмента, добавьте (R.id.fragment_container, fragment, "MYTAG") или, если это не удается, замените (R.id.fragment_container, fragment, "MYTAG ")
Джейсон

2
Провожу расследования. Когда основное действие (FragmentTestActivity) перезапускается при изменении ориентации, и я получаю новый экземпляр FragmentManager, затем выполняю FindFragmentByTag, чтобы найти фрагмент, который все еще существует, поэтому фрагмент, который он сохраняет, при воссоздании основного действия. Если я найду фрагмент и ничего не сделаю, он все равно снова отобразится с помощью MainActivity.
MartinS

Ответы:


189

Вы накладываете свои фрагменты один на другой.

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

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

if (savedInstanceState == null) {
    // only create fragment if activity is started for the first time
    mFragmentManager = getSupportFragmentManager();
    FragmentTransaction fragmentTransaction = mFragmentManager.beginTransaction();

    FragmentOne fragment = new FragmentOne();

    fragmentTransaction.add(R.id.fragment_container, fragment);
    fragmentTransaction.commit();
} else {        
    // do nothing - fragment is recreated automatically
}

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


54
«Большую часть времени это
сильная

1
Как можно обработать тот же сценарий в случае использования ViewPage с FragmentStatePagerAdapter ... любое предложение?
CoDe

5
Есть ли подобное утверждение в официальной документации? Разве это не противоречит тому, что сказано в руководстве "when the activity is destroyed, so are all fragments":? Поскольку "When the screen orientation changes, the system destroys and recreates the activity [...]".
cYrus

4
Сайрус - Нет, Activity действительно уничтожено, фрагменты, которые оно содержит, ссылаются в FragmentManager, а не только из Activity, поэтому оно остается и читается.
Грэм

4
регистрация фрагментов методов onCreate и onDestroy, а также его хэш-код после нахождения в FragmentManager ясно показывает, что фрагмент IS уничтожен. он просто воссоздается и автоматически подключается заново. только если вы поместите setRetainInstance (true) во фрагменты метода onCreate, он действительно не будет уничтожен
Lemao1981

87

Чтобы процитировать эту книгу , «для обеспечения согласованного взаимодействия с пользователем Android сохраняет макет фрагмента и связанный задний стек при перезапуске Activity из-за изменения конфигурации». (стр.124)

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

@Override
public void onCreate(Bundle savedInstanceState) {

        ...    

    FragmentOne fragment = (FragmentOne) mFragmentManager.findFragmentById(R.id.fragment_container); 

    if (fragment == null) {
        FragmentTransaction fragmentTransaction = mFragmentManager.beginTransaction();
        fragmentTransaction.add(R.id.fragment_container, new FragmentOne());
        fragmentTransaction.commit();
    }
}

2
Вы, наверное, сэкономили мне много времени с этим ... большое спасибо. Вы можете объединить этот ответ с ответом Грэма, чтобы получить идеальное решение для обработки изменений и фрагментов конфигурации.
azpublic 05

10
На самом деле это правильный ответ, а не отмеченный. Большое спасибо!
Уриэль Франкель

как можно обработать тот же сценарий в случае реализации ViewPager Fragment.
CoDe

Эта маленькая жемчужина помогла мне решить проблему, над которой я работал несколько дней. Спасибо! Это определенно решение.
Whome

1
@SharpEdge Если у вас несколько фрагментов, вы должны указать им теги при добавлении в контейнер, а затем использовать mFragmentManager.findFragmentByTag (вместо findFragmentById) для получения ссылок на них - таким образом вы будете знать класс каждого фрагмента и сможете бросить правильно
k29

10

Метод onCreate () вашего действия вызывается после изменения ориентации, как вы видели. Итак, не выполняйте FragmentTransaction, который добавляет фрагмент после изменения ориентации в вашей деятельности.

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    if (savedInstanceState==null) {
        //do your stuff
    }
}

Фрагменты должны и должны быть неизменными.


Знаем ли мы, что экземпляр будет сохранен после создания и добавления фрагмента? Я имею в виду шляпу, если пользователь вращается непосредственно перед добавлением фрагмента? У нас по-прежнему будет сохраненное состояние, отличное от NULL, которое не содержит состояния фрагмента
Фарид

4

Вы можете @Overrideиспользовать FragmentActivity onSaveInstanceState(). Убедитесь, что вы не вызывали super.onSaveInstanceState()метод.


2
Скорее всего, это нарушит жизненный цикл действий, создав больше потенциальных проблем в этом и без того довольно запутанном процессе. Загляните в исходный код FragmentActivity: он сохраняет там состояния всех фрагментов.
Брайан

У меня была проблема, что у меня разное количество адаптеров для разной ориентации. Так что у меня всегда была странная ситуация после того, как я повернул устройство и пролистал несколько страниц, я получил старую и неправильную. при включении сохраненного экземпляра он лучше всего работает без утечек памяти (я использовал setSavedEnabled (false) до этого и в итоге получал большие утечки памяти при каждом изменении ориентации)
Informatic0re

0

Мы всегда должны пытаться предотвратить исключение нулевого указателя, поэтому мы должны сначала проверить в методе saveinstance информацию о пакете. для краткого объяснения, чтобы проверить эту ссылку в блоге

public static class DetailsActivity extends Activity {

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

        if (getResources().getConfiguration().orientation
            == Configuration.ORIENTATION_LANDSCAPE) {
            // If the screen is now in landscape mode, we can show the
            // dialog in-line with the list so we don't need this activity.
            finish();
            return;
        }

        if (savedInstanceState == null) {
            // During initial setup, plug in the details fragment.
            DetailsFragment details = new DetailsFragment();
            details.setArguments(getIntent().getExtras());
            getFragmentManager().beginTransaction().add(android.R.id.content, details).commit();
        }
    } 
}

0

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

Вы автоматически определите состояние экрана, загрузите соответствующий макет), из-за необходимости повторно инициализировать действие или фрагмент, пользовательский интерфейс не очень хороший, а не непосредственно на переключателе экрана, я имею в виду? URL = YgNfP-VHY-Nuldi7YHTfNet3AtLdN-w__O3z1wLOnzr3wDjYo7X7PYdNyhw8R24ZE22xiKnydni7R0r35s2fOLcHOiLGYT9Qh_fjqtytJki & = & WD eqid = f258719e0001f24000000004585a1082

Предпосылка состоит в том, что ваш макет использует вес, как макет layout_weight, следующим образом:

<LinearLayout
Android:id= "@+id/toplayout"
Android:layout_width= "match_parent"
Android:layout_height= "match_parent"
Android:layout_weight= "2"
Android:orientation= "horizontal" >

Итак, мой подход заключается в том, что при переключении экрана не нужно загружать новый макет файла представления, изменять макет в динамических весах onConfigurationChanged, следующие шаги: 1 первый набор: AndroidManifest.xml в атрибуте активности: android: configChanges = "keyboardHidden | Ориентация | screenSize" Чтобы предотвратить переключение экрана, избегайте повторной загрузки, чтобы иметь возможность отслеживать в onConfigurationChanged 2 действие перезаписи или фрагмент в методе onConfigurationChanged.

@Override
Public void onConfigurationChanged (Configuration newConfig) {
    Super.onConfigurationChanged (newConfig);
    SetContentView (R.layout.activity_main);
    if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) {
        //On the layout / / weight adjustment
        LinearLayout toplayout = (LinearLayout) findViewById (R.id.toplayout);
        LinearLayout.LayoutParams LP = new LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, 0, 2.0f);
        Toplayout.setLayoutParams (LP);
        LinearLayout tradespace_layout = (LinearLayout) findViewById(R.id.tradespace_layout);
        LinearLayout.LayoutParams LP3 = new LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, 0, 2.8f);
        Tradespace_layout.setLayoutParams (LP3);
    }
    else if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT)
    {
        //On the layout / / weight adjustment
        LinearLayout toplayout = (LinearLayout) findViewById (R.id.toplayout);
        LinearLayout.LayoutParams LP = new LayoutParams (LinearLayout.LayoutParams.MATCH_PARENT, 0, 2.8f);
        Toplayout.setLayoutParams (LP);
        LinearLayout tradespace_layout = (LinearLayout) findViewById (R.id.tradespace_layout);
        LinearLayout.LayoutParams LP3 = new LayoutParams (LinearLayout.LayoutParams.MATCH_PARENT, 0, 2.0f);
        Tradespace_layout.setLayoutParams (LP3);
    }
}

0

При изменении конфигурации платформа создаст для вас новый экземпляр фрагмента и добавит его в действие. Итак, вместо этого:

FragmentOne fragment = new FragmentOne();

fragmentTransaction.add(R.id.fragment_container, fragment);

сделай это:

if (mFragmentManager.findFragmentByTag(FRAG1_TAG) == null) {
    FragmentOne fragment = new FragmentOne();

    fragmentTransaction.add(R.id.fragment_container, fragment, FRAG1_TAG);
}

Обратите внимание, что фреймворк добавляет новый экземпляр FragmentOne при изменении ориентации, если вы не вызываете setRetainInstance (true), и в этом случае он добавит старый экземпляр FragmentOne.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.