429 Stimmen

Fragment MyFragment nicht mit Aktivität verbunden

Ich habe eine kleine Testanwendung 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 die Thread.sleep Teil, um das Herunterladen von Daten zu simulieren. Der Code im Teil onPostExecute ist die Simulation der Nutzung des Fragment .

Wenn ich den Bildschirm sehr schnell zwischen Hoch- und Querformat drehe, erhalte ich eine Exception bei der onPostExecute Code:

java.lang.IllegalStateException: Fragment MyFragment mit Aktivität verbunden

Ich glaube, das liegt daran, dass eine neue MyFragment wurde in der Zwischenzeit erstellt und wurde der Aktivität vor der AsyncTask beendet. Der Code in onPostExecute ruft einen ungebundenen MyFragment .

Aber wie kann ich das beheben?

834voto

nhaarman Punkte 94842

Ich habe eine sehr einfache Antwort gefunden: [isAdded()](http://developer.android.com/reference/android/app/Fragment.html#isAdded()) :

Rückkehr true wenn das Fragment gerade zu seiner Aktivität hinzugefügt wird.

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

Zur Vermeidung von onPostExecute nicht aufgerufen wird, wenn die Fragment ist nicht mit dem Activity ist die Annullierung der AsyncTask beim Pausieren oder Stoppen des Fragment . Dann isAdded() wäre nicht mehr notwendig. Es ist jedoch ratsam, diese Kontrolle beizubehalten.

36voto

Das Problem besteht darin, dass Sie versuchen, mit getResources().getString() auf Ressourcen (in diesem Fall Strings) zuzugreifen, wodurch versucht wird, die Ressourcen von der Activity zu erhalten. Siehe diesen Quellcode der Klasse 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 ist das Objekt, das Ihre Aktivität enthält.

Da die Activity möglicherweise nicht angehängt ist, wird Ihr getResources()-Aufruf eine Exception auslösen.

Die akzeptierte Lösung ist IMHO nicht der richtige Weg, da Sie das Problem nur verstecken. Der richtige Weg ist, die Ressourcen von einem anderen Ort zu beziehen, der garantiert immer vorhanden ist, wie der Anwendungskontext:

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

26voto

luixal Punkte 906

Ich bin hier mit zwei verschiedenen Szenarien konfrontiert worden:

1) Wenn ich möchte, dass die asynchrone Aufgabe sowieso beendet wird: Stellen Sie sich vor, dass mein onPostExecute die empfangenen Daten speichert und dann einen Listener aufruft, um die Ansichten zu aktualisieren, so dass ich, um effizienter zu sein, möchte, dass die Aufgabe sowieso beendet wird, damit ich die Daten bereit habe, wenn der Benutzer zurückkommt. 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 beenden, wenn Ansichten aktualisiert werden können: der Fall, den Sie hier vorschlagen, die Aufgabe aktualisiert nur die Ansichten, keine Datenspeicherung erforderlich, so dass es keinen Anhaltspunkt für die Aufgabe zu beenden, wenn Ansichten nicht mehr angezeigt werden. Ich tue dies:

@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 keine Probleme damit, obwohl ich auch eine (vielleicht) komplexere Methode verwende, die das Starten von Aufgaben aus der Aktivität anstelle der Fragmente beinhaltet.

Ich hoffe, das hilft jemandem! :)

24voto

Vinayak Punkte 5668

Hierfür gibt es ziemlich trickreiche Lösungen und das Entweichen von Fragmenten aus der Aktivität.

Also im Falle von getResource oder irgendetwas, das vom Aktivitätskontext abhängt, der von einem Fragment aus zugreift, 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

        }
    }

18voto

Das Problem mit Ihrem Code ist die Art und Weise, die Sie die AsyncTask verwenden, denn wenn Sie den Bildschirm während Ihres Sleep-Threads drehen:

Thread.sleep(2000) 

die AsyncTask noch funktioniert, liegt es daran, dass Sie die AsyncTask-Instanz nicht ordnungsgemäß in onDestroy() abgebrochen haben, bevor das Fragment neu aufgebaut wird (wenn Sie rotieren) und wenn dieselbe AsyncTask-Instanz (nach dem Rotieren) onPostExecute() ausführt, versucht diese, die Ressourcen mit getResources() mit der alten Fragment-Instanz (einer ungültigen Instanz) zu finden:

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

was gleichbedeutend ist mit:

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

Die endgültige Lösung besteht also darin, die AsyncTask-Instanz zu verwalten (um abzubrechen, wenn sie noch funktioniert), bevor das Fragment beim Drehen des Bildschirms neu aufgebaut wird, und, falls der Vorgang während des Übergangs abgebrochen wird, die AsyncTask nach dem Wiederaufbau mit Hilfe eines booleschen Flags neu zu starten:

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

    }
}

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X