OnCreateOptionsMenu está siendo llamado demasiadas veces en ActionBar usando tabs

Aquí está mi problema. Tengo una aplicación donde estoy usando ActionBar Sherlock con pestañas, fragmentos con menús de opción. Cada vez que giro el emulador, los menús se agregan para todos los fragmentos incluso los que se hidded / removed (probé ambos).

Esta es la configuración: One FragmentActivity, que tiene una ActionBar con

final ActionBar bar = getSupportActionBar(); bar.addTab(bar.newTab() .setText("1") .setTabListener(new MyTabListener(new FragmentList1()))); bar.addTab(bar.newTab() .setText("2") .setTabListener(new MyTabListener(new FragmentList2()))); bar.addTab(bar.newTab() .setText("3") .setTabListener(new MyTabListener(new FragmentList3()))); bar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS); bar.setDisplayShowHomeEnabled(true); bar.setDisplayShowTitleEnabled(true); 

Las pestañas utilizan el mismo Listener:

 private class MyTabListener implements ActionBar.TabListener { private final FragmentListBase m_fragment; public MyTabListener(FragmentListBase fragment) { m_fragment = fragment; } public void onTabSelected(ActionBar.Tab tab, FragmentTransaction ft) { FragmentManager fragmentMgr = ActivityList.this.getSupportFragmentManager(); FragmentTransaction transaction = fragmentMgr.beginTransaction(); transaction.add(R.id.frmlyt_list, m_fragment, m_fragment.LIST_TAG); transaction.commit(); } public void onTabUnselected(ActionBar.Tab tab, FragmentTransaction ft) { FragmentManager fragmentMgr = ActivityList.this.getSupportFragmentManager(); FragmentTransaction transaction = fragmentMgr.beginTransaction(); transaction.remove(m_fragment); transaction.commit(); } public void onTabReselected(ActionBar.Tab tab, FragmentTransaction ft) { } } 

Cada subclase de FragmentListBase tiene su propio menú y por lo tanto las 3 subclases tienen:

  setHasOptionsMenu(true); 

Y el

 public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { Log.d(TAG, "OnCreateOptionsMenu"); inflater.inflate(R.menu.il_options_menu, menu); } 

Cuando ejecuto la aplicación puedo ver que el onCreateOptionsMenu se llama varias veces, para todos los diferentes fragmentos.

Estoy totalmente perplejo.

He intentado publicar el mayor número de código posible sin ser abrumador, si usted encuentra que algo falta, por favor avise.

[Editar] He añadido más registros, y resulta que el fragmento se está conectando dos veces (o más) en la rotación. Una cosa que noto es que todo se llama varias veces, excepto el método onCreate () que se llama una sola vez.

 06.704:/WindowManager(72): Setting rotation to 0, animFlags=0 06.926:/ActivityManager(72): Config changed: { scale=1.0 imsi=310/260 loc=en_US touch=3 keys=1/1/2 nav=1/2 orien=L layout=0x10000014 uiMode=0x11 seq=35} 07.374:/FragmentList1(6880): onAttach 07.524:/FragmentList1(6880): onCreateView 07.564:/FragmentList1(6880): onAttach 07.564:/FragmentListBase(6880): onCreate 07.564:/FragmentList1(6880): OnCreateOptionsMenu 07.574:/FragmentList1(6880): OnCreateOptionsMenu 07.604:/FragmentList1(6880): onCreateView 

[Editar 2]

Ok, empecé a buscar en el código de Android y encontré esta parte aquí (que he editado para acortar este post).

/com_actionbarsherlock/src/android/support/v4/app/FragmentManager.java

 public boolean dispatchCreateOptionsMenu(Menu menu, MenuInflater inflater) { if (mActive != null) { for (int i=0; i<mAdded.size(); i++) { Fragment f = mAdded.get(i); if (f != null && !f.mHidden && f.mHasMenu) { f.onCreateOptionsMenu(menu, inflater); } } } 

El problema es que mAdded de hecho tiene varias instancias de FragmentList1 en él, por lo que el método onCreateOptionsMenu () es "correctamente" se llama 3 veces, pero para diferentes instancias de la clase FragmentList1. Lo que no entiendo es por qué esa clase se está agregando varias veces … Pero eso es un infierno de una buena ventaja.

Parece que he encontrado el problema (s). Digo problema (s) porque encima de la multitud de menús, ahora hay también una Excepción.

1) la llamada a

  bar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS); 

Que es después de las llamadas a addTab () tiene un efecto secundario de llamar onTabSelected (). Mi TabListener entonces agregaría un FragmentList1 al FragmentManager

2) girar el dispositivo destruiría la Actividad como se esperaba, pero no destruiría los Fragmentos. Cuando se crea la nueva actividad después de la rotación haría dos cosas:

  1. Crear otro conjunto de fragmentos que se sumaría a la FragmentManager. Esto es lo que causaba la multitud de menús
  2. Call onTabSelected (a través de setNavigationMode ()) que realizaría el siguiente código:

      if (null != fragmentMgr.findFragmentByTag(m_fragment.LIST_TAG)) { transaction.attach(m_fragment); transaction.show(m_fragment); } else { transaction.add(R.id.frmlyt_list, m_fragment, m_fragment.LIST_TAG); } 

Básicamente si el fragmento ya está en el FragmentManager no hay necesidad de añadirlo, sólo muéstrelo. Pero ahí está el problema. ¡No es el mismo Fragmento! Es el Fragmento que fue creado por la instancia anterior de la Actividad. Así que trataría de adjuntar y mostrar este Fragmento recién creado que causaría una excepción

