Ciclo di vita dei frammenti di Android rispetto ai cambiamenti di orientamento


120

Utilizzo del pacchetto di compatibilità per target 2.2 utilizzando Fragments.

Dopo aver ricodificato un'attività per utilizzare i frammenti in un'app, non sono riuscito a far funzionare le modifiche dell'orientamento / la gestione dello stato, quindi ho creato una piccola app di test con un singolo FragmentActivity e un singolo Fragment.

I registri delle modifiche all'orientamento sono strani, con più chiamate ai frammenti OnCreateView.

Ovviamente mi manca qualcosa, come staccare il frammento e ricollegarlo piuttosto che creare una nuova istanza, ma non riesco a vedere alcuna documentazione che indichi dove sto sbagliando.

Qualcuno può far luce su quello che sto facendo di sbagliato qui, per favore. Grazie

Il registro è il seguente dopo i cambiamenti di orientamento.

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

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

E il frammento

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

Manifesto

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

Non so se è una risposta corretta, ma prova a utilizzare un tag quando aggiungi il frammento, aggiungi (R.id.fragment_container, fragment, "MYTAG") o, in caso contrario, sostituisci (R.id.fragment_container, fragment, "MYTAG ")
Jason

2
Facendo alcune indagini. Quando l'attività principale (FragmentTestActivity) si riavvia al cambio di orientamento e ottengo una nuova istanza di FragmentManager, quindi eseguo un FindFragmentByTag per individuare il frammento che esiste ancora, quindi il frammento viene conservato durante la ricreazione dell'attività principale. Se trovo il frammento e non faccio nulla, viene comunque visualizzato nuovamente con MainActivity.
MartinS

Risposte:


189

Stai sovrapponendo i tuoi frammenti uno sopra l'altro.

Quando si verifica una modifica della configurazione, il vecchio frammento si aggiunge alla nuova attività quando viene ricreato. Questo è un enorme dolore alla schiena per la maggior parte del tempo.

È possibile impedire che si verifichino errori utilizzando lo stesso frammento anziché ricrearne uno nuovo. Aggiungi semplicemente questo codice:

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
}

Attenzione però: si verificheranno problemi se provi ad accedere alle visualizzazioni attività dall'interno del frammento poiché i cicli di vita cambieranno leggermente. (Ottenere viste da un'attività genitore da un frammento non è facile).


54
"Questo è un enorme dolore alla schiena per la maggior parte del tempo" (pollice in alto)
precipitoso

1
Come si può gestire lo stesso scenario in caso di utilizzo di ViewPage con FragmentStatePagerAdapter ... qualche suggerimento?
CoDe

5
C'è un'affermazione simile nella documentazione ufficiale? Non è questo in contraddizione con quanto affermato nella guida "when the activity is destroyed, so are all fragments":? Da allora "When the screen orientation changes, the system destroys and recreates the activity [...]".
cYrus

4
Cyrus - No, l'attività è effettivamente distrutta, i frammenti che contiene sono referenziati nel FragmentManager, non solo dall'attività, quindi rimangono e vengono riaggiunti.
Graeme

4
la registrazione dei frammenti sui metodi onCreate e onDestroy così come il suo codice hash dopo aver trovato in FragmentManager mostra chiaramente che il frammento È distrutto. viene semplicemente ricreato e ricollegato automaticamente. solo se metti setRetainInstance (true) in frammenti sul metodo Create non verrà davvero distrutto
Lemao1981

87

Per citare questo libro , "per garantire un'esperienza utente coerente, Android mantiene il layout Fragment e il back stack associato quando un'attività viene riavviata a causa di una modifica della configurazione". (p. 124)

E il modo per approcciarlo è controllare prima se lo stack di frammento è già stato popolato e creare la nuova istanza di frammento solo se non lo è:

@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
Probabilmente mi hai risparmiato un sacco di tempo con questo ... grazie mille. Puoi combinare questa risposta con quella di Graeme per ottenere una soluzione perfetta per gestire modifiche e frammenti di configurazione.
azpublic

10
Questa è effettivamente la risposta giusta, non quella contrassegnata. Grazie mille!
Uriel Frankel

come può gestire lo stesso scenario in caso di implementazione di ViewPager Fragment.
CoDe

Questo piccolo gioiello ha aiutato a risolvere un problema che stavo osservando da diversi giorni. Grazie! Questa è sicuramente la soluzione.
Chi

1
@SharpEdge Se hai più frammenti, dovresti dare loro dei tag quando li aggiungi al contenitore, quindi usa mFragmentManager.findFragmentByTag (invece di findFragmentById) per ottenere riferimenti ad essi - in questo modo conoscerai la classe di ogni frammento e sarai in grado di cast correttamente
k29

10

Il metodo onCreate () della tua attività viene chiamato dopo il cambio di orientamento, come hai visto. Quindi, non eseguire FragmentTransaction che aggiunge il frammento dopo il cambio di orientamento nella tua attività.

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

I frammenti devono e devono essere invariati.


Sappiamo che l'istanza verrà salvata dopo la creazione e l'aggiunta del frammento? Voglio dire, cosa succede se un utente ruota appena prima che venga aggiunto il frammento? Avremo ancora salvatoInstanceState non nullo che non contiene lo stato del frammento
Farid

4

Puoi @Overrideusare FragmentActivity onSaveInstanceState(). Assicurati di non chiamare super.onSaveInstanceState()nel metodo.


2
Ciò molto probabilmente interromperà il ciclo di vita delle attività introducendo più potenziali problemi in questo processo già piuttosto disordinato. Guarda nel codice sorgente di FragmentActivity: sta salvando gli stati di tutti i frammenti lì.
Brian

Ho avuto il problema di avere un numero di adattatori diverso per orientamento diverso. Quindi ho sempre avuto una situazione strana dopo aver acceso il dispositivo e fatto scorrere alcune pagine, ho ottenuto quella vecchia e sbagliata. con la rotazione del savedInstance funziona meglio senza perdite di memoria (ho usato setSavedEnabled (false) prima e ho finito con grandi perdite di memoria ad ogni cambio di orientamento)
Informatic0re

0

Dovremmo sempre cercare di prevenire l'eccezione nullpointer, quindi dobbiamo prima controllare nel metodo saveinstance le informazioni sul bundle. per una breve spiegazione controllare questo collegamento al blog

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

Se fai solo un progetto, il project manager dice che devi ottenere la schermata della funzione di commutazione, ma non vuoi che la commutazione dello schermo carichi un layout diverso (può creare layout e sistema di porta layout.

Determinerai automaticamente lo stato dello schermo, caricherai il layout corrispondente), a causa della necessità di reinizializzare l'attività o il frammento, l'esperienza dell'utente non è buona, non direttamente sullo switch dello schermo, mi riferisco? Url = YgNfP-VHY-Nuldi7YHTfNet3AtLdN-w__O3z1wLOnzr3wDjYo7X7PYdNyhw8R24ZE22xiKnydni7R0r35s2fOLcHOiLGYT9Qh_fjqtytJki & WD = & eqid = f258719e0001f24000000004585a1082

La premessa è che il tuo layout utilizzi il peso del modo in cui il layout di layout_weight, come segue:

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

Quindi il mio approccio è, quando si cambia schermata, non è necessario caricare un nuovo layout del file di visualizzazione, modificare il layout in pesi dinamici onConfigurationChanged, i seguenti passaggi: 1 primo set: AndroidManifest.xml nell'attributo di attività: android: configChanges = "keyboardHidden | Orientamento | ScreenSize" Per impedire il cambio di schermata, evitare di ricaricare, in modo da poter monitorare in onConfigurationChanged 2 attività di riscrittura o frammento nel metodo 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

Alla modifica della configurazione, il framework creerà una nuova istanza del frammento e la aggiungerà all'attività. Quindi, invece di questo:

FragmentOne fragment = new FragmentOne();

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

Fai questo:

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

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

Si noti che il framework aggiunge una nuova istanza di FragmentOne al cambio di orientamento a meno che non si chiami setRetainInstance (true), nel qual caso verrà aggiunta la vecchia istanza di FragmentOne.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.