web-dev-qa-db-ger.com

Fragment onCreateView und onActivityCreated werden zweimal aufgerufen

Ich entwickle eine App mit Android 4.0 ICS und Fragmenten.

Betrachten Sie dieses modifizierte Beispiel aus der Demo-Beispiel-App der ICS= 4.0.3 (API Level 15) API:

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

}

Hier ist die Ausgabe, die durch Ausführen dieses Beispiels und anschließendes Drehen des Telefons abgerufen wurde:

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

Meine Frage ist, warum onCreateView und onActivityCreated zweimal aufgerufen werden? Das erste Mal mit einem Bundle mit dem gespeicherten Status und das zweite Mal mit einem Nullwert für savedInstanceState?

Dies führt zu Problemen beim Beibehalten des Status des Fragments bei der Drehung.

92
Dave

Ok, hier ist was ich herausgefunden habe.

Was ich nicht verstanden habe, ist, dass alle Fragmente, die bei einer Konfigurationsänderung an eine Aktivität angehängt werden (das Telefon dreht sich), neu erstellt und der Aktivität wieder hinzugefügt werden. (was Sinn macht)

Im TabListener-Konstruktor wurde die Registerkarte getrennt, wenn sie gefunden und an die Aktivität angehängt wurde. Siehe unten:

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

Später in der Aktivität onCreate wurde die zuvor ausgewählte Registerkarte aus dem Status der gespeicherten Instanz ausgewählt. Siehe unten:

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

Wenn die Registerkarte ausgewählt wurde, wurde sie im onTabSelected-Rückruf wieder angefügt.

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

Das angehängte Fragment ist der zweite Aufruf der Methoden onCreateView und onActivityCreated. (Das erste ist, wenn das System die Aktivität und alle angehängten Fragmente neu erstellt.) Das erste Mal, wenn das onSavedInstanceState-Bundle Daten gespeichert hat, aber nicht das zweite Mal.

Die Lösung besteht darin, das Fragment im TabListener-Konstruktor nicht zu trennen, sondern es einfach angefügt zu lassen. (Sie müssen es immer noch im FragmentManager anhand des Tags finden.) Außerdem überprüfe ich in der onTabSelected-Methode, ob das Fragment abgetrennt ist, bevor ich es anhänge. Etwas wie das:

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

Ich habe mich auch eine Weile am Kopf gekratzt und da Daves Erklärung ein wenig schwer zu verstehen ist, werde ich meinen (anscheinend funktionierenden) Code posten:

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

Wie Sie sehen, ähnelt es dem Beispiel Android), abgesehen davon, dass es sich nicht vom Konstruktor löst und Ersetzen statt Hinzufügen verwendet.

Nach langem Headscratchen und Ausprobieren stellte ich fest, dass das Finden des Fragments im Konstruktor das doppelte onCreateView-Problem auf magische Weise zu lösen scheint (ich nehme an, dass es für onTabSelected nur null ist, wenn es über den ActionBar.setSelectedNavigationItem () - Pfad aufgerufen wird, wenn Zustand speichern/wiederherstellen).

44
Staffan

Ich hatte das gleiche Problem mit einer einfachen Aktivität, die nur ein Fragment enthielt (das manchmal ersetzt wurde). Dann wurde mir klar, dass ich onSaveInstanceState nur im Fragment (und onCreateView, um nach savedInstanceState zu suchen) und nicht in der Aktivität verwende.

Beim Einschalten des Geräts wird die Aktivität mit den Fragmenten neu gestartet und onCreated aufgerufen. Dort habe ich das benötigte Fragment angehängt (welches beim ersten Start korrekt ist).

Schalten Sie auf dem Gerät Android erst das Fragment, das sichtbar war, neu erstellt und dann onCreate der enthaltenen Aktivität aufgerufen, an die mein Fragment angehängt wurde, wodurch das ursprüngliche sichtbare ersetzt wird.

Um dies zu vermeiden, habe ich einfach meine Aktivität geändert und nach savedInstanceState gesucht:

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

if (savedInstanceState != null) {
/**making sure you are not attaching the fragments again as they have 
 been 
 *already added
 **/
 return; 
 }
 else{
  // following code to attach fragment initially
 }

 }

Ich habe nicht einmal den Status der Aktivität überschrieben.

21

Die beiden hier aufgeführten Antworten zeigen Lösungen für eine Aktivität mit dem Navigationsmodus NAVIGATION_MODE_TABS, Aber ich hatte das gleiche Problem mit einem NAVIGATION_MODE_LIST. Es führte dazu, dass meine Fragmente unerklärlicherweise ihren Zustand verloren, als sich die Bildschirmausrichtung änderte, was wirklich ärgerlich war. Zum Glück konnte ich es aufgrund ihres hilfreichen Codes herausfinden.

Grundsätzlich muss bei Verwendung einer Listennavigation "onNavigationItemSelected () is automatically called when your activity is created/re-created, whether you like it or not. To prevent your Fragment's OnCreateView () 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 OnCreateView ()" zweimal aufgerufen werden!

Siehe meine onNavigationItemSelected() Implementierung unten.

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

Ich habe Inspiration für diese Lösung von hier entlehnt.

12

Es sieht für mich so aus, als ob Sie Ihren TabListener jedes Mal instanziieren ... also erstellt das System Ihr Fragment aus dem SavedInstanceState neu und Sie machen es dann erneut in Ihrem OnCreate.

Sie sollten das in eine if(savedInstanceState == null) einschließen, damit es nur ausgelöst wird, wenn kein savedInstanceState vorhanden ist.

7
Barak