Frammento suCreateView e onActivityCreated chiamato due volte

Sto sviluppando un'applicazione utilizzando Android 4.0 ICS e frammenti.

Consideri questo esempio modificato dall'applicazione demo dell'API ICS 4.0.3 (API livello 15):

  • tutte le immagini che non vengono visualizzate dallo stesso URL
  • Imansible creare il gestore all'interno del thread che non ha chiamato Looper.prepare ()
  • Condividi Android via dialogo
  • Avviare AnimationDrawable in elementi ListView. Quando sono gli elementi allegati?
  • Come creare layout per pianoforte
  • come aggiornare la variabile di textview each 5 secondi
  • 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<SimpleFragment>( 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<T extends Fragment> implements ActionBar.TabListener { private final Activity mActivity; private final String mTag; private final Class<T> mClass; private final Bundle mArgs; private Fragment mFragment; public TabListener(Activity activity, String tag, Class<T> clz) { this(activity, tag, clz, null); } public TabListener(Activity activity, String tag, Class<T> 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; } } 

    }

    Ecco l'output recuperata dall'esecuzione di questo esempio e ruotando il telefono:

     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 

    La mia domanda è, perché è chiamato due volte su onCreateView e onActivityCreated? La prima volta con un Bundle con lo stato salvato e la seconda volta con un salvatoInstanceState null?

    Questo sta causando problemi a mantenere lo stato del frammento in rotazione.

  • Come annullare questo allarme ripetuto?
  • Importzione di ant build.xml in Eclipse
  • una visualizzazione di text in una row di listview non può fare clic dopo aver impostato descendantFocusability = "blocksDescendants"
  • Come innescare un evento quando scrollView raggiunge il fondo con Android?
  • CyanogenMod: Traduci un progetto
  • Devo utilizzare PhoneGap anziché accentuare l'SDK Android?
  • 5 Solutions collect form web for “Frammento suCreateView e onActivityCreated chiamato due volte”

    Stavo grasso la testa su questo per un po 'troppo, e poiché la spiegazione di Dave è un po' difficile da capire, pubblicherò il mio codice (apparentemente funzionante):

     private class TabListener<T extends Fragment> implements ActionBar.TabListener { private Fragment mFragment; private Activity mActivity; private final String mTag; private final Class<T> mClass; public TabListener(Activity activity, String tag, Class<T> 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) { } } 

    Come potete vedere è abbastanza simile al campione Android, a parte non separare il constructor e utilizzare sostituire invece di aggiungere .

    Dopo molte scoperte e trial-e-error ho scoperto che trovare il frammento nel constructor sembra rendere il doppio problema onCreateView sparire magicamente (suppongo che finisce solo per essere null per onTabSelected quando richiamato attraverso il path ActionBar.setSelectedNavigationItem () quando salvataggio / ripristino dello stato).

    Ok, ecco quello che ho scoperto.

    Quello che non ho capito è che tutti i frammenti che sono collegati ad un'attività quando si verifica un cambiamento di configuration (rotazione del telefono) vengono ricreati e aggiunti all'attività. (che ha senso)

    Quello che stava accadendo nel constructor di TabListener era che la scheda è stata staccata se è stata trovata e collegata all'attività. Vedi sotto:

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

    Più avanti nell'attività in Crea la scheda selezionata in precedenza è stata selezionata dalla condizione di istanza salvata. Vedi sotto:

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

    Quando la scheda è stata selezionata, verrà reattaccata nella 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); } } 

    Il frammento allegato è la seconda chiamata ai methods onCreateView e onActivityCreated. (Il primo è quando il sistema sta ricreando l'azione e tutti i frammenti allegati) La prima volta che il pacchetto onSavedInstanceState avrebbe salvato i dati, ma non la seconda volta.

    La soluzione è quella di non separare il frammento nel constructor di TabListener, basta lasciarlo collegato. (Hai ancora bisogno di trovarlo nel FragmentManager tramite il suo tag) Inoltre, nel metodo onTabSelected, verifico se il frammento è staccato prima di allegarlo. Qualcosa come questo:

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

    Le due risposte prevalse qui mostrano soluzioni per un'attività con la modalità di navigazione NAVIGATION_MODE_TABS , ma ho avuto lo stesso problema con un NAVIGATION_MODE_LIST . Ha causato che i miei frammenti perdono inesatto il loro stato quando l'orientamento dello schermo cambiato, cosa che era veramente fastidiosa. Per fortuna, grazie al loro codice utile, sono riuscito a capire.

    Fondamentalmente, quando si utilizza una navigazione di elenco, `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 suCreateView () del 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 () chiamato due volte!

    Vedere la mia implementazione onNavigationItemSelected() qui sotto.

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

    Ho preso in considerazione l'idea di questa soluzione da qui .

    Ho avuto lo stesso problema con una semplice attività che port solo un frammento (che potrebbe essere sostituito a volte). Ho poi capito di utilizzare onSaveInstanceState solo nel frammento (e onCreateView per controllare saveInstanceState), non nell'attività.

    Sul dispositivo ruotare l'attività che contiene i frammenti viene riavviata e viene chiamata onCreated. Lì ho allegato il frammento richiesto (che è corretto al primo avvio).

    Sull'apparecchio, l'Android ha prima ristabilito il frammento visibile e poi chiamato onCreate dell'attività contenente where il mio frammento è stato allegato, sostituendo così l'originale visibile.

    Per evitare che ho semplicemente cambiato la mia attività per verificare salvatoInstanceState:

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

    Non ho neanche sovrascritto suSaveInstanceState dell'attivit.

    Mi sembra piace perché è l'istanziazione del tuo TabListener each volta … quindi il sistema sta ricreando il tuo frammento dal savedInstanceState e poi lo stai facendo di nuovo nel tuo onCreate.

    Dovresti avvolgerlo in un if(savedInstanceState == null) modo che si spegni solo se non c'è saveInstanceState.

    L'Android è un fan Android di Google, tutto su telefoni Android, Android Wear, Android Dev e applicazioni Android Games e così via.