wake-up-neo.net

Fragment MyFragment nicht an Aktivität angehängt

Ich habe eine kleine Test-App erstellt, die mein Problem darstellt. Ich verwende ActionBarSherlock, um Registerkarten mit (Sherlock-) Fragmenten zu implementieren.

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

Ich habe den Teil Thread.sleep hinzugefügt, um das Herunterladen von Daten zu simulieren. Der Code in onPostExecute simuliert die Verwendung von Fragment.

Wenn ich den Bildschirm sehr schnell zwischen Quer- und Hochformat drehe, erhalte ich eine Ausnahme beim onPostExecute Code:

Java.lang.IllegalStateException: Fragment MyFragment {410f6060} ist nicht an Activity angehängt

Ich denke, das liegt daran, dass in der Zwischenzeit ein neues MyFragment erstellt wurde, das der Aktivität angehängt wurde, bevor das AsyncTask beendet wurde. Der Code in onPostExecute ruft ein nicht verbundenes MyFragment auf.

Aber wie kann ich das beheben?

377
nhaarman

Ich habe die sehr einfache Antwort gefunden: isAdded() :

Geben Sie true zurück, wenn das Fragment derzeit zu seiner Aktivität hinzugefügt wird.

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

Um zu verhindern, dass onPostExecute aufgerufen wird, wenn Fragment nicht an Activity angehängt ist, wird AsyncTask abgebrochen, wenn Fragment angehalten oder angehalten wird. Dann wäre isAdded() nicht mehr nötig. Es ist jedoch ratsam, diese Kontrolle beizubehalten.

756
nhaarman