La solución.

Había algunas cosas que hacer para arreglar todo esto.

1) Moví el setNavigationMode () sobre el addTab () s.

2) así es como ahora creo mis pestañas:

  FragmentListBase fragment = (FragmentListBase)fragmentMgr.findFragmentByTag(FragmentList1.LIST_TAG_STATIC); if (null == fragment) { fragment = new FragmentList1(); } bar.addTab(bar.newTab() .setText("1") .setTabListener(new MyTabListener(fragment))); 

Así que en la creación de actividades tengo que comprobar para ver si los fragmentos ya están en el FragmentManager. Si son yo utilizo esas instancias, si no entonces creo nuevas. Esto se hace para las tres pestañas.

Usted puede haber notado que hay dos etiquetas similares: m_fragment.LIST_TAG y FragmentList1.LIST_TAG_STATIC. Ah, esto es encantador … (<- sarcasmo)

En ordrer para utilizar mi TagListener de forma polimórfica he declarado la siguiente variable no estática en la clase base:

 public class FragmentListBase extends Fragment { public String LIST_TAG = null; } 

Se asigna desde dentro de los descendientes y me permite buscar en el FragmentManager para los diferentes descendientes de FragmentListBase.

Pero también necesito buscar descendientes específicos ANTES de que se creen (porque necesito saber si debo crearlos o no), así que también tengo que declarar la siguiente variable estática.

 public class FragmentList1 extends FragmentListBase { public final static String LIST_TAG_STATIC = "TAG_LIST_1"; public FragmentList1() { LIST_TAG = LIST_TAG_STATIC; }; } 

Basta con decir que estoy decepcionado de que nadie llegó con esta solución simple y elegante (<- más sarcasmo)

Muchas gracias a Jake Wharton que se tomó el tiempo para mirar esto para mí 🙂

 public FragmentListBase() { setRetainInstance(true); setHasOptionsMenu(true); } 

Esto salvará / restaurará los estados individuales de cada uno de los fragmentos tras la rotación.


Otro cambio simple que puede que desee hacer es llamar a transaction.replace(R.id.frmlyt_list, m_fragment, m_fragment.LIST_TAG) en la ficha de devolución de llamada seleccionada y deshacerse del contenido de la devolución de llamada no seleccionada.

Tenía estos problemas muy similares con los menús "apilables" en la rotación. Yo no uso pestañas pero sí uso ViewPager con FragmentStatePagerAdapter así que realmente no puedo reutilizar mis Fragmentos. Después de golpear la cabeza durante 2 días encontré solución muy simple. De hecho, el problema parece ser con onCreateOptionsMenu llamado varias veces. Este pequeño fragmento de código cuida (máscaras?) De todos los problemas:

 /** to prevent multiple calls to inflate menu */ private boolean menuIsInflated; @Override public void onCreateOptionsMenu(final Menu menu, final MenuInflater inflater) { if (!menuIsInflated) { inflater.inflate(R.menu.job_details_fragment_menu, menu); menuIsInflated = true; } } 

Sólo una nota bastante en sus frustraciones de etiquetas polimórficas.

Declare su clase base de la siguiente manera:

 public abstract class ListFragmentBase { protected abstract String getListTag(); } 

Ahora declare sus sub clases algo como esto:

 public class FragmentList1 extends ListFragmentBase { public static final String LIST_TAG = "TAG_LIST_1"; @Override protected String getListTag() { return LIST_TAG; } } 

Ahora, la forma polimórfica de obtener la etiqueta de instancia es la siguiente:

 ListFragmentBase frag = new FragmentList1(); frag.getListTag(); 

Obtenga la etiqueta statically like so:

 FragmentList1.LIST_TAG; 

Lo que funcionó para mí fue mover el setHasMenuOptions (true) a la actividad de llamada, es decir, la actividad en la que se declaró el fragmento. Anteriormente lo tenía en el método onCreate del fragmento.

Aquí está el fragmento de código:

 @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); FragmentManager fragmentManager = getFragmentManager(); FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction(); ForecastFragment forecastFragment = new ForecastFragment(); forecastFragment.setHasOptionsMenu(true); fragmentTransaction.add(R.id.fragment, forecastFragment); fragmentTransaction.commit(); } 

Al menos en Honeycomb SDK relacionados el problema se resuelve mediante la adición

 android:configChanges="orientation" 

A la declaración de actividad en su archivo AndroidManifest.xml. Aún puede agregar y quitar fragmentos como se muestra en la sección Adición de pestañas de http://developer.android.com/guide/topics/ui/actionbar.html

  • Los animadores sólo se pueden ejecutar en los subprocesos Looper en Sherlock Action Bar
  • Cambiar el tema de una actividad de un solo toque
  • No se puede mantener fragmentos en vivo a través de cambiar la orientación o Guardar fragmento de interfaz de usuario
  • Ocultar icono del lanzador de aplicaciones en la barra de título cuando se inicia la actividad en android
  • ActionBarSherlock problema ShareActionProvider
  • Android - ActionBarSherlock - Establecer textcolor de texto en el menú
  • ¿Puede Android Actionbar ser colocado verticalmente?
  • Elegir ActionBar Sherlock
  • ActionBarSherlock & HoloEverywhere - Forzar el desbordamiento?
  • ViewPagerIndicator no muestra el texto del título
  • ¿Cuáles son los problemas comunes al migrar de ActionBarSherlock a ActionBarCompat?
  • FlipAndroid es un fan de Google para Android, Todo sobre Android Phones, Android Wear, Android Dev y Aplicaciones para Android Aplicaciones.