Frammento MyFragment non associato all'attività


393

Ho creato una piccola app di prova che rappresenta il mio problema. Sto usando ActionBarSherlock per implementare le schede con (Sherlock) Frammenti.

Il mio codice: TestActivity.java

public class TestActivity extends SherlockFragmentActivity {
    private ActionBar actionBar;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setupTabs(savedInstanceState);
    }

    private void setupTabs(Bundle savedInstanceState) {
        actionBar = getSupportActionBar();
        actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);

        addTab1();
        addTab2();
    }

    private void addTab1() {
        Tab tab1 = actionBar.newTab();
        tab1.setTag("1");
        String tabText = "1";
        tab1.setText(tabText);
        tab1.setTabListener(new TabListener<MyFragment>(TestActivity.this, "1", MyFragment.class));

        actionBar.addTab(tab1);
    }

    private void addTab2() {
        Tab tab1 = actionBar.newTab();
        tab1.setTag("2");
        String tabText = "2";
        tab1.setText(tabText);
        tab1.setTabListener(new TabListener<MyFragment>(TestActivity.this, "2", MyFragment.class));

        actionBar.addTab(tab1);
    }
}

TabListener.java

public class TabListener<T extends SherlockFragment> implements com.actionbarsherlock.app.ActionBar.TabListener {
    private final SherlockFragmentActivity mActivity;
    private final String mTag;
    private final Class<T> mClass;

    public TabListener(SherlockFragmentActivity activity, String tag, Class<T> clz) {
        mActivity = activity;
        mTag = tag;
        mClass = clz;
    }

    /* The following are each of the ActionBar.TabListener callbacks */

    public void onTabSelected(Tab tab, FragmentTransaction ft) {
        SherlockFragment preInitializedFragment = (SherlockFragment) mActivity.getSupportFragmentManager().findFragmentByTag(mTag);

        // Check if the fragment is already initialized
        if (preInitializedFragment == null) {
            // If not, instantiate and add it to the activity
            SherlockFragment mFragment = (SherlockFragment) SherlockFragment.instantiate(mActivity, mClass.getName());
            ft.add(android.R.id.content, mFragment, mTag);
        } else {
            ft.attach(preInitializedFragment);
        }
    }

    public void onTabUnselected(Tab tab, FragmentTransaction ft) {
        SherlockFragment preInitializedFragment = (SherlockFragment) mActivity.getSupportFragmentManager().findFragmentByTag(mTag);

        if (preInitializedFragment != null) {
            // Detach the fragment, because another one is being attached
            ft.detach(preInitializedFragment);
        }
    }

    public void onTabReselected(Tab tab, FragmentTransaction ft) {
        // User selected the already selected tab. Usually do nothing.
    }
}

MyFragment.java

public class MyFragment extends SherlockFragment {

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        new AsyncTask<Void, Void, Void>() {

            @Override
            protected Void doInBackground(Void... params) {
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException ex) {
                }
                return null;
            }

            @Override
            protected void onPostExecute(Void result){
                getResources().getString(R.string.app_name);
            }

        }.execute();
    }
}

Ho aggiunto la Thread.sleepparte per simulare il download dei dati. Il codice in onPostExecuteè per simulare l'uso di Fragment.

Quando ruoto lo schermo molto velocemente tra orizzontale e verticale, ottengo un'eccezione al onPostExecutecodice:

java.lang.IllegalStateException: Fragment MyFragment {410f6060} non associato all'attività

Penso che sia perché MyFragmentnel frattempo è stato creato un nuovo ed è stato attaccato all'attività prima del AsyncTaskcompletamento. Il codice in onPostExecuterichiama un distaccato MyFragment.

Ma come posso risolvere questo?


1
Dovresti usare la vista dal gonfiatore di frammenti. mView = inflater.inflate(R.layout.my_layout, container, false) E ora utilizzare questa vista quando si desidera ottenere risorse: mView.getResources().***. Mi aiuta a correggere questo errore.
Foxis,

@foxis That perde quello Contextche è attaccato al tuo `mView`.
nhaarman,

Forse non lo controllo ancora. Per evitare perdite, che ne dici di diventare nullo mViewin onDestroy?
foxis,

Risposte:


774

Ho trovato la risposta molto semplice isAdded():

Restituisce truese il frammento è attualmente aggiunto alla sua attività.

@Override
protected void onPostExecute(Void result){
    if(isAdded()){
        getResources().getString(R.string.app_name);
    }
}

Per evitare onPostExecutedi essere chiamato quando il Fragmentnon è collegato al Activityè di annullare il AsyncTaskquando si mette in pausa o si interrompe il Fragment. Quindi isAdded()non sarebbe più necessario. Tuttavia, è consigliabile mantenere questo controllo in atto.


Nel mio caso, quando lancio un'altra applicazione Intento da ... allora ricevo lo stesso errore ... qualche suggerimento?
CoDe,

1
developer.android.com/reference/android/app/… ... c'è anche isDetached(), che è stato aggiunto al livello API 13
Lucas Jota

5
Quando su API <11, stai utilizzando developer.android.com/reference/android/support/v4/app/… dove funzionerà.
nhaarman,

