Fragment MyFragment non attaché à Activity

J’ai créé une petite application de test qui représente mon problème. J’utilise ActionBarSherlock pour implémenter des tabs avec des fragments (Sherlock).

Mon 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"); Ssortingng tabText = "1"; tab1.setText(tabText); tab1.setTabListener(new TabListener(TestActivity.this, "1", MyFragment.class)); actionBar.addTab(tab1); } private void addTab2() { Tab tab1 = actionBar.newTab(); tab1.setTag("2"); Ssortingng tabText = "2"; tab1.setText(tabText); tab1.setTabListener(new TabListener(TestActivity.this, "2", MyFragment.class)); actionBar.addTab(tab1); } } 

TabListener.java

 public class TabListener implements com.actionbarsherlock.app.ActionBar.TabListener { private final SherlockFragmentActivity mActivity; private final Ssortingng mTag; private final Class mClass; public TabListener(SherlockFragmentActivity activity, Ssortingng tag, Class 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() { @Override protected Void doInBackground(Void... params) { try { Thread.sleep(2000); } catch (InterruptedException ex) { } return null; } @Override protected void onPostExecute(Void result){ getResources().getSsortingng(R.ssortingng.app_name); } }.execute(); } } 

J’ai ajouté la partie Thread.sleep pour simuler le téléchargement de données. Le code dans onPostExecute consiste à simuler l’utilisation du Fragment .

Lorsque je fais pivoter l’écran très rapidement entre paysage et portrait, j’obtiens une exception au code onPostExecute :

java.lang.IllegalStateException: Fragment MyFragment {410f6060} non attaché à Activity

Je pense que c’est parce qu’un nouveau MyFragment a été créé entre-temps et a été attaché à l’activité avant la AsyncTask la AsyncTask . Le code dans onPostExecute appelle un MyFragment non MyFragment .

Mais comment puis-je résoudre ce problème?

J’ai trouvé la réponse très simple: isAdded() :

Renvoie true si le fragment est actuellement ajouté à son activité.

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

Pour éviter que onPostExecute soit appelé lorsque le Fragment n’est pas associé à l’ Activity faut annuler la AsyncTask lors de la pause ou de l’arrêt du Fragment . Ensuite, isAdded() ne serait plus nécessaire. Cependant, il est conseillé de garder cette vérification en place.

J’ai fait face à deux scénarios différents ici:

1) Quand je veux que la tâche asynchrone se termine de toute façon: imaginez que mon onPostExecute stocke les données reçues et appelle ensuite un auditeur pour mettre à jour les vues afin, pour être plus efficace, que la tâche se termine de toute façon. arrière. Dans ce cas, je le fais habituellement:

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

2) Lorsque je souhaite que la tâche asynchrone ne se termine que lorsque les vues peuvent être mises à jour: le cas que vous proposez ici, la tâche ne met à jour que les vues, aucun stockage de données nécessaire. ne plus être montré. Je fais ça:

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

Je n’ai trouvé aucun problème avec cela, bien que j’utilise également un moyen (peut-être) plus complexe qui consiste à lancer des tâches à partir de l’activité au lieu des fragments.

Souhaite que cela aide quelqu’un! 🙂

Le problème est que vous essayez d’accéder aux ressources (dans ce cas, des chaînes) en utilisant getResources (). GetSsortingng (), qui tentera d’obtenir les ressources de l’activité. Voir ce code source de la classe Fragment:

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

mHost est l’object qui contient votre activité.

Étant donné que l’activité peut ne pas être attachée, votre appel getResources () lancera une exception.

La solution acceptée IMHO n’est pas la solution, car vous ne faites que cacher le problème. La manière correcte consiste simplement à obtenir les ressources d’un autre endroit qui sont toujours garanties, comme le contexte de l’application:

 youApplicationObject.getResources().getSsortingng(...) 

Le problème avec votre code est la manière dont vous utilisez AsyncTask, car lorsque vous faites pivoter l’écran pendant votre thread de veille:

 Thread.sleep(2000) 