Das Problem besteht darin, dass Sie versuchen, mit getResources (). GetString () auf Ressourcen (in diesem Fall Zeichenfolgen) zuzugreifen, um die Ressourcen aus der Aktivität abzurufen. Siehe diesen Quellcode der Fragment-Klasse:

 /**
  * 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 ist das Objekt, das Ihre Aktivität enthält.

Da die Aktivität möglicherweise nicht angehängt ist, löst Ihr Aufruf von getResources () eine Ausnahme aus.

Die akzeptierte Lösung IMHO ist nicht der richtige Weg, da Sie nur das Problem verbergen. Der richtige Weg besteht darin, die Ressourcen von einem anderen Ort zu beziehen, von dem immer garantiert wird, dass sie existieren, wie z. B. dem Anwendungskontext:

youApplicationObject.getResources().getString(...)
25
Tiago

Ich habe hier zwei verschiedene Szenarien erlebt:

1) Wenn die asynchrone Aufgabe trotzdem beendet werden soll: Stellen Sie sich vor, mein onPostExecute speichert empfangene Daten und ruft dann einen Listener auf, um die Ansichten zu aktualisieren zurück. In diesem Fall mache ich normalerweise Folgendes:

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

2) Wenn ich möchte, dass die asynchrone Aufgabe nur beendet wird, wenn Ansichten aktualisiert werden können: In dem hier vorgeschlagenen Fall aktualisiert die Aufgabe nur die Ansichten, es ist kein Datenspeicher erforderlich, sodass keine Ahnung vorliegt, ob die Aufgabe beendet wird, wenn Ansichten vorhanden sind nicht mehr gezeigt. Ich mache das:

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

Ich habe damit kein Problem gefunden, obwohl ich auch eine (vielleicht) komplexere Methode verwende, die das Starten von Aufgaben aus der Aktivität anstelle der Fragmente umfasst.

Wünschte, das hilft jemandem! :)

24
luixal

Das Problem mit Ihrem Code ist die Art und Weise, wie Sie die AsyncTask verwenden, denn wenn Sie den Bildschirm während Ihres Ruhezustands drehen, geschieht Folgendes:

Thread.sleep(2000) 

die AsyncTask funktioniert noch, weil Sie die AsyncTask-Instanz in onDestroy () nicht ordnungsgemäß abgebrochen haben, bevor das Fragment neu erstellt wurde (beim Drehen), und wenn dieselbe AsyncTask-Instanz (nach dem Drehen) auf PostExecute () ausgeführt wird, wird versucht, dies zu finden die Ressourcen mit getResources () mit der alten Fragmentinstanz (eine ungültige Instanz):

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

was äquivalent ist zu:

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

Die endgültige Lösung besteht also darin, die AsyncTask-Instanz zu verwalten (abzubrechen, wenn dies noch funktioniert), bevor das Fragment beim Drehen des Bildschirms wiederhergestellt wird, und die AsyncTask nach der Rekonstruktion mithilfe eines Booleschen Flags erneut zu starten, wenn sie während des Übergangs abgebrochen wird:

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

    }
}
18

Das ist eine ziemlich trickreiche Lösung für dieses Problem und das Austreten von Fragmenten aus der Aktivität.

Im Fall von getResource oder irgendetwas anderem, das vom Aktivitätskontext abhängt, auf den von Fragment zugegriffen wird, wird immer der Aktivitätsstatus und der Fragmentstatus wie folgt überprüft

 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


        }
    }
17
Vinayak

Ich hatte das gleiche Problem, ich füge nur die Singleton-Instanz hinzu, um die Ressource zu erhalten, auf die Erick verwies

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

sie können auch verwenden

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

Ich hoffe das wird helfen.

10
Aristo Michael
if (getActivity() == null) return;

funktioniert auch in einigen fällen. Bricht einfach die Code-Ausführung ab und stellt sicher, dass die App nicht abstürzt

10
superUser

Ich hatte ähnliche Probleme, als die Aktivität der Anwendungseinstellungen mit den geladenen Einstellungen sichtbar wurde. Wenn ich eine der Einstellungen ändere und dann den Anzeigeinhalt drehen und die Einstellung erneut ändern würde, würde dies mit der Meldung abstürzen, dass das Fragment (meine Einstellungsklasse) keiner Aktivität angehängt wurde.

Beim Debuggen sah es so aus, als würde die onCreate () -Methode des PreferencesFragment zweimal aufgerufen, wenn der Anzeigeinhalt gedreht wurde. Das war schon seltsam genug. Dann habe ich das isAdded () - Häkchen außerhalb des Blocks hinzugefügt, wo es den Absturz anzeigt und das Problem löst.

Hier ist der Code des Listeners, der die Zusammenfassung der Einstellungen aktualisiert, um den neuen Eintrag anzuzeigen. Es befindet sich in der onCreate () -Methode meiner Preferences-Klasse, die die PreferenceFragment-Klasse erweitert:

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

Ich hoffe das wird anderen helfen!

2

Ein alter Beitrag, aber ich war überrascht über die am häufigsten gewählte Antwort.

Die richtige Lösung hierfür sollte darin bestehen, die asynchrone Aufgabe in onStop (oder wo immer dies in Ihrem Fragment angezeigt wird) abzubrechen. Auf diese Weise führen Sie keinen Speicherverlust ein (eine asynchrone Aufgabe, die einen Verweis auf Ihr zerstörtes Fragment enthält) und Sie haben eine bessere Kontrolle darüber, was in Ihrem Fragment vor sich geht.

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

Wenn Sie die Klasse Application wie folgt erweitern und ein statisches 'globales' Kontextobjekt verwalten, können Sie dies anstelle der Aktivität zum Laden einer Zeichenfolgenressource verwenden.

public class MyApplication extends Application {
    public static Context GLOBAL_APP_CONTEXT;

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

Wenn Sie dies verwenden, können Sie mit Toast und dem Laden von Ressourcen davonkommen, ohne sich Gedanken über den Lebenszyklus zu machen.

0

In meinem Fall wurden Fragmentmethoden nach aufgerufen

getActivity().onBackPressed();
0
CoolMind