Ho riscontrato questo problema quando ho usato DialogFragment. Dopo aver chiuso dialogFragment, ho provato ad avviare un'altra attività. Quindi si è verificato questo errore. Ho evitato questo errore chiamando dismiss () dopo startActivity. Il problema era che il frammento era già staccato dall'attività.
Ataru,

28

Il problema è che si sta tentando di accedere alle risorse (in questo caso, stringhe) utilizzando getResources (). GetString (), che tenterà di ottenere le risorse dall'attività. Vedi questo codice sorgente della classe Fragment:

 /**
  * Return <code>getActivity().getResources()</code>.
  */
 final public Resources getResources() {
     if (mHost == null) {
         throw new IllegalStateException("Fragment " + this + " not attached to Activity");
     }
     return mHost.getContext().getResources();
 }

mHost è l'oggetto che contiene la tua attività.

Poiché l'attività potrebbe non essere collegata, la chiamata getResources () genererà un'eccezione.

La soluzione accettata IMHO non è la strada da percorrere perché stai solo nascondendo il problema. Il modo corretto è solo quello di ottenere le risorse da qualche altra parte che è sempre garantita l'esistenza, come il contesto dell'applicazione:

youApplicationObject.getResources().getString(...)

Ho usato questa soluzione perché dovevo eseguirla getString()quando il mio frammento era in pausa. Grazie
Geekarist

24

Ho affrontato due diversi scenari qui:

1) Quando voglio che l'attività asincrona finisca comunque: immagina che il mio onPostExecute memorizzi i dati ricevuti e quindi chiamo un ascoltatore per aggiornare le viste, quindi, per essere più efficiente, voglio che l'attività finisca comunque in modo da avere i dati pronti quando l'utente arriva indietro. In questo caso di solito faccio questo:

@Override
protected void onPostExecute(void result) {
    // do whatever you do to save data
    if (this.getView() != null) {
        // update views
    }
}

2) Quando desidero che l'attività asincrona termini solo quando le viste possono essere aggiornate: nel caso che stai proponendo qui, l'attività aggiorna solo le viste, non è necessaria l'archiviazione dei dati, quindi non ha idea del completamento dell'attività se le viste sono non viene più mostrato. Lo faccio:

@Override
protected void onStop() {
    // notice here that I keep a reference to the task being executed as a class member:
    if (this.myTask != null && this.myTask.getStatus() == Status.RUNNING) this.myTask.cancel(true);
    super.onStop();
}

Non ho riscontrato alcun problema, anche se utilizzo anche un modo (forse) più complesso che include l'avvio di attività dall'attività anziché dai frammenti.

Vorrei che questo aiuti qualcuno! :)


18

Il problema con il tuo codice è il modo in cui stai usando AsyncTask, perché quando ruoti lo schermo durante il thread del sonno:

Thread.sleep(2000) 

AsyncTask funziona ancora, è perché non hai annullato correttamente l'istanza di AsyncTask in onDestroy () prima che il frammento venga ricostruito (quando ruoti) e quando questa stessa istanza di AsyncTask (dopo la rotazione) viene eseguita suPostExecute (), questo cerca di trovare le risorse con getResources () con la vecchia istanza di frammento (un'istanza non valida):

getResources().getString(R.string.app_name)

che equivale a:

MyFragment.this.getResources().getString(R.string.app_name)

Quindi la soluzione finale è gestire l'istanza di AsyncTask (per annullare se funziona ancora) prima che il frammento si ricostruisca quando si ruota lo schermo e, se annullato durante la transizione, riavviare AsyncTask dopo la ricostruzione con l'aiuto di un flag booleano:

public class MyFragment extends SherlockFragment {

    private MyAsyncTask myAsyncTask = null;
    private boolean myAsyncTaskIsRunning = true;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        if(savedInstanceState!=null) {
            myAsyncTaskIsRunning = savedInstanceState.getBoolean("myAsyncTaskIsRunning");
        }
        if(myAsyncTaskIsRunning) {
            myAsyncTask = new MyAsyncTask();
            myAsyncTask.execute();
        }
    }

    @Override
    public void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        outState.putBoolean("myAsyncTaskIsRunning",myAsyncTaskIsRunning);
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        if(myAsyncTask!=null) myAsyncTask.cancel(true);
        myAsyncTask = null;

    }

    public class MyAsyncTask extends AsyncTask<Void, Void, Void>() {

        public MyAsyncTask(){}

        @Override
        protected void onPreExecute() {
            super.onPreExecute();
            myAsyncTaskIsRunning = true;
        }
        @Override
        protected Void doInBackground(Void... params) {
            try {
                Thread.sleep(2000);
            } catch (InterruptedException ex) {}
            return null;
        }

        @Override
        protected void onPostExecute(Void result){
            getResources().getString(R.string.app_name);
            myAsyncTaskIsRunning = false;
            myAsyncTask = null;
        }

    }
}

invece se getResources().***usando Fragments.this.getResource().***aiutato
Prabs

17

Sono una soluzione abbastanza ingannevole per questo e la perdita di frammento dall'attività.

