Fragmento onCreateView y onActivityCreated llamado dos veces

Estoy desarrollando una aplicación con Android 4.0 ICS y fragmentos.

Considere este ejemplo modificado de la aplicación de ejemplo de demostración de ICS 4.0.3 (API nivel 15):

public class FragmentTabs extends Activity { private static final String TAG = FragmentTabs.class.getSimpleName(); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); final ActionBar bar = getActionBar(); bar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS); bar.setDisplayOptions(0, ActionBar.DISPLAY_SHOW_TITLE); bar.addTab(bar.newTab() .setText("Simple") .setTabListener(new TabListener( this, "mysimple", SimpleFragment.class))); if (savedInstanceState != null) { bar.setSelectedNavigationItem(savedInstanceState.getInt("tab", 0)); Log.d(TAG, "FragmentTabs.onCreate tab: " + savedInstanceState.getInt("tab")); Log.d(TAG, "FragmentTabs.onCreate number: " + savedInstanceState.getInt("number")); } } @Override protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); outState.putInt("tab", getActionBar().getSelectedNavigationIndex()); } public static class TabListener implements ActionBar.TabListener { private final Activity mActivity; private final String mTag; private final Class mClass; private final Bundle mArgs; private Fragment mFragment; public TabListener(Activity activity, String tag, Class clz) { this(activity, tag, clz, null); } public TabListener(Activity activity, String tag, Class clz, Bundle args) { mActivity = activity; mTag = tag; mClass = clz; mArgs = args; // Check to see if we already have a fragment for this tab, probably // from a previously saved state. If so, deactivate it, because our // initial state is that a tab isn't shown. mFragment = mActivity.getFragmentManager().findFragmentByTag(mTag); if (mFragment != null && !mFragment.isDetached()) { Log.d(TAG, "constructor: detaching fragment " + mTag); FragmentTransaction ft = mActivity.getFragmentManager().beginTransaction(); ft.detach(mFragment); ft.commit(); } } public void onTabSelected(Tab tab, FragmentTransaction ft) { if (mFragment == null) { mFragment = Fragment.instantiate(mActivity, mClass.getName(), mArgs); Log.d(TAG, "onTabSelected adding fragment " + mTag); ft.add(android.R.id.content, mFragment, mTag); } else { Log.d(TAG, "onTabSelected attaching fragment " + mTag); ft.attach(mFragment); } } public void onTabUnselected(Tab tab, FragmentTransaction ft) { if (mFragment != null) { Log.d(TAG, "onTabUnselected detaching fragment " + mTag); ft.detach(mFragment); } } public void onTabReselected(Tab tab, FragmentTransaction ft) { Toast.makeText(mActivity, "Reselected!", Toast.LENGTH_SHORT).show(); } } public static class SimpleFragment extends Fragment { TextView textView; int mNum; /** * When creating, retrieve this instance's number from its arguments. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Log.d(FragmentTabs.TAG, "onCreate " + (savedInstanceState != null ? ("state " + savedInstanceState.getInt("number")) : "no state")); if(savedInstanceState != null) { mNum = savedInstanceState.getInt("number"); } else { mNum = 25; } } @Override public void onActivityCreated(Bundle savedInstanceState) { Log.d(TAG, "onActivityCreated"); if(savedInstanceState != null) { Log.d(TAG, "saved variable number: " + savedInstanceState.getInt("number")); } super.onActivityCreated(savedInstanceState); } @Override public void onSaveInstanceState(Bundle outState) { Log.d(TAG, "onSaveInstanceState saving: " + mNum); outState.putInt("number", mNum); super.onSaveInstanceState(outState); } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { Log.d(FragmentTabs.TAG, "onCreateView " + (savedInstanceState != null ? ("state: " + savedInstanceState.getInt("number")) : "no state")); textView = new TextView(getActivity()); textView.setText("Hello world: " + mNum); textView.setBackgroundDrawable(getResources().getDrawable(android.R.drawable.gallery_thumb)); return textView; } } 

}

Aquí está la salida recuperada de ejecutar este ejemplo y luego girar el teléfono:

 06-11 11:31:42.559: D/FragmentTabs(10726): onTabSelected adding fragment mysimple 06-11 11:31:42.559: D/FragmentTabs(10726): onCreate no state 06-11 11:31:42.559: D/FragmentTabs(10726): onCreateView no state 06-11 11:31:42.567: D/FragmentTabs(10726): onActivityCreated 06-11 11:31:45.286: D/FragmentTabs(10726): onSaveInstanceState saving: 25 06-11 11:31:45.325: D/FragmentTabs(10726): onCreate state 25 06-11 11:31:45.340: D/FragmentTabs(10726): constructor: detaching fragment mysimple 06-11 11:31:45.340: D/FragmentTabs(10726): onTabSelected attaching fragment mysimple 06-11 11:31:45.348: D/FragmentTabs(10726): FragmentTabs.onCreate tab: 0 06-11 11:31:45.348: D/FragmentTabs(10726): FragmentTabs.onCreate number: 0 06-11 11:31:45.348: D/FragmentTabs(10726): onCreateView state: 25 06-11 11:31:45.348: D/FragmentTabs(10726): onActivityCreated 06-11 11:31:45.348: D/FragmentTabs(10726): saved variable number: 25 06-11 11:31:45.348: D/FragmentTabs(10726): onCreateView no state 06-11 11:31:45.348: D/FragmentTabs(10726): onActivityCreated 

Mi pregunta es, ¿por qué se invocan dos veces onCreateView y onActivityCreated? ¿La primera vez con un paquete con el estado guardado y la segunda vez con un null savedInstanceState?

Esto está causando problemas para conservar el estado del fragmento en rotación.

Me estuve rascando la cabeza acerca de esto por un tiempo también, y como la explicación de Dave es un poco difícil de entender, publicaré mi código (aparentemente operativo):

 private class TabListener implements ActionBar.TabListener { private Fragment mFragment; private Activity mActivity; private final String mTag; private final Class mClass; public TabListener(Activity activity, String tag, Class clz) { mActivity = activity; mTag = tag; mClass = clz; mFragment=mActivity.getFragmentManager().findFragmentByTag(mTag); } public void onTabSelected(Tab tab, FragmentTransaction ft) { if (mFragment == null) { mFragment = Fragment.instantiate(mActivity, mClass.getName()); ft.replace(android.R.id.content, mFragment, mTag); } else { if (mFragment.isDetached()) { ft.attach(mFragment); } } } public void onTabUnselected(Tab tab, FragmentTransaction ft) { if (mFragment != null) { ft.detach(mFragment); } } public void onTabReselected(Tab tab, FragmentTransaction ft) { } } 

Como puede ver, se parece mucho a la muestra de Android, además de no desconectarse en el constructor, y usar replace en lugar de add .

Después de mucho headscratching y prueba y error, encontré que encontrar el fragmento en el constructor parece hacer que el doble problema deCreateView desaparezca mágicamente (supongo que acaba siendo nulo paraTabSelected cuando se llama a través de la ruta ActionBar.setSelectedNavigationItem () cuando guardar / restaurar el estado).

Ok, esto es lo que descubrí.

Lo que no entendí es que todos los fragmentos que están conectados a una actividad cuando ocurre un cambio de configuración (el teléfono gira) se vuelven a crear y se agregan a la actividad. (lo cual tiene sentido)

Lo que sucedía en el constructor de TabListener era que la pestaña se separaba si se encontraba y se adjuntaba a la actividad. Vea abajo:

 mFragment = mActivity.getFragmentManager().findFragmentByTag(mTag); if (mFragment != null && !mFragment.isDetached()) { Log.d(TAG, "constructor: detaching fragment " + mTag); FragmentTransaction ft = mActivity.getFragmentManager().beginTransaction(); ft.detach(mFragment); ft.commit(); } 

Más adelante en la actividad onCreate, la pestaña seleccionada previamente se seleccionó del estado de la instancia guardada. Vea abajo:

 if (savedInstanceState != null) { bar.setSelectedNavigationItem(savedInstanceState.getInt("tab", 0)); Log.d(TAG, "FragmentTabs.onCreate tab: " + savedInstanceState.getInt("tab")); Log.d(TAG, "FragmentTabs.onCreate number: " + savedInstanceState.getInt("number")); } 

Cuando se seleccionó la pestaña, se volvería a conectar en la callback onTabSelected.

 public void onTabSelected(Tab tab, FragmentTransaction ft) { if (mFragment == null) { mFragment = Fragment.instantiate(mActivity, mClass.getName(), mArgs); Log.d(TAG, "onTabSelected adding fragment " + mTag); ft.add(android.R.id.content, mFragment, mTag); } else { Log.d(TAG, "onTabSelected attaching fragment " + mTag); ft.attach(mFragment); } } 

El fragmento adjunto es la segunda llamada a los métodos onCreateView y onActivityCreated. (La primera es cuando el sistema está recreando la actividad y todos los fragmentos adjuntos) La primera vez que el paquete onSavedInstanceState hubiera guardado datos pero no la segunda vez.

La solución es no separar el fragmento en el constructor de TabListener, solo déjelo adjunto. (Todavía necesita encontrarlo en el FragmentManager por su etiqueta) Además, en el método onTabSelected verifico si el fragmento está separado antes de adjuntarlo. Algo como esto:

 public void onTabSelected(Tab tab, FragmentTransaction ft) { if (mFragment == null) { mFragment = Fragment.instantiate(mActivity, mClass.getName(), mArgs); Log.d(TAG, "onTabSelected adding fragment " + mTag); ft.add(android.R.id.content, mFragment, mTag); } else { if(mFragment.isDetached()) { Log.d(TAG, "onTabSelected attaching fragment " + mTag); ft.attach(mFragment); } else { Log.d(TAG, "onTabSelected fragment already attached " + mTag); } } } 

He tenido el mismo problema con una actividad simple que solo tiene un fragmento (que a veces se reemplaza). Luego me di cuenta de que uso onSaveInstanceState solo en el fragmento (y onCreateView para verificar savedInstanceState), no en la actividad.

En el dispositivo, se activa la actividad que contiene los fragmentos y se llama a onCreated. Ahí adjunté el fragmento requerido (que es correcto el primer inicio).

En el dispositivo, Android primero volvió a crear el fragmento que era visible y luego llamó a Crear de la actividad que contenía el fragmento adjunto, reemplazando así el visible original.

Para evitar eso, simplemente cambié mi actividad para verificar savedInstanceState:

 protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); If (savedInstanceState != null) return; // following code to attach fragment initially 

Ni siquiera sobrescribí onSaveInstanceState de la actividad.

Las dos respuestas vertidas aquí muestran soluciones para una actividad con el modo de navegación NAVIGATION_MODE_TABS , pero tuve el mismo problema con una NAVIGATION_MODE_LIST . Hizo que mis Fragmentos perdieran inexplicablemente su estado cuando cambió la orientación de la pantalla, lo cual era realmente molesto. Afortunadamente, debido a su código útil logré resolverlo.

Básicamente, cuando se usa una navegación de lista, is automatically called when your activity is created/re-created, whether you like it or not. To prevent your Fragment's “ onNavigationItemSelected () is automatically called when your activity is created/re-created, whether you like it or not. To prevent your Fragment's is automatically called when your activity is created/re-created, whether you like it or not. To prevent your Fragment's from being called twice, this initial automatic call to onCreateView () de is automatically called when your activity is created/re-created, whether you like it or not. To prevent your Fragment's from being called twice, this initial automatic call to onNavigationItemSelected () should check whether the Fragment is already in existence inside your Activity. If it is, return immediately, because there is nothing to do; if it isn't, then simply construct the Fragment and add it to the Activity like you normally would. Performing this check prevents your Fragment from needlessly being created again, which is what causes should check whether the Fragment is already in existence inside your Activity. If it is, return immediately, because there is nothing to do; if it isn't, then simply construct the Fragment and add it to the Activity like you normally would. Performing this check prevents your Fragment from needlessly being created again, which is what causes should check whether the Fragment is already in existence inside your Activity. If it is, return immediately, because there is nothing to do; if it isn't, then simply construct the Fragment and add it to the Activity like you normally would. Performing this check prevents your Fragment from needlessly being created again, which is what causes onCreateView () `se llame dos veces.

Ver mi implementación onNavigationItemSelected() continuación.

 public class MyActivity extends FragmentActivity implements ActionBar.OnNavigationListener { private static final String STATE_SELECTED_NAVIGATION_ITEM = "selected_navigation_item"; private boolean mIsUserInitiatedNavItemSelection; // ... constructor code, etc. @Override public void onRestoreInstanceState(Bundle savedInstanceState) { super.onRestoreInstanceState(savedInstanceState); if (savedInstanceState.containsKey(STATE_SELECTED_NAVIGATION_ITEM)) { getActionBar().setSelectedNavigationItem(savedInstanceState.getInt(STATE_SELECTED_NAVIGATION_ITEM)); } } @Override public void onSaveInstanceState(Bundle outState) { outState.putInt(STATE_SELECTED_NAVIGATION_ITEM, getActionBar().getSelectedNavigationIndex()); super.onSaveInstanceState(outState); } @Override public boolean onNavigationItemSelected(int position, long id) { Fragment fragment; switch (position) { // ... choose and construct fragment here } // is this the automatic (non-user initiated) call to onNavigationItemSelected() // that occurs when the activity is created/re-created? if (!mIsUserInitiatedNavItemSelection) { // all subsequent calls to onNavigationItemSelected() won't be automatic mIsUserInitiatedNavItemSelection = true; // has the same fragment already replaced the container and assumed its id? Fragment existingFragment = getSupportFragmentManager().findFragmentById(R.id.container); if (existingFragment != null && existingFragment.getClass().equals(fragment.getClass())) { return true; //nothing to do, because the fragment is already there } } getSupportFragmentManager().beginTransaction().replace(R.id.container, fragment).commit(); return true; } } 

Tomé inspiración de esta solución desde aquí .

Me parece que es así porque está creando instancias de su TabListener cada vez … por lo que el sistema está recreando su fragmento de savedInstanceState y luego lo está haciendo nuevamente en su onCreate.

Debería envolverlo en un if(savedInstanceState == null) por lo que solo se activará si no hay savedInstanceState.