le AsyncTask fonctionne toujours, c’est parce que vous n’avez pas annulé correctement l’instance AsyncTask dans onDestroy () avant la reconstruction du fragment (lorsque vous faites pivoter) et que cette même instance AsyncTask (après la rotation) exécute onPostExecute (), les ressources avec getResources () avec l’ancienne instance de fragment (une instance non valide):

 getResources().getSsortingng(R.ssortingng.app_name) 

ce qui équivaut à:

 MyFragment.this.getResources().getSsortingng(R.ssortingng.app_name) 

Donc, la solution finale est de gérer l’instance AsyncTask (pour annuler si cela fonctionne toujours) avant que le fragment ne se reconstruise lorsque vous faites pivoter l’écran, et s’il est annulé pendant la transition, redémarrez AsyncTask après reconstruction à l’aide d’un indicateur booléen:

 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() { 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().getSsortingng(R.ssortingng.app_name); myAsyncTaskIsRunning = false; myAsyncTask = null; } } } 

Leur solution est assez compliquée pour cela et la fuite de fragment d’activité.

Ainsi, dans le cas de getResource ou de tout élément dépendant du contexte d’activités accédant à partir de Fragment, il faut toujours vérifier le statut de l’activité et l’état des fragments comme suit.

  Activity activity = getActivity(); if(activity != null && isAdded()) getResources().getSsortingng(R.ssortingng.no_internet_error_msg); //Or any other depends on activity context to be live like dailog } } 

J’ai fait face au même problème, je viens d’append l’instance singletone pour obtenir des ressources comme indiqué par Erick

 MainFragmentActivity.defaultInstance().getResources().getSsortingng(R.ssortingng.app_name); 

vous pouvez aussi utiliser

 getActivity().getResources().getSsortingng(R.ssortingng.app_name); 

J’espère que cela aidera.

 if (getActivity() == null) return; 

fonctionne aussi dans certains cas. Il suffit d’interrompre l’exécution du code et de s’assurer que l’application ne plante pas

J’ai rencontré des problèmes similaires lorsque l’activité des parameters de l’application avec les préférences chargées était visible. Si je modifiais l’une des préférences et que je fais ensuite pivoter le contenu de l’écran et que je modifie à nouveau la préférence, le message suivant s’affiche: le fragment (ma classe Preferences) n’était pas associé à une activité.

Lors du débogage, la méthode onCreate () du fichier PreferencesFragment était appelée deux fois lors de la rotation du contenu de l’affichage. C’était déjà étrange. Ensuite, j’ai ajouté la vérification isAdded () en dehors du bloc où elle indiquerait le plantage et le problème résolu.

Voici le code de l’écouteur qui met à jour le résumé des préférences pour afficher la nouvelle entrée. Il se trouve dans la méthode onCreate () de ma classe Preferences qui étend 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, Ssortingng 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(getSsortingng(R.ssortingng.pref_fileviewer_textsize))) { ListPreference listPref = (ListPreference) findPreference(key); listPref.setSummary("Display file content with a text size of " + listPref.getEntry()); } else if (key.equals(getSsortingng(R.ssortingng.pref_fileviewer_segmentsize))) { ListPreference listPref = (ListPreference) findPreference(key); listPref.setSummary("Show " + listPref.getEntry() + " bytes of a file at once"); } } } }; // ... } 

J’espère que cela aidera les autres!

Si vous étendez la classe Application et gérez un object Contexte ‘global’ statique, procédez comme suit, vous pouvez l’utiliser plutôt que l’activité pour charger une ressource Ssortingng.

 public class MyApplication extends Application { public static Context GLOBAL_APP_CONTEXT; @Override public void onCreate() { super.onCreate(); GLOBAL_APP_CONTEXT = this; } } 

Si vous utilisez ceci, vous pouvez vous débrouiller avec Toast et le chargement des ressources sans vous soucier des cycles de vie.

Dans mon cas, les méthodes de fragment ont été appelées après

 getActivity().onBackPressed(); 

Un ancien message, mais j’ai été surpris par la réponse la plus élue.

La solution appropriée devrait être d’annuler le masque asynchrone dans onStop (ou, le cas échéant, dans votre fragment). De cette façon, vous n’introduisez pas de fuite de mémoire (un asynctask contenant une référence à votre fragment détruit) et vous contrôlez mieux ce qui se passe dans votre fragment.

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