Fragment MyFragment nie dołączony do aktywności

Stworzyłem małą aplikację testową, która reprezentuje mój problem. Używam ActionBarSherlock do implementacji tabulatorów z fragmentami (Sherlocka).

Mój kod: 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();
    }
}

Dodałem część Thread.sleep do symulacji pobierania danych. Kod w {[7] } ma symulować użycie Fragment.

Kiedy bardzo szybko obracam ekran między poziomym a pionowym, dostaję wyjątek w kodzie onPostExecute:

Java.lang.IllegalStateException: Fragment MyFragment{410f6060} not dołączony do aktywności

Myślę, że to dlatego, że nowy MyFragment został stworzony w międzyczasie i został dołączony do aktywności przed zakończeniem AsyncTask. Kod w onPostExecute wywołuje niezwiązany MyFragment.

Ale jak Mogę to naprawić?

Author: nhaarman, 2012-06-06

11 answers

Znalazłem bardzo prostą odpowiedź: isAdded():

Zwraca true jeśli fragment jest aktualnie dodany do jego aktywności.

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

Aby uniknąć wywołania onPostExecute, Gdy Fragment nie jest dołączony do Activity, należy anulować AsyncTask podczas zatrzymywania lub zatrzymywania Fragment. Wtedy isAdded() nie byłoby już konieczne. Zaleca się jednak utrzymanie tej kontroli na miejscu.

 724
Author: nhaarman,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-11-21 09:57:28

Miałem tu do czynienia z dwoma różnymi scenariuszami:

1) gdy chcę, aby zadanie asynchroniczne i tak się skończyło: wyobraź sobie, że mój onPostExecute przechowuje odebrane dane, a następnie wywołuje słuchacz, aby zaktualizować widoki, więc, aby być bardziej wydajnym, chcę, aby zadanie i tak się skończyło, więc mam dane gotowe, gdy użytkownik wraca. W tym przypadku zwykle robię to:

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

2) Gdy chcę, aby zadanie asynchroniczne kończyło się tylko wtedy, gdy widoki mogą być aktualizowane: przypadek, który tutaj proponujesz, zadanie aktualizuje tylko widoki, nie wymaga przechowywania danych, więc nie ma pojęcia, jak zakończyć zadanie, jeśli widoki nie są już wyświetlane. Robię to:

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

Nie znalazłem z tym problemu, chociaż używam również (być może) bardziej złożonego sposobu, który obejmuje uruchamianie zadań z aktywności zamiast fragmentów.

Szkoda, że to komuś pomaga! :)

 24
Author: luixal,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-04-24 10:13:35

Problem polega na tym, że próbujesz uzyskać dostęp do zasobów (w tym przypadku łańcuchów) za pomocą getResources ().getString (), który będzie próbował uzyskać zasoby z aktywności. Zobacz ten kod źródłowy klasy 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 jest obiektem, który przechowuje Twoją aktywność.

Ponieważ aktywność może nie być dołączona, funkcja getResources() wywoła wyjątek.

Zaakceptowane rozwiązanie IMHO nie jest drogą do zrobienia, ponieważ po prostu ukrywasz problem. The correct way jest po prostu, aby uzyskać zasoby z innego miejsca, które jest zawsze gwarantowane, aby istnieć, jak kontekst aplikacji: {]}

youApplicationObject.getResources().getString(...)
 21
Author: Tiago,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-06-30 21:00:55

Problem z kodem polega na tym, jak używasz Asynctasku, ponieważ gdy obracasz ekran podczas snu wątku:

Thread.sleep(2000) 

AsyncTask nadal działa, ponieważ nie anulowałeś poprawnie instancji AsyncTask w ondestroy () przed przebudowaniem fragmentu (gdy obracasz) i kiedy ta sama instancja AsyncTask (po rotacji) uruchomi onpostexecute (), to próbuje znaleźć zasoby za pomocą getResources () ze starą instancją fragmentu(nieprawidłową instancją):

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

Co jest równoważne:

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

Więc ostatecznym rozwiązaniem jest zarządzanie instancją AsyncTask (aby anulować, jeśli nadal działa) przed przebudową fragmentu po obróceniu ekranu, a jeśli anulowane podczas przejścia, uruchom ponownie AsyncTask po rekonstrukcji za pomocą flagi logicznej:

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
Author: Erick Reátegui Diaz,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-05-30 15:57:08

Ich są dość podstępne rozwiązanie tego i wyciek fragmentu z aktywności.

Tak więc w przypadku getResource lub czegokolwiek, co zależy od kontekstu aktywności dostępnego z fragmentu, zawsze sprawdza status aktywności i status fragmentów w następujący sposób

 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


        }
    }
 14
Author: Vinayak,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-09-02 09:44:26

Napotkałem ten sam problem, po prostu dodałem instancję singletone, aby uzyskać zasób, o którym mowa przez Ericka

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

Możesz również użyć

getActivity().getResources().getString(R.string.app_name);
Mam nadzieję, że to pomoże.
 10
Author: Aristo Michael,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-08-27 15:03:47
if (getActivity() == null) return;

Działa również w niektórych przypadkach. Po prostu przerywa wykonywanie kodu i upewnij się, że aplikacja się nie zawiesi

 6
Author: superUser,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-08-17 19:55:26

Miałem do czynienia z podobnymi problemami, gdy aktywność ustawień aplikacji z załadowanymi preferencjami była widoczna. Jeśli zmieniłbym jedną z preferencji, a następnie zmieniłbym zawartość wyświetlacza i ponownie zmieniał preferencje, zawiesiłoby się z wiadomością, że fragment (moja klasa preferencji) nie został dołączony do aktywności.

Podczas debugowania wyglądało to tak, jakby metoda OnCreate() PreferencesFragment była wywoływana dwukrotnie, gdy zawartość wyświetlacza się obróciła. To było dziwne. wystarczy. Następnie dodałem sprawdzanie isAdded() poza blokiem, gdzie wskazywałoby awarię i rozwiązało problem.

Oto kod słuchacza, który aktualizuje podsumowanie preferencji, aby pokazać nowy wpis. Znajduje się on w metodzie OnCreate () klasy moje preferencje, która rozszerza klasę 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");
                    }
                }
            }
        };
        // ...
    }
Mam nadzieję, że to pomoże innym!
 2
Author: ohgodnotanotherone,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-07-10 21:37:15

Jeśli rozszerzysz klasę Application i utrzymasz statyczny' globalny ' obiekt kontekstowy, w następujący sposób, możesz użyć tego zamiast aktywności do załadowania zasobu Łańcuchowego.

public class MyApplication extends Application {
    public static Context GLOBAL_APP_CONTEXT;

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

Jeśli tego użyjesz, możesz uciec od Toast i ładowania zasobów bez martwienia się o cykle życia.

 0
Author: Anthony Chuinard,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-06-08 22:39:41

W moim przypadku metody fragmentowe zostały wywołane po

getActivity().onBackPressed();
 0
Author: CoolMind,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-10-18 12:33:09

Stary post, ale zaskoczyła mnie najbardziej głosowana odpowiedź.

Właściwym rozwiązaniem powinno być anulowanie asynctask w onStop (lub, gdzie to stosowne, w Twoim fragmencie). W ten sposób nie wprowadzasz wycieku pamięci (asynctask zachowujący odniesienie do zniszczonego fragmentu) i masz lepszą kontrolę nad tym, co dzieje się w Twoim fragmencie.

@Override
public void onStop() {
    super.onStop();
    mYourAsyncTask.cancel(true);
}
 0
Author: Raz,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-01-20 21:13:04