Quindi, nel caso di getResource o di qualsiasi altra cosa che dipenda dal contesto di attività che accede dal frammento, si controlla sempre lo stato dell'attività e lo stato dei frammenti come segue

 Activity activity = getActivity(); 
    if(activity != null && isAdded())

         getResources().getString(R.string.no_internet_error_msg);
//Or any other depends on activity context to be live like dailog


        }
    }

7
isAdded () è sufficiente perché: booleano pubblico finale isAdded () {return mHost! = null && mAdded; }
NguyenDat

Nel mio caso, questi controlli non sono fatturati, ma si verificano comunque arresti anomali nonostante li abbia aggiunti.
David,

@ David, isAddedè abbastanza. Non ho mai visto una situazione quando si getString()era schiantato se isAdded == true. Sei sicuro che sia stata mostrata un'attività e che sia stato attaccato un frammento?
CoolMind

14
if (getActivity() == null) return;

funziona anche in alcuni casi. Basta interrompere l'esecuzione del codice da esso e assicurarsi che l'app non si blocchi


10

Ho affrontato lo stesso problema, ho solo aggiunto l'istanza singletone per ottenere la risorsa indicata da Erick

MainFragmentActivity.defaultInstance().getResources().getString(R.string.app_name);

puoi anche usare

getActivity().getResources().getString(R.string.app_name);

Spero che questo possa aiutare.


2

Ho riscontrato problemi simili quando era visibile l'attività delle impostazioni dell'applicazione con le preferenze caricate. Se cambiassi una delle preferenze e poi facessi ruotare il contenuto del display e cambiassi di nuovo la preferenza, si bloccherebbe con un messaggio che il frammento (la mia classe Preferenze) non era associato a un'attività.

Durante il debug sembrava che il metodo onCreate () di PreferencesFragment fosse chiamato due volte quando il contenuto di visualizzazione ruotava. Era già abbastanza strano. Quindi ho aggiunto il controllo isAdded () al di fuori del blocco, dove avrebbe indicato l'incidente e risolto il problema.

Ecco il codice dell'ascoltatore che aggiorna il riepilogo delle preferenze per mostrare la nuova voce. Si trova nel metodo onCreate () della mia classe Preferenze che estende la classe PreferenceFragment:

public static class Preferences extends PreferenceFragment {
    SharedPreferences.OnSharedPreferenceChangeListener listener;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        // ...
        listener = new SharedPreferences.OnSharedPreferenceChangeListener() {
            @Override
            public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
                // check if the fragment has been added to the activity yet (necessary to avoid crashes)
                if (isAdded()) {
                    // for the preferences of type "list" set the summary to be the entry of the selected item
                    if (key.equals(getString(R.string.pref_fileviewer_textsize))) {
                        ListPreference listPref = (ListPreference) findPreference(key);
                        listPref.setSummary("Display file content with a text size of " + listPref.getEntry());
                    } else if (key.equals(getString(R.string.pref_fileviewer_segmentsize))) {
                        ListPreference listPref = (ListPreference) findPreference(key);
                        listPref.setSummary("Show " + listPref.getEntry() + " bytes of a file at once");
                    }
                }
            }
        };
        // ...
    }

Spero che questo possa aiutare gli altri!


0

Se estendi la Applicationclasse e mantieni un oggetto contestuale 'globale' statico, come segue, puoi usarlo al posto dell'attività per caricare una risorsa String.

public class MyApplication extends Application {
    public static Context GLOBAL_APP_CONTEXT;

    @Override
    public void onCreate() {
        super.onCreate();
        GLOBAL_APP_CONTEXT = this;
    }
}

Se lo usi, puoi cavartela Toaste caricare le risorse senza preoccuparti dei cicli di vita.


5
Sono sottovalutato ma nessuno ha spiegato il perché. I contesti statici di solito sono male, ma ero dell'idea che non si tratti di una perdita di memoria se si dispone di un riferimento statico all'applicazione.
Anthony Chuinard,

La tua risposta è sottovalutata perché questa è solo una soluzione non adeguata. Verifica la soluzione condivisa da @nhaarman
Vivek Kumar Srivastava il

0

Nel mio caso sono stati chiamati i metodi dei frammenti dopo

getActivity().onBackPressed();

0

Un vecchio post, ma sono rimasto sorpreso dalla risposta più votata.

La soluzione corretta per questo dovrebbe essere quella di cancellare l'asincctask in onStop (o dove appropriato nel tuo frammento). In questo modo non si introduce una perdita di memoria (un asincrono che mantiene un riferimento al frammento distrutto) e si ha un migliore controllo di ciò che sta accadendo nel frammento.

@Override
public void onStop() {
    super.onStop();
    mYourAsyncTask.cancel(true);
}

1
La risposta più votata include questo. Inoltre, cancelpotrebbe non impedire onPostExecutedi essere invocato.
nhaarman,

La chiamata di annullamento garantisce che onPostExecute non verrà mai chiamato, entrambe le chiamate vengono eseguite sullo stesso thread, quindi sei sicuro che non verrà invocato dopo aver chiamato Annulla
Raz
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.