Cykl życia fragmentu systemu Android w przypadku zmian orientacji

120

Użycie pakietu zgodności do celu 2.2 przy użyciu fragmentów.

Po przekodowaniu działania w celu użycia fragmentów w aplikacji nie mogłem uzyskać działania zmiany orientacji / zarządzania stanem, więc utworzyłem małą aplikację testową z pojedynczym FragmentActivity i jednym fragmentem.

Dzienniki ze zmian orientacji są dziwne, z wieloma wywołaniami do fragmentów OnCreateView.

Oczywiście czegoś mi brakuje - na przykład odłączam fragment i podłączam go ponownie zamiast tworzyć nową instancję, ale nie widzę żadnej dokumentacji, która wskazywałaby, gdzie idę źle.

Czy ktoś może rzucić trochę światła na to, co tutaj robię źle. Dzięki

Dziennik wygląda następująco po zmianie orientacji.

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

Główna aktywność (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();
}

I fragment

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());
}

Oczywisty

<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>
MartinS
źródło
Nie wiem, czy to poprawna odpowiedź, ale spróbuj użyć tagu podczas dodawania fragmentu, dodaj (R.id.fragment_container, fragment, "MYTAG"), lub jeśli to się nie uda, zamień (R.id.fragment_container, fragment, "MYTAG ")
Jason
2
Robię śledztwo. Gdy działanie główne (FragmentTestActivity) zostanie uruchomione ponownie po zmianie orientacji i otrzymam nowe wystąpienie FragmentManager, a następnie wykonaj polecenie FindFragmentByTag, aby zlokalizować fragment, który nadal istnieje, więc fragment jest zachowywany podczas odtwarzania głównego działania. Jeśli znajdę fragment i nic nie zrobię, i tak zostanie ponownie wyświetlony z MainActivity.
MartinS,

Odpowiedzi:

189

Nakładasz swoje Fragmenty jeden na drugi.

W przypadku zmiany konfiguracji stary fragment dodaje się do nowego działania po jego odtworzeniu. Przez większość czasu jest to ogromny ból z tyłu.

Możesz zatrzymać występujące błędy, używając tego samego fragmentu, zamiast tworzyć nowy. Po prostu dodaj ten kod:

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
}

Ostrzegamy jednak: jeśli spróbujesz uzyskać dostęp do widoków aktywności z wnętrza fragmentu, wystąpią problemy, ponieważ cykle życia ulegną subtelnej zmianie. (Pobieranie widoków z działania nadrzędnego z fragmentu nie jest łatwe).

Graeme
źródło
54
„To jest ogromny ból z tyłu przez większość czasu” (kciuki do góry)
gorączka
1
Jak można obsłużyć ten sam scenariusz w przypadku użycia ViewPage z FragmentStatePagerAdapter ... jakąkolwiek sugestię?
CoDe
5
Czy istnieje podobne stwierdzenie w oficjalnej dokumentacji? Czy nie jest to sprzeczność z tym, co jest napisane w przewodniku "when the activity is destroyed, so are all fragments":? Od "When the screen orientation changes, the system destroys and recreates the activity [...]".
cYrus
4
Cyrus - nie, działanie jest rzeczywiście zniszczone, fragmenty, które zawiera, są przywoływane w module FragmentManager, a nie tylko z działania, więc pozostaje i jest odczytywane.
Graeme
4
rejestrowanie fragmentów metod onCreate i onDestroy oraz jego hashcode po znalezieniu w FragmentManager wyraźnie pokazuje, że fragment JEST zniszczony. po prostu jest automatycznie odtwarzany i podłączany. tylko jeśli umieścisz setRetainInstance (true) we fragmentach w metodzie naCreate, to naprawdę nie zostanie zniszczone
Lemao1981
87

Cytując tę książkę , „aby zapewnić spójne wrażenia użytkownika, system Android zachowuje układ fragmentów i powiązany stos tylny po ponownym uruchomieniu działania z powodu zmiany konfiguracji”. (s. 124)

Sposobem na podejście jest najpierw sprawdzenie, czy tylny stos fragmentów został już zapełniony i utworzenie nowej instancji fragmentu tylko wtedy, gdy nie:

@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();
    }
}
k29
źródło
2
Prawdopodobnie zaoszczędziłeś mi dużo czasu dzięki temu ... wielkie dzięki. Możesz połączyć tę odpowiedź z odpowiedzią od Graeme, aby uzyskać idealne rozwiązanie do obsługi zmian konfiguracji i fragmentów.
azpublic,
10
To jest właściwie prawidłowa odpowiedź, a nie zaznaczona. Dziękuję Ci bardzo!
Uriel Frankel
jak można obsłużyć ten sam scenariusz w przypadku implementacji fragmentu ViewPager.
CoDe
Ten mały klejnot pomógł w rozwiązaniu problemu, na który patrzyłem od kilku dni. Dziękuję Ci! To jest zdecydowanie rozwiązanie.
Kogo
1
@SharpEdge Jeśli masz wiele fragmentów, powinieneś nadać im tagi podczas dodawania do kontenera, a następnie użyć mFragmentManager.findFragmentByTag (zamiast findFragmentById), aby uzyskać odniesienia do nich - w ten sposób poznasz klasę każdego fragmentu i będziesz mógł obsada poprawnie
k29
10

Metoda onCreate () twojego działania jest wywoływana po zmianie orientacji, jak widzieliśmy. Dlatego nie wykonuj FragmentTransaction, który dodaje Fragment po zmianie orientacji w Twojej aktywności.

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

Fragmenty powinny i muszą pozostać niezmienione.

Αλέκος
źródło
Czy wiemy, że instancja zostanie zapisana po utworzeniu i dodaniu fragmentu? Mam na myśli kapelusz, jeśli użytkownik obraca się tuż przed dodaniem fragmentu? Nadal będziemy mieć zapisaną InstanceState o wartości innej niż null, która nie zawiera stanu fragmentu
Farid
4

Możesz użyć @OverrideFragmentActivity za pomocą onSaveInstanceState(). Pamiętaj, aby nie wywoływać super.onSaveInstanceState()metody w metodzie.

Victor.Chan
źródło
2
Najprawdopodobniej przerwałoby to cykl życia działań, wprowadzając więcej potencjalnych problemów w tym już dość niechlujnym procesie. Zajrzyj do kodu źródłowego FragmentActivity: zapisuje stany wszystkich tam zawartych fragmentów.
Brian,
Miałem problem, że mam różną liczbę adapterów dla różnych orientacji. Więc zawsze miałem dziwną sytuację po włączeniu urządzenia i przewinięciu niektórych stron, mam stary i zły. z włączeniem zapisanej instancji działa najlepiej bez wycieków pamięci (użyłem setSavedEnabled (false) przed i skończyło się to dużymi wyciekami pamięci przy każdej zmianie orientacji)
Informatic0re
0

Powinniśmy zawsze unikać wyjątku nullpointer, więc najpierw musimy sprawdzić w metodzie saveinstance informacje o pakunku. aby uzyskać krótkie wyjaśnienie, sprawdź ten link do bloga

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();
        }
    } 
}
abhi
źródło
0

Jeśli robisz tylko projekt, kierownik projektu mówi, że musisz uzyskać ekran funkcji przełączania, ale nie chcesz, aby przełączanie ekranu ładowało inny układ (można utworzyć układ i układ układu-portu.

Automatycznie określisz stan ekranu, załadujesz odpowiedni układ), ze względu na konieczność ponownej inicjalizacji działania lub fragmentu wrażenia użytkownika nie są dobre, a nie bezpośrednio na przełączniku ekranu, do którego odsyłam? Url = YgNfP-vHy-Nuldi7YHTfNet3AtLdN-w__O3z1wLOnzr3wDjYo7X7PYdNyhw8R24ZE22xiKnydni7R0r35s2fOLcHOiLGYT9Qh_fjqtytJki7X7PYdNyhw8R24ZE22xiKnydni7R0r35s2fOLcHOiLGYT9Qh_fjqtytJki7X7PYdNyhw8R24ZE22xiKnydni7R0r35s2fOLcHOiLGYT9Qh_fjqtytJki000082

Założeniem jest, że twój układ używa wagi sposobu układu z layout_weight, w następujący sposób:

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

Więc moje podejście jest takie, że podczas przełączania ekranu nie trzeba ładować nowego układu pliku widoku, modyfikować układu w onConfigurationChanged dynamicznych wagach, następujące kroki: 1 pierwszy zestaw: AndroidManifest.xml w atrybucie aktywności: android: configChanges = "keyboardHidden | Orientacja | screenSize" Aby zapobiec przełączaniu ekranu, unikaj ponownego ładowania, aby móc monitorować czynność przepisywania onConfigurationChanged 2 lub fragment w metodzie 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);
    }
}
nihaoqiulinhe
źródło
0

Po zmianie konfiguracji framework utworzy dla ciebie nową instancję fragmentu i doda ją do działania. Więc zamiast tego:

FragmentOne fragment = new FragmentOne();

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

Zrób to:

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

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

Zwróć uwagę, że framework dodaje nowe wystąpienie FragmentOne przy zmianie orientacji, chyba że wywołasz setRetainInstance (true), w którym to przypadku doda stare wystąpienie FragmentOne.

vlazzle
źródło