Stworzyłem małą aplikację testową, która reprezentuje mój problem. Używam ActionBarSherlock do implementacji zakładek z fragmentami (Sherlock).
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 Thread.sleep
część do symulacji pobierania danych. Kod w to onPostExecute
ma symulować użycie Fragment
.
Kiedy bardzo szybko obracam ekran między poziomym a pionowym, pojawia się wyjątek w onPostExecute
kodzie:
java.lang.IllegalStateException: Fragment MyFragment {410f6060} nie jest dołączony do działania
Myślę, że dzieje się tak, ponieważ MyFragment
w międzyczasie utworzono nowy i został on dołączony do działania przed AsyncTask
zakończeniem. Kod onPostExecute
wywołuje nieprzyłączony MyFragment
.
Ale jak mogę to naprawić?
android
android-fragments
actionbarsherlock
nhaarman
źródło
źródło
mView = inflater.inflate(R.layout.my_layout, container, false)
A teraz użyć tego widoku, gdy chcesz uzyskać zasobów:mView.getResources().***
. Pomaga mi naprawić ten błąd.Context
który jest dołączony do twojego `mView`.mView
zerowanie w usłudze Destroy?Odpowiedzi:
Znalazłem bardzo prostą odpowiedź
isAdded()
:Aby uniknąć
onPostExecute
wywoływania, gdyFragment
nie jest on dołączony do,Activity
należy anulowaćAsyncTask
przerwanie lub zatrzymanieFragment
. WtedyisAdded()
nie byłoby już konieczne. Jednak wskazane jest, aby utrzymać tę kontrolę na miejscu.źródło
isDetached()
, że został dodany na poziomie API 13Problem polega na tym, że próbujesz uzyskać dostęp do zasobów (w tym przypadku ciągów znaków) za pomocą getResources (). GetString (), który spróbuje uzyskać zasoby z działania. Zobacz ten kod źródłowy klasy Fragment:
mHost
jest przedmiotem, który trzyma twoją aktywność.Ponieważ działanie może nie zostać dołączone, wywołanie getResources () spowoduje zgłoszenie wyjątku.
Przyjęte rozwiązanie IMHO nie jest dobrym rozwiązaniem, ponieważ tylko ukrywasz problem. Prawidłowym sposobem jest po prostu zdobycie zasobów z innego miejsca, które zawsze istnieje, na przykład kontekst aplikacji:
źródło
getString()
gdy mój fragment został wstrzymany. DziękiMam tu do czynienia z dwoma różnymi scenariuszami:
1) Gdy chcę mimo to zakończyć zadanie asynchroniczne: wyobraź sobie, że mój onPostExecute przechowuje odebrane dane, a następnie wywołuje słuchacza w celu zaktualizowania widoków, aby być bardziej wydajnym, chcę, aby zadanie i tak zakończyło się, więc mam dane gotowe, gdy użytkownik zadzwoni plecy. W takim przypadku zwykle robię to:
2) Gdy chcę, aby zadanie asynchroniczne zakończyło się tylko wtedy, gdy widoki mogą zostać zaktualizowane: w przypadku, gdy proponujesz tutaj, zadanie aktualizuje tylko widoki, nie wymaga przechowywania danych, więc nie ma pojęcia, że zadanie może zakończyć się, jeśli widoki są nie jest już pokazywany. Robię to:
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 działania zamiast fragmentów.
Szkoda, że to komuś pomaga! :)
źródło
Problem z kodem polega na sposobie korzystania z AsyncTask, ponieważ podczas obracania ekranu podczas wątku uśpienia:
AsyncTask nadal działa, ponieważ nie anulowałeś poprawnie instancji AsyncTask w onDestroy () przed odbudowaniem fragmentu (po obróceniu), a gdy ta sama instancja AsyncTask (po obróceniu) działa na PostPostExecute (), to próbuje znaleźć zasoby za pomocą getResources () ze starym wystąpieniem fragmentu (niepoprawne wystąpienie):
co jest równoważne z:
Tak więc ostatecznym rozwiązaniem jest zarządzanie instancją AsyncTask (aby anulować, jeśli nadal działa), zanim fragment zostanie odbudowany po obróceniu ekranu, a jeśli zostanie anulowany podczas przejścia, zrestartuj AsyncTask po rekonstrukcji za pomocą flagi logicznej:
źródło
getResources().***
korzystasz zFragments.this.getResource().***
pomocyIch rozwiązanie jest dość trudne i wyciek fragmentu z działalności.
Tak więc w przypadku getResource lub czegokolwiek, co jest zależne od kontekstu aktywności uzyskującego dostęp z Fragmentu, zawsze sprawdza się status aktywności i status fragmentów w następujący sposób
źródło
isAdded
wystarczy. Nigdy nie widziałem sytuacji, w którejgetString()
się rozbiłisAdded == true
. Czy na pewno pokazano aktywność i dołączono fragment?działa również w niektórych przypadkach. Po prostu przerywa wykonanie kodu i upewnia się, że aplikacja nie ulega awarii
źródło
Napotkałem ten sam problem, po prostu dodałem instancję singletone, aby uzyskać zasób, o którym wspomniał Erick
możesz także użyć
Mam nadzieję, że to pomoże.
źródło
Napotkałem podobne problemy, gdy aktywność ustawień aplikacji z załadowanymi preferencjami była widoczna. Gdybym zmienił jedną z preferencji, a następnie sprawiłby, że wyświetlana zawartość zostanie obrócona i ponownie zmieniona, spowodowałoby to awarię z komunikatem, że fragment (moja klasa Preferencji) nie został dołączony do działania.
Podczas debugowania wyglądało to tak, jakby metoda onCreate () metody PreferencesFragment była wywoływana dwukrotnie podczas obracania wyświetlanej treści. To już było dość dziwne. Następnie dodałem kontrolę isAdded () poza blokiem, która wskazywałaby awarię i rozwiązała problem.
Oto kod detektora, który aktualizuje podsumowanie preferencji, aby pokazać nowy wpis. Znajduje się w metodzie onCreate () mojej klasy Preferences, która rozszerza klasę PreferenceFragment:
Mam nadzieję, że to pomoże innym!
źródło
Jeśli rozszerzysz
Application
klasę i utrzymasz statyczny „globalny” obiekt kontekstu w następujący sposób, możesz użyć go zamiast działania do załadowania zasobu String.Jeśli go użyjesz, możesz uciec
Toast
i ładować zasoby bez martwienia się o cykle życia.źródło
W moim przypadku wywołano metody fragmentów
źródło
Stary post, ale byłem zaskoczony najbardziej pozytywnie ocenioną odpowiedzią.
Właściwym rozwiązaniem tego problemu powinno być anulowanie asynchroniki w programie onStop (lub w dowolnym miejscu w swoim fragmencie). W ten sposób nie wprowadzasz przecieku pamięci (asynctask z odniesieniem do twojego zniszczonego fragmentu) i masz lepszą kontrolę nad tym, co dzieje się w twoim fragmencie.
źródło
cancel
nie może uniemożliwićonPostExecute
wywołania.