Життєвий цикл фрагмента Android через зміни орієнтації


120

Використання пакета сумісності для націлювання на 2.2 за допомогою фрагментів.

Після перекодування діяльності для використання фрагментів у додатку я не зміг змінити орієнтацію / управління станом, тому я створив невеликий тестовий додаток з одним FragmentActivity та одним Fragment.

Журнали із зміни орієнтації дивні, із безліччю викликів до фрагментів 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, фрагмент, "MYTAG") або, якщо цього не вдалося, замінити (R.id.fragment_container, фрагмент, "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
}

Але будьте попереджені: проблеми виникнуть, якщо ви спробуєте отримати доступ до переглядів активності всередині фрагмента, оскільки життєві цикли будуть помітно змінюватися. (Отримати перегляди батьківської діяльності з фрагмента непросто).


54
"Це велика біль у тилу більшу частину часу" (пальці вгору)
rushinge

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
Кір - Ні, Діяльність дійсно знищена, Фрагменти, які вона містить, посилаються в FragmentManager, а не лише з Діяльності, тому він залишається і читається.
Graeme

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

87

Цитуючи цю книгу , "для забезпечення послідовного користувацького досвіду Android зберігає макет фрагмента та пов'язаний з ним задній стек, коли активність перезапускається через зміну конфігурації". (стор. 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
Ви, мабуть, врятували мене багато часу з цим ... велике спасибі. Ви можете комбінувати цю відповідь з відповіддю від Graeme, щоб отримати ідеальне рішення для обробки змін та фрагментів конфігурації.
azpublic

10
Це насправді правильна відповідь, а не позначена. Велике спасибі!
Уріель Франкель

як можна обробляти той самий сценарій у разі реалізації фрагмента ViewPager.
CoDe

Цей маленький дорогоцінний камінь допоміг проблемі, яку я розглядав кілька днів. Дякую! Це, безумовно, рішення.
Хто

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

10

Метод onCreate () вашої діяльності викликається після зміни орієнтації, як ви вже бачили. Отже, не виконайте FragmentTransaction, який додає фрагмент після зміни орієнтації у вашій діяльності.

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

Фрагменти повинні і повинні бути незмінними.


Ми знаємо, що екземпляр буде збережений після створення та додавання фрагмента? Я маю на увазі шапку, якщо користувач обертається безпосередньо перед тим, як додавати фрагмент? У нас все ще буде ненулевий збереженийInstanceState, який не містить стану фрагмента
Farid

4

Ви можете @Overrideвикористовувати FragmentActivity, використовуючиonSaveInstanceState() . Будь ласка, не забудьте не викликати super.onSaveInstanceState()в методі.


2
Це, швидше за все, порушить життєвий цикл діяльності, вводячи більше потенційних проблем у цей уже досить брудний процес. Подивіться у вихідний код FragmentActivity: він зберігає стани всіх фрагментів там.
Брайан

У мене виникла проблема, що у мене є різний перехідник для різної орієнтації. Тому у мене завжди була дивна ситуація після перевертання пристрою та перегортання деяких сторінок, я отримав стару та неправильну. при повороті збереженогоInstance найкраще працює без витоків пам’яті (я використовував setSavedEnabled (помилково) befor і закінчувався великими витоками пам'яті при кожній зміні орієнтації)
Informatic0re

0

Ми завжди повинні намагатися запобігати виключенню nullpointer, тому ми повинні спочатку перевірити метод збереження речовини для інформації про пакет. для короткого пояснення, щоб перевірити це посилання на блог

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

Після зміни конфігурації фреймворк створить для вас новий екземпляр фрагмента та додасть його до активності. Тож замість цього:

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.


-1

Якщо ви просто робите проект, то менеджер проекту каже, що вам потрібно досягти екрана функції перемикання, але ви не хочете, щоб екран перемикання завантажував інший макет (можна створити макет і макет-порт-систему.

Ви автоматично визначатимете стан екрана, завантажуєте відповідний макет), через необхідність повторної ініціалізації активності чи фрагменту, досвід роботи з користувачем не є хорошим, не безпосередньо на екрані перемикача, на який я посилаюся? 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 в атрибуті Activity: android: configChanges = "Клавіатура прихована | орієнтація | 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);
    }
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.