106 votes

Fragment d’onCreateView et d’onActivityCreated appelé deux fois

Je suis le développement d'une application Android 4.0 ICS et de fragments.

Considérez ceci modifiés exemple de l'ICS 4.0.3 (API de niveau 15) de l'API de démo exemple d'application:

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

}

Voici la sortie extrait de l'exécution de cet exemple et en faisant pivoter le téléphone:

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

Ma question est, pourquoi le onCreateView et onActivityCreated appelé deux fois? La première fois avec un Bundle avec l'état sauvegardé et la deuxième fois avec une valeur null savedInstanceState?

C'est à l'origine des problèmes de maintenir l'état de fragment de rotation.

45voto

Staffan Points 391

J'ai été de me gratter la tête à ce sujet pendant un certain temps aussi, et depuis Dave explication est un peu dur à comprendre, je vais poster mon (apparemment de travail) code:

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

Comme vous pouvez le voir, il est à peu près comme l'Android de l'échantillon, à l'exception de ne pas détacher le constructeur, et à l'aide de remplacer au lieu d'ajouter.

Après beaucoup de headscratching et d'essais et d'erreurs, j'ai trouvé que le fait de trouver le fragment dans le constructeur semble faire le double onCreateView problème comme par magie disparaître (je suppose que ça se termine juste en haut être null pour onTabSelected lorsqu'il est appelé par le biais de l'ActionBar.setSelectedNavigationItem() chemin d'accès lors de la sauvegarde/restauration de l'etat).

29voto

Gunnar Bernstein Points 853

J'ai eu le même problème avec une Activité simple comptable un seul fragment (qui serait remplacé parfois). J'ai alors réalisé que j'utilise onSaveInstanceState seulement dans le fragment (et onCreateView pour vérifier savedInstanceState), pas dans l'activité.

Sur l'appareil allumez l'activité contenant les fragments obtient redémarré et onCreated est appelé. Là, je ne joindre le fragment (ce qui est correct au premier démarrage).

Sur le tour de périphériques Android d'abord re-créé le fragment qui était visible, puis a appelé onCreate de l'contenant de l'activité où mon fragment a été fixé, en remplacement de l'original visible.

Pour éviter que j'ai simplement changé mon activité pour vérifier les savedInstanceState:

protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

If (savedInstanceState != null)
        return;

    // following code to attach fragment initially

Je n'ai même pas Écraser onSaveInstanceState de la activiy.

26voto

Dave Points 1743

Ok, Voici ce que j'ai trouvé.

Ce que je ne comprends pas, c'est que tous les fragments qui sont attachés à une activité lorsqu'une config changement se produit (téléphone tourne) sont recréés et ajoutée à l'activité. (ce qui est logique)

Ce qui se passait dans la TabListener constructeur a été l'onglet a été détaché s'il a été trouvé et fixé à l'activité. Voir ci-dessous:

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

Plus tard dans l'activité onCreate précédemment sélectionné, l'onglet est sélectionné à partir de l'enregistrés l'état de l'instance. Voir ci-dessous:

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

Lorsque l'onglet est sélectionné, il serait rattachée à la onTabSelected de rappel.

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

Le fragment attaché est le deuxième appel à la onCreateView et onActivityCreated méthodes. (Le premier étant lorsque le système est en recréant l'acitivité et tous les fragments) La première fois que le onSavedInstanceState Bundle aurait sauvé des données, mais pas la deuxième fois.

La solution est de ne pas détacher le fragment dans le TabListener constructeur, juste laisser. (Vous avez encore besoin de trouver dans le FragmentManager par tag) dans le onTabSelected méthode-je vérifier pour voir si le fragment est détachée avant de me joindre. Quelque chose comme ceci:

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

12voto

Les deux upvoted réponses ici montrent des solutions pour une Activité de mode de navigation NAVIGATION_MODE_TABS, mais j'ai eu le même problème avec un NAVIGATION_MODE_LIST. Il la cause de mon Fragments inexplicablement perdre leur état lorsque l'orientation de l'écran a changé, ce qui était vraiment gênant. Heureusement, en raison de leur code utile, j'ai réussi à le comprendre.

Fondamentalement, lorsque vous utilisez une liste de navigation, `onNavigationItemSelected() est automatiquement appelée quand votre activité est créée/re-créé, si vous l'aimez ou pas. Pour éviter que votre Fragment de la onCreateView() d'être appelé deux fois, ce premier appel automatique d' onNavigationItemSelected() doivent vérifier si le Fragment est déjà en existence à l'intérieur de votre Activité. Si elle l'est, de retour immédiatement, car il n'y a rien à faire; si elle ne l'est pas, puis il suffit de construire le Fragment et l'ajouter à l'Activité comme vous le feriez normalement. L'exécution de cette vérification empêche votre Fragment de la inutilement de l'être créé de nouveau, qui est ce qui provoque onCreateView() à être appelé deux fois!

Voir mon onNavigationItemSelected() de mise en œuvre ci-dessous.

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

J'ai emprunté l'inspiration pour cette solution à partir d' ici.

8voto

Barak Points 12045

Il me semble que c’est parce que vous êtes instancier votre TabListener chaque fois... donc le système est recréer votre fragment de la savedInstanceState et puis vous le faites à nouveau dans votre onCreate.

Vous devez encapsuler que dans une `` donc il ne se déclenche que si il n’y a aucun savedInstanceState.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X