J'ai créé une petite application de test qui représente mon problème. J'utilise ActionBarSherlock pour implémenter des onglets 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");
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();
}
}
J'ai ajouté la Thread.sleep
partie pour simuler le téléchargement des données. Le code dans le onPostExecute
est de simuler l'utilisation du Fragment
.
Lorsque je fais pivoter l'écran très rapidement entre paysage et portrait, j'obtiens une exception au onPostExecute
code:
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
fin. Le code dans fait onPostExecute
appel à un non attaché MyFragment
.
Mais comment puis-je résoudre ce problème?
mView = inflater.inflate(R.layout.my_layout, container, false)
Et maintenant utiliser ce point de vue lorsque vous souhaitez obtenir des ressources:mView.getResources().***
. Cela m'aide à corriger ce bogue.Context
qui est attaché à votre `mView`.mView
dans onDestroy?Réponses:
J'ai trouvé la réponse très simple
isAdded()
:Pour éviter
onPostExecute
d'être appelé lorsque leFragment
n'est pas attaché auActivity
est d'annuler leAsyncTask
lorsque vous mettez en pause ou arrêtez leFragment
. AlorsisAdded()
ce ne serait plus nécessaire. Cependant, il est conseillé de garder ce contrôle en place.la source
isDetached()
, qui a été ajouté au niveau API 13Le problème est que vous essayez d'accéder aux ressources (dans ce cas, les chaînes) à l'aide de getResources (). GetString (), qui tentera d'obtenir les ressources de l'activité. Voir ce code source de la classe Fragment:
mHost
est l'objet qui détient votre activité.Parce que l'activité peut ne pas être attachée, votre appel getResources () lèvera une exception.
La solution acceptée à mon humble avis n'est pas la voie à suivre car vous cachez simplement le problème. La bonne façon est simplement d'obtenir les ressources d'un autre endroit qui est toujours garanti d'exister, comme le contexte de l'application:
la source
getString()
lorsque mon fragment était en pause. MerciJ'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, puis appelle un écouteur pour mettre à jour les vues donc, pour être plus efficace, je veux que la tâche se termine quand même afin que les données soient prêtes lorsque l'utilisateur arrive retour. Dans ce cas, je fais généralement ceci:
2) Lorsque je veux que la tâche asynchrone se termine uniquement 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, donc il n'y a aucun indice pour que la tâche se termine si les vues sont ne plus être montré. Je fais ça:
Je n'ai trouvé aucun problème avec cela, bien que j'utilise également une méthode (peut-être) plus complexe qui inclut le lancement de tâches à partir de l'activité au lieu des fragments.
Je souhaite que cela aide quelqu'un! :)
la source
Le problème avec votre code est la façon dont vous utilisez AsyncTask, car lorsque vous faites pivoter l'écran pendant votre thread de sommeil:
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 lorsque cette même instance AsyncTask (après rotation) s'exécute sur PostExecute (), cela essaie de trouver les ressources avec getResources () avec l'ancienne instance de fragment (une instance non valide):
ce qui équivaut à:
La solution finale consiste donc à gérer l'instance AsyncTask (pour annuler si cela fonctionne toujours) avant la reconstruction du fragment lorsque vous faites pivoter l'écran, et si elle est annulée pendant la transition, redémarrez AsyncTask après la reconstruction à l'aide d'un indicateur booléen:
la source
getResources().***
vousFragments.this.getResource().***
Ils sont une solution assez astucieuse pour cela et une fuite de fragment de l'activité.
Donc, dans le cas de getResource ou de tout ce qui dépend du contexte d'activité accédant à partir de Fragment, il faut toujours vérifier l'état de l'activité et l'état des fragments comme suit
la source
isAdded
c'est assez. Je n'ai jamais vu une situation oùgetString()
s'était écrasé siisAdded == true
. Êtes-vous sûr qu'une activité a été montrée et qu'un fragment a été attaché?fonctionne également dans certains cas. Interrompt juste l'exécution du code et assurez-vous que l'application ne plante pas
la source
J'ai fait face au même problème que j'ajoute juste l'instance de monotone pour obtenir la ressource comme indiqué par Erick
vous pouvez aussi utiliser
J'espère que cela vous aidera.
la source
J'ai rencontré des problèmes similaires lorsque l'activité des paramètres de l'application avec les préférences chargées était visible. Si je modifiais l'une des préférences, puis fais pivoter le contenu d'affichage et modifiais à nouveau la préférence, il se plantait avec un message indiquant que le fragment (ma classe Préférences) n'était pas attaché à une activité.
Lors du débogage, il semblait que la méthode onCreate () du PreferencesFragment était appelée deux fois lorsque le contenu de l'affichage pivotait. C'était déjà assez étrange. Ensuite, j'ai ajouté la vérification isAdded () en dehors du bloc où cela indiquerait le crash et cela a résolu le problème.
Voici le code de l'auditeur qui met à jour le résumé des préférences pour afficher la nouvelle entrée. Il est situé dans la méthode onCreate () de ma classe Preferences qui étend la classe PreferenceFragment:
J'espère que cela aidera les autres!
la source
Si vous étendez la
Application
classe et maintenez un objet contextuel «global» statique, comme suit, vous pouvez l'utiliser à la place de l'activité pour charger une ressource String.Si vous l'utilisez, vous pouvez vous en sortir
Toast
et charger les ressources sans vous soucier des cycles de vie.la source
Dans mon cas, des méthodes de fragment ont été appelées après
la source
Un vieux post, mais j'ai été surpris de la réponse la plus votée.
La bonne solution pour cela devrait être d'annuler la tâche asynctrone dans onStop (ou, le cas échéant, dans votre fragment). De cette façon, vous n'introduisez pas de fuite de mémoire (un asynctasque gardant une référence à votre fragment détruit) et vous avez un meilleur contrôle de ce qui se passe dans votre fragment.
la source
cancel
ne peut pas empêcheronPostExecute
d'être invoqué.