Android-Fragment-Lebenszyklus über Orientierungsänderungen

120

Verwenden des Kompatibilitätspakets für das Ziel 2.2 mithilfe von Fragmenten.

Nach dem Neukodieren einer Aktivität zur Verwendung von Fragmenten in einer App konnte ich die Orientierungsänderungen / Statusverwaltung nicht zum Laufen bringen. Daher habe ich eine kleine Test-App mit einer einzelnen FragmentActivity und einem einzelnen Fragment erstellt.

Die Protokolle der Orientierungsänderungen sind seltsam, mit mehreren Aufrufen der Fragmente OnCreateView.

Mir fehlt offensichtlich etwas - wie das Entfernen des Fragments und das erneute Anhängen, anstatt eine neue Instanz zu erstellen, aber ich kann keine Dokumentation sehen, die darauf hinweist, wo ich falsch liege.

Kann jemand etwas Licht ins Dunkel bringen, was ich hier falsch mache? Vielen Dank

Das Protokoll sieht nach Orientierungsänderungen wie folgt aus.

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

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

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

Manifest

<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
quelle
Ich weiß nicht, ob es eine richtige Antwort ist, aber versuchen Sie, beim Hinzufügen des Fragments ein Tag zu verwenden, fügen Sie (R.id.fragment_container, Fragment, "MYTAG") hinzu oder ersetzen Sie (R.id.fragment_container, Fragment, "MYTAG" ")
Jason
2
Einige Untersuchungen durchführen. Wenn die Hauptaktivität (FragmentTestActivity) bei einer Orientierungsänderung neu gestartet wird und ich eine neue Instanz von FragmentManager erhalte, führen Sie einen FindFragmentByTag durch, um das noch vorhandene Fragment zu lokalisieren, sodass das Fragment während der Neuerstellung der Hauptaktivität beibehalten wird. Wenn ich das Fragment finde und nichts tue, wird es trotzdem mit der MainActivity erneut angezeigt.
MartinS

Antworten:

189

Sie legen Ihre Fragmente übereinander.

Wenn eine Konfigurationsänderung auftritt, fügt sich das alte Fragment der neuen Aktivität hinzu, wenn es neu erstellt wird. Dies ist die meiste Zeit ein massiver Schmerz im hinteren Bereich.

Sie können das Auftreten von Fehlern stoppen, indem Sie dasselbe Fragment verwenden, anstatt ein neues neu zu erstellen. Fügen Sie einfach diesen Code hinzu:

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
}

Seien Sie jedoch gewarnt: Probleme treten auf, wenn Sie versuchen, aus dem Fragment heraus auf Aktivitätsansichten zuzugreifen, da sich die Lebenszyklen geringfügig ändern. (Abrufen von Ansichten von einer übergeordneten Aktivität aus einem Fragment ist nicht einfach).

Graeme
quelle
54
"Dies ist die meiste Zeit ein massiver Schmerz im hinteren Bereich" (Daumen hoch)
Rushinge
1
Wie kann dasselbe Szenario bei Verwendung von ViewPage mit FragmentStatePagerAdapter behandelt werden?
CoDe
5
Gibt es eine ähnliche Behauptung in der offiziellen Dokumentation? Ist dies nicht ein Widerspruch zu dem, was in der Anleitung angegeben ist : "when the activity is destroyed, so are all fragments"? Seit "When the screen orientation changes, the system destroys and recreates the activity [...]".
Cyrus
4
Cyrus - Nein, die Aktivität wird tatsächlich zerstört. Die darin enthaltenen Fragmente werden im FragmentManager referenziert, nicht nur aus der Aktivität. Sie bleiben also erhalten und werden erneut gelesen.
Graeme
4
Das Protokollieren von Fragmenten der Methoden onCreate und onDestroy sowie ihres Hashcodes nach dem Auffinden in FragmentManager zeigt deutlich, dass das Fragment zerstört wurde. es wird einfach automatisch neu erstellt und wieder angebracht. Nur wenn Sie setRetainInstance (true) in Fragmente auf der Create-Methode setzen, wird es wirklich nicht zerstört
Lemao1981
87

Um dieses Buch zu zitieren : "Um eine konsistente Benutzererfahrung zu gewährleisten, behält Android das Fragment-Layout und den zugehörigen Backstack bei, wenn eine Aktivität aufgrund einer Konfigurationsänderung neu gestartet wird." (S. 124)

Um dies zu erreichen, müssen Sie zunächst prüfen, ob der Fragment-Backstack bereits gefüllt ist, und die neue Fragmentinstanz nur dann erstellen, wenn dies nicht der Fall ist:

@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
quelle
2
Sie haben mir wahrscheinlich viel Zeit damit gespart ... vielen Dank. Sie können diese Antwort mit der von Graeme kombinieren, um eine perfekte Lösung für Konfigurationsänderungen und Fragmente zu erhalten.
Azpublic
10
Dies ist eigentlich die richtige Antwort, nicht die markierte. Vielen Dank!
Uriel Frankel
Wie kann das gleiche Szenario bei der Implementierung von ViewPager Fragment behandelt werden?
CoDe
Dieses kleine Juwel half bei einem Problem, das ich mir seit mehreren Tagen angesehen hatte. Danke dir! Dies ist definitiv die Lösung.
Wen
1
@SharpEdge Wenn Sie mehrere Fragmente haben, sollten Sie ihnen beim Hinzufügen zum Container Tags geben und dann mFragmentManager.findFragmentByTag (anstelle von findFragmentById) verwenden, um Verweise auf sie zu erhalten. Auf diese Weise kennen Sie die Klasse jedes Fragments und können dies richtig gegossen
k29
10

Die onCreate () -Methode Ihrer Aktivität wird nach der Orientierungsänderung aufgerufen, wie Sie gesehen haben. Führen Sie daher nicht die FragmentTransaction aus, die das Fragment nach der Orientierungsänderung in Ihrer Aktivität hinzufügt.

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

Die Fragmente sollten und müssen unverändert bleiben.

Αλέκος
quelle
Wissen wir, dass die Instanz gespeichert wird, nachdem das Fragment erstellt und hinzugefügt wurde? Ich meine, wenn sich ein Benutzer dreht, kurz bevor Fragment hinzugefügt wird? Wir werden immer noch nicht null savedInstanceState haben, das keinen Fragmentstatus enthält
Farid
4

Sie können @Overridedie FragmentActivity mit onSaveInstanceState(). Bitte rufen Sie die super.onSaveInstanceState()in der Methode nicht auf.

Victor.Chan
quelle
2
Dies würde höchstwahrscheinlich den Lebenszyklus der Aktivitäten unterbrechen und mehr potenzielle Probleme in diesem bereits recht chaotischen Prozess verursachen. Schauen Sie sich den Quellcode von FragmentActivity an: Er speichert dort den Status aller Fragmente.
Brian
Ich hatte das Problem, dass ich unterschiedliche Adapter für unterschiedliche Ausrichtung habe. Also hatte ich immer eine seltsame Situation, nachdem ich das Gerät umgedreht und einige Seiten gewischt hatte. Ich habe die alte und die falsche bekommen. Mit dem Ausschalten der gespeicherten Instanz funktioniert es am besten ohne Speicherlecks (ich habe vorher setSavedEnabled (false) verwendet und bei jeder Orientierungsänderung große Speicherlecks festgestellt)
Informatic0re
0

Wir sollten immer versuchen, eine Nullzeiger-Ausnahme zu verhindern, daher müssen wir zuerst in der saveinstance-Methode nach Bundle-Informationen suchen. Für eine kurze Erklärung überprüfen Sie diesen Blog- Link

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
quelle
0

Bei einer Konfigurationsänderung erstellt das Framework eine neue Instanz des Fragments für Sie und fügt es der Aktivität hinzu. Also stattdessen:

FragmentOne fragment = new FragmentOne();

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

mach das:

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

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

Beachten Sie, dass das Framework bei einer Änderung der Ausrichtung eine neue Instanz von FragmentOne hinzufügt, es sei denn, Sie rufen setRetainInstance (true) auf. In diesem Fall wird die alte Instanz von FragmentOne hinzugefügt.

vlazzle
quelle
-1

Wenn Sie nur ein Projekt ausführen, sagt der Projektmanager, dass Sie einen Schaltfunktionsbildschirm erreichen müssen, aber nicht möchten, dass das Umschalten unterschiedliche Layouts lädt (kann Layout und Layout-Port-System erstellen.

Sie bestimmen automatisch den Bildschirmstatus, laden das entsprechende Layout), da die Aktivität oder das Fragment neu initialisiert werden muss, ist die Benutzererfahrung nicht gut, nicht direkt auf dem Bildschirmschalter, auf den ich mich beziehe? URL = YgNfP-vHy-Nuldi7YHTfNet3AtLdN-w__O3z1wLOnzr3wDjYo7X7PYdNyhw8R24ZE22xiKnydni7R0r35s2fOLcHOiLGYT9Qh_fjqtyt110 =

Die Voraussetzung ist, dass Ihr Layout das Gewicht des Layouts des layout_weight wie folgt verwendet:

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

Mein Ansatz ist es also, beim Umschalten des Bildschirms kein neues Layout der Ansichtsdatei zu laden und das Layout in den dynamischen Gewichten von onConfigurationChanged zu ändern. Führen Sie dazu die folgenden Schritte aus: 1 Erster Satz: AndroidManifest.xml im Aktivitätsattribut: android: configChanges = "keyboardHidden | orientierung | screenSize" Um ein Umschalten des Bildschirms zu verhindern, vermeiden Sie ein erneutes Laden, um in onConfigurationChanged 2 die Aktivität oder das Fragment in der onConfigurationChanged-Methode neu zu schreiben.

@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
quelle