Ciclo de vida de fragmentos de Android sobre cambios de orientación

120

Usando el paquete de compatibilidad para apuntar a 2.2 usando Fragments.

Después de recodificar una actividad para usar fragmentos en una aplicación, no pude hacer que funcionaran los cambios de orientación / gestión de estado, así que creé una pequeña aplicación de prueba con un solo FragmentActivity y un solo Fragment.

Los registros de los cambios de orientación son extraños, con múltiples llamadas a los fragmentos OnCreateView.

Obviamente, me falta algo, como separar el fragmento y volver a adjuntarlo en lugar de crear una nueva instancia, pero no puedo ver ninguna documentación que indique dónde me estoy equivocando.

¿Alguien puede arrojar algo de luz sobre lo que estoy haciendo mal aquí, por favor? Gracias

El registro es el siguiente después de los cambios de orientación.

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

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

Y el fragmento

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

Manifiesto

<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
fuente
No sé si es una respuesta adecuada, pero intente usar una etiqueta cuando agregue el fragmento, agregue (R.id.fragment_container, fragment, "MYTAG"), o en su defecto, reemplace (R.id.fragment_container, fragment, "MYTAG ")
Jason
2
Haciendo algunas investigaciones. Cuando la actividad principal (FragmentTestActivity) se reinicia en el cambio de orientación y obtengo una nueva instancia de FragmentManager, luego realizo un FindFragmentByTag para ubicar el fragmento que todavía existe, por lo que el fragmento se retiene durante la recreación de la actividad principal. Si encuentro el fragmento y no hago nada, se vuelve a mostrar con MainActivity de todos modos.
MartinS

Respuestas:

189

Estás superponiendo tus Fragmentos uno encima del otro.

Cuando se produce un cambio de configuración, el fragmento antiguo se agrega a la nueva actividad cuando se vuelve a crear. Este es un gran dolor en el trasero la mayor parte del tiempo.

Puede evitar que se produzcan errores utilizando el mismo fragmento en lugar de volver a crear uno nuevo. Simplemente agregue este código:

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
}

Sin embargo, tenga cuidado: se producirán problemas si intenta acceder a las Vistas de actividad desde el interior del Fragmento, ya que los ciclos de vida cambiarán sutilmente. (Obtener vistas de una actividad principal a partir de un fragmento no es fácil).

Graeme
fuente
54
"Este es un dolor enorme en la parte trasera la mayor parte del tiempo" (pulgar hacia arriba)
rushinge
1
¿Cómo se puede manejar el mismo escenario en caso de uso de ViewPage con FragmentStatePagerAdapter ... alguna sugerencia?
Código
5
¿Existe una afirmación similar en la documentación oficial? ¿No es esto una contradicción con lo que se indica en la guía "when the activity is destroyed, so are all fragments":? Desde que "When the screen orientation changes, the system destroys and recreates the activity [...]".
cyrus
4
Cyrus: no, la actividad se destruye de hecho, los fragmentos que contiene se referencian en el administrador de fragmentos, no solo de la actividad, por lo que permanece y se lee.
Graeme
4
el registro de fragmentos en los métodos onCreate y onDestroy, así como su código hash después de encontrarlo en FragmentManager, muestra claramente que el fragmento ESTÁ destruido. simplemente se vuelve a crear y se vuelve a unir automáticamente. solo si pones setRetainInstance (verdadero) en fragmentos del método onCreate, realmente no se destruirá
Lemao1981
87

Para citar este libro , "para garantizar una experiencia de usuario coherente, Android conserva el diseño de Fragmento y la pila de actividades asociada cuando se reinicia una actividad debido a un cambio de configuración". (pág.124)

Y la forma de abordar eso es verificar primero si la pila posterior de Fragmento ya se ha poblado y crear la nueva instancia de fragmento solo si no lo ha hecho:

@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
fuente
2
Probablemente me ahorraste mucho tiempo con este ... muchas gracias. Puede combinar esta respuesta con la de Graeme para obtener una solución perfecta para manejar cambios de configuración y fragmentos.
azpublic
10
En realidad, esta es la respuesta correcta, no la marcada. ¡Muchas gracias!
Uriel Frankel
¿Cómo se puede manejar el mismo escenario en el caso de la implementación de ViewPager Fragment?
Código
Esta pequeña joya ayudó en un problema que había estado analizando durante varios días. ¡Gracias! Definitivamente esta es la solución.
Quién
1
@SharpEdge Si tiene varios fragmentos, debe asignarles etiquetas al agregarlos al contenedor y luego usar mFragmentManager.findFragmentByTag (en lugar de findFragmentById) para obtener referencias a ellos; de esta manera, sabrá la clase de cada fragmento y podrá lanzar correctamente
k29
10

El método onCreate () de su actividad se llama después del cambio de orientación como ha visto. Por lo tanto, no ejecute FragmentTransaction que agrega el Fragment después del cambio de orientación en su actividad.

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

Los Fragmentos deben y deben permanecer sin cambios.

Αλέκος
fuente
¿Sabemos que la instancia se guardará después de que se cree y se agregue el fragmento? Quiero decir, ¿qué pasa si un usuario gira justo antes de que se agregue Fragmento? Seguiremos teniendo SavedInstanceState no nulo que no contiene el estado del fragmento
Farid
4

Puede @Overrideutilizar FragmentActivity onSaveInstanceState(). Asegúrese de no llamar super.onSaveInstanceState()al método.

Victor.Chan
fuente
2
Lo más probable es que esto rompa el ciclo de vida de las actividades y presente más problemas potenciales en este proceso ya bastante complicado. Mire el código fuente de FragmentActivity: está guardando los estados de todos los fragmentos allí.
Brian
Tuve el problema de que tengo un número de adaptadores diferente para diferentes orientaciones. Así que siempre tuve una situación extraña después de encender el dispositivo y deslizar algunas páginas, obtuve la anterior y la incorrecta. con el giro de la instancia guardada, funciona mejor sin pérdidas de memoria (utilicé setSavedEnabled (falso) antes y terminé con grandes pérdidas de memoria en cada cambio de orientación)
Informatic0re
0

Siempre debemos intentar evitar la excepción de puntero nulo, por lo que primero debemos verificar en el método saveinstance la información del paquete. para una breve explicación, consulte el enlace de este 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();
        }
    } 
}
abhi
fuente
0

Si solo hace un proyecto, entonces el gerente de proyecto dice que necesita lograr la función de cambio de pantalla, pero no desea cambiar la pantalla y cargar un diseño diferente (puede crear un diseño y un sistema de puerto de diseño.

Determinará automáticamente el estado de la pantalla, cargará el diseño correspondiente), debido a la necesidad de reiniciar la actividad o el fragmento, la experiencia del usuario no es buena, no directamente en el interruptor de pantalla, ¿me refiero? Url = YgNfP-vHy-Nuldi7YHTfNet3AtLdN-w__O3z1wLOnzr3wDjYo7X7PYdNyhw8R24ZE22xiKnydni7R0r35s2fOLcHOiLGYT9Qh_fjqtyfolcHOiLGYT9Qh_fjqtyt & e19q45 & wd85a85

La premisa es que su diseño use el peso de la forma en que el diseño del layout_weight, de la siguiente manera:

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

Entonces, mi enfoque es, cuando se cambia de pantalla, no es necesario cargar un nuevo diseño del archivo de vista, modificar el diseño en los pesos dinámicos onConfigurationChanged, los siguientes pasos: 1 primer conjunto: AndroidManifest.xml en el atributo de actividad: android: configChanges = "keyboardHidden | Orientación | screenSize" Para evitar el cambio de pantalla, evite volver a cargar, para poder monitorear en onConfigurationChanged 2 la actividad de reescritura o fragmento en el método 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
fuente
0

Al cambiar la configuración, el marco creará una nueva instancia del fragmento para usted y la agregará a la actividad. Entonces en lugar de esto:

FragmentOne fragment = new FragmentOne();

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

hacer esto:

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

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

Tenga en cuenta que el marco agrega una nueva instancia de FragmentOne en el cambio de orientación a menos que llame a setRetainInstance (true), en cuyo caso agregará la instancia anterior de FragmentOne.

vlazzle
fuente