DERNIÈRES INFORMATIONS:
J'ai réduit mon problème à un problème avec le fragmentManager conservant des instances d'anciens fragments et mon viewpager étant désynchronisé avec mon FragmentManager. Consultez ce problème ... http://code.google.com/p/android/issues/detail?id=19211#makechanges . Je n'ai toujours aucune idée de comment résoudre ce problème. Aucune suggestion...
J'ai essayé de déboguer ceci pendant une longue période et toute aide serait grandement appréciée. J'utilise un FragmentPagerAdapter qui accepte une liste de fragments comme ceci:
List<Fragment> fragments = new Vector<Fragment>();
fragments.add(Fragment.instantiate(this, Fragment1.class.getName()));
...
new PagerAdapter(getSupportFragmentManager(), fragments);
La mise en œuvre est standard. J'utilise ActionBarSherlock et la bibliothèque de calculabilité v4 pour les fragments.
Mon problème est qu'après avoir quitté l'application et ouvert plusieurs autres applications et y être revenu, les fragments perdent leur référence à FragmentActivity (c'est-à-dire. getActivity() == null
). Je ne peux pas comprendre pourquoi cela se produit. J'ai essayé de définir manuellement setRetainInstance(true);
mais cela n'aide pas. J'ai pensé que cela se produit lorsque mon FragmentActivity est détruit, mais cela se produit toujours si j'ouvre l'application avant de recevoir le message de journal. Y a-t-il des idées?
@Override
protected void onDestroy(){
Log.w(TAG, "DESTROYDESTROYDESTROYDESTROYDESTROYDESTROYDESTROY");
super.onDestroy();
}
L'adaptateur:
public class PagerAdapter extends FragmentPagerAdapter {
private List<Fragment> fragments;
public PagerAdapter(FragmentManager fm, List<Fragment> fragments) {
super(fm);
this.fragments = fragments;
}
@Override
public Fragment getItem(int position) {
return this.fragments.get(position);
}
@Override
public int getCount() {
return this.fragments.size();
}
}
Un de mes fragments a été dépouillé mais j'ai tout fait disparaître qui est dépouillé et ne fonctionne toujours pas ...
public class MyFragment extends Fragment implements MyFragmentInterface, OnScrollListener {
...
@Override
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
handler = new Handler();
setHasOptionsMenu(true);
}
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
Log.w(TAG,"ATTACHATTACHATTACHATTACHATTACH");
context = activity;
if(context== null){
Log.e("IS NULL", "NULLNULLNULLNULLNULLNULLNULLNULLNULLNULLNULL");
}else{
Log.d("IS NOT NULL", "NOTNOTNOTNOTNOTNOTNOTNOT");
}
}
@Override
public void onActivityCreated(Bundle savedState) {
super.onActivityCreated(savedState);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.my_fragment,container, false);
return v;
}
@Override
public void onResume(){
super.onResume();
}
private void callService(){
// do not call another service is already running
if(startLoad || !canSet) return;
// set flag
startLoad = true;
canSet = false;
// show the bottom spinner
addFooter();
Intent intent = new Intent(context, MyService.class);
intent.putExtra(MyService.STATUS_RECEIVER, resultReceiver);
context.startService(intent);
}
private ResultReceiver resultReceiver = new ResultReceiver(null) {
@Override
protected void onReceiveResult(int resultCode, final Bundle resultData) {
boolean isSet = false;
if(resultData!=null)
if(resultData.containsKey(MyService.STATUS_FINISHED_GET)){
if(resultData.getBoolean(MyService.STATUS_FINISHED_GET)){
removeFooter();
startLoad = false;
isSet = true;
}
}
switch(resultCode){
case MyService.STATUS_FINISHED:
stopSpinning();
break;
case SyncService.STATUS_RUNNING:
break;
case SyncService.STATUS_ERROR:
break;
}
}
};
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
menu.clear();
inflater.inflate(R.menu.activity, menu);
}
@Override
public void onPause(){
super.onPause();
}
public void onScroll(AbsListView arg0, int firstVisible, int visibleCount, int totalCount) {
boolean loadMore = /* maybe add a padding */
firstVisible + visibleCount >= totalCount;
boolean away = firstVisible+ visibleCount <= totalCount - visibleCount;
if(away){
// startLoad can now be set again
canSet = true;
}
if(loadMore)
}
public void onScrollStateChanged(AbsListView arg0, int state) {
switch(state){
case OnScrollListener.SCROLL_STATE_FLING:
adapter.setLoad(false);
lastState = OnScrollListener.SCROLL_STATE_FLING;
break;
case OnScrollListener.SCROLL_STATE_IDLE:
adapter.setLoad(true);
if(lastState == SCROLL_STATE_FLING){
// load the images on screen
}
lastState = OnScrollListener.SCROLL_STATE_IDLE;
break;
case OnScrollListener.SCROLL_STATE_TOUCH_SCROLL:
adapter.setLoad(true);
if(lastState == SCROLL_STATE_FLING){
// load the images on screen
}
lastState = OnScrollListener.SCROLL_STATE_TOUCH_SCROLL;
break;
}
}
@Override
public void onDetach(){
super.onDetach();
if(this.adapter!=null)
this.adapter.clearContext();
Log.w(TAG, "DETACHEDDETACHEDDETACHEDDETACHEDDETACHEDDETACHED");
}
public void update(final int id, String name) {
if(name!=null){
getActivity().getSupportActionBar().setTitle(name);
}
}
}
La méthode de mise à jour est appelée lorsqu'un utilisateur interagit avec un fragment différent et que getActivity renvoie null. Voici la méthode que l'autre fragment appelle ...
((MyFragment) pagerAdapter.getItem(1)).update(id, name);
Je pense que lorsque l'application est détruite puis créée à nouveau au lieu de simplement démarrer l'application jusqu'au fragment par défaut, l'application démarre, puis viewpager accède à la dernière page connue. Cela semble étrange, l'application ne devrait-elle pas simplement se charger dans le fragment par défaut?
la source
Réponses:
Vous rencontrez un problème car vous instanciez et gardez des références à vos fragments en dehors de
PagerAdapter.getItem
, et essayez d'utiliser ces références indépendamment du ViewPager. Comme le dit Seraph, vous avez la garantie qu'un fragment a été instancié / ajouté dans un ViewPager à un moment donné - cela devrait être considéré comme un détail d'implémentation. Un ViewPager effectue le chargement paresseux de ses pages; par défaut, il ne charge que la page actuelle et celle de gauche et de droite.Si vous mettez votre application en arrière-plan, les fragments qui ont été ajoutés au gestionnaire de fragments sont enregistrés automatiquement. Même si votre application est tuée, ces informations sont restaurées lorsque vous relancez votre application.
Considérez maintenant que vous avez vu quelques pages, Fragments A, B et C. Vous savez que celles-ci ont été ajoutées au gestionnaire de fragments. Parce que vous utilisez
FragmentPagerAdapter
et nonFragmentStatePagerAdapter
, ces fragments seront toujours ajoutés (mais potentiellement détachés) lorsque vous faites défiler vers d'autres pages.Considérez que vous avez ensuite l'arrière-plan de votre application, puis elle est tuée. Lorsque vous reviendrez, Android se souviendra que vous aviez les fragments A, B et C dans le gestionnaire de fragments et qu'il les recrée pour vous, puis les ajoute.Cependant, ceux qui sont maintenant ajoutés au gestionnaire de fragments ne sont PAS ceux que vous avez dans votre liste de fragments dans votre activité.
Le FragmentPagerAdapter n'essaiera pas d'appeler
getPosition
s'il y a déjà un fragment ajouté pour cette position de page particulière. En fait, comme le fragment recréé par Android ne sera jamais supprimé, vous n'avez aucun espoir de le remplacer par un appel àgetPosition
. Il est également assez difficile d'en obtenir une référence car il a été ajouté avec une balise qui vous est inconnue. C'est par conception; vous êtes découragé de jouer avec les fragments que le pager de vue gère. Vous devez effectuer toutes vos actions dans un fragment, communiquer avec l'activité et demander à basculer vers une page particulière, si nécessaire.Maintenant, revenons à votre problème avec l'activité manquante. L'appel une
pagerAdapter.getItem(1)).update(id, name)
fois que tout cela s'est produit vous renvoie le fragment de votre liste, qui n'a pas encore été ajouté au gestionnaire de fragments , et il n'aura donc pas de référence d'activité. Je suggérerais que votre méthode de mise à jour modifie une structure de données partagée (éventuellement gérée par l'activité), puis lorsque vous passez à une page particulière, elle peut se dessiner en fonction de ces données mises à jour.la source
instantiateItem
et vous devriez le faire dansonCreate
votre activité. voir les détails ici: stackoverflow.com/questions/14035090/…J'ai trouvé une solution simple qui a fonctionné pour moi.
Faites en sorte que votre adaptateur de fragment étende FragmentStatePagerAdapter au lieu de FragmentPagerAdapter et remplacez la méthode onSave pour retourner null
Cela empêche Android de recréer le fragment
Un jour plus tard, j'ai trouvé une autre et meilleure solution.
Appelez
setRetainInstance(true)
tous vos fragments et enregistrez les références à eux quelque part. Je l'ai fait en variable statique dans mon activité, car il est déclaré comme singleTask et les fragments peuvent rester les mêmes tout le temps.De cette façon, Android ne recrée pas de fragments mais utilise les mêmes instances.
la source
setRetainInstance(true)
avecFragmentPagerAdapter
. Tout fonctionne bien. Mais lorsque je fais pivoter l'appareil, l'adaptateur contient toujours les fragments mais les fragments ne sont pas affichés. Les méthodes de cycle de vie des fragments ne sont pas non plus appelées. Quelqu'un peut-il aider?J'ai résolu ce problème en accédant à mes fragments directement via le FragmentManager au lieu de via le FragmentPagerAdapter comme ça. Je dois d'abord comprendre la balise du fragment généré automatiquement par le FragmentPagerAdapter ...
Ensuite, j'obtiens simplement une référence à ce fragment et je fais ce dont j'ai besoin, alors ...
À l'intérieur de mes fragments, j'ai défini le
setRetainInstance(false);
afin que je puisse ajouter manuellement des valeurs au bundle savedInstanceState.puis dans OnCreate, je saisis cette clé et je restaure l'état du fragment si nécessaire. Une solution facile et difficile (pour moi du moins) à comprendre.
la source
FragmentByTag
dans ViewPager.Solution testée au niveau mondial.
getSupportFragmentManager()
conserve la référence nulle quelques fois et View pager ne crée pas de nouveau car il trouve une référence au même fragment. Donc, surmonter cette utilisationgetChildFragmentManager()
résout le problème de manière simple.Ne fais pas ça:
new PagerAdapter(getSupportFragmentManager(), fragments);
Faites ceci:
new PagerAdapter(getChildFragmentManager() , fragments);
la source
FragmentStatePagerAdapter(activity!!.supportFragmentManager)
FragmentStatePagerAdapter(childFragmentManager)
N'essayez pas d'interagir entre les fragments dans ViewPager. Vous ne pouvez pas garantir qu'un autre fragment est attaché ou même existe. Au lieu de changer le titre de la barre d'action du fragment, vous pouvez le faire à partir de votre activité. Utilisez le modèle d'interface standard pour cela:
la source
Vous pouvez supprimer les fragments lors de la destruction du viewpager, dans mon cas, je les ai supprimés sur
onDestroyView()
de mon fragment:la source
ViewPager
est également basé sur l'childFragmentManager
adaptateur (nonfragmentManager
). Une autre variante fonctionne également: ne pas utiliseronDestroyView
, mais supprimer les fragments enfants avant laViewPager
création de l'adaptateur.Après quelques heures de recherche d'un problème similaire, je pense avoir une autre solution. Celui-ci au moins a fonctionné pour moi et je n'ai qu'à changer quelques lignes.
C'est le problème que j'ai eu, j'ai une activité avec un pager de vue qui utilise un FragmentStatePagerAdapter avec deux fragments. Tout fonctionne bien jusqu'à ce que je force l'activité à être détruite (options du développeur) ou que je fasse pivoter l'écran. Je garde une référence aux deux fragments après leur création dans la méthode getItem.
À ce stade, l'activité sera créée à nouveau et tout fonctionne bien à ce stade, mais j'ai perdu la référence à mes fragmetns car getItem n'est plus appelé.
Voici comment j'ai résolu ce problème, à l'intérieur du FragmentStatePagerAdapter:
Vous ne recevrez plus un appel sur getItem si l'adaptateur y fait déjà référence en interne, et vous ne devriez pas changer cela. Au lieu de cela, vous pouvez obtenir le fragment qu'il est utilisé en regardant cette autre méthode instantiateItem () qui sera appelée pour chacun de vos fragments.
J'espère que cela aide n'importe qui.
la source
Puisque le FragmentManager se chargera de restaurer vos Fragments pour vous dès que la méthode onResume () est appelée, je fais appeler le fragment à l'activité et s'ajouter à une liste. Dans mon cas, je stocke tout cela dans mon implémentation PagerAdapter. Chaque fragment connaît sa position car il est ajouté aux arguments de fragment lors de la création. Désormais, chaque fois que j'ai besoin de manipuler un fragment à un index spécifique, tout ce que j'ai à faire est d'utiliser la liste de mon adaptateur.
Voici un exemple d'adaptateur pour un ViewPager personnalisé qui agrandira le fragment au fur et à mesure qu'il se déplacera dans le focus, et le réduira à mesure qu'il se déplace. Outre les classes Adapter et Fragment que j'ai ici, tout ce dont vous avez besoin est que l'activité parent puisse référencer la variable d'adaptateur et vous êtes défini.
Adaptateur
Fragment
la source
Ma solution: j'ai défini presque toutes les vues comme
static
. Maintenant, mon application interagit parfaitement. Pouvoir appeler les méthodes statiques de partout n'est peut-être pas un bon style, mais pourquoi jouer avec du code qui ne fonctionne pas? J'ai lu beaucoup de questions et leurs réponses ici sur SO et aucune solution n'a apporté de succès (pour moi).Je sais que cela peut fuir la mémoire et gaspiller du tas, et mon code ne sera pas adapté à d'autres projets, mais je n'ai pas peur à ce sujet - j'ai testé l'application sur différents appareils et conditions, aucun problème, l'Android La plate-forme semble être en mesure de gérer cela. L'interface utilisateur est actualisée toutes les secondes et même sur un appareil S2 ICS (4.0.3), l'application est capable de gérer des milliers de géomarqueurs.
la source
J'ai rencontré le même problème mais mon ViewPager était à l'intérieur d'un TopFragment qui a créé et défini un adaptateur à l'aide de
setAdapter(new FragmentPagerAdapter(getChildFragmentManager()))
.J'ai résolu ce problème
onAttachFragment(Fragment childFragment)
en remplaçant dans TopFragment comme ceci:Comme on le sait déjà (voir les réponses ci-dessus), lorsque le childFragmentManager se recrée, il crée également les fragments qui se trouvaient à l'intérieur du viewPager.
La partie importante est qu'après cela, il appelle onAttachFragment et nous avons maintenant une référence au nouveau fragment recréé!
J'espère que cela aidera quiconque à obtenir ce vieux Q comme moi :)
la source
J'ai résolu le problème en enregistrant les fragments dans SparceArray:
la source
Juste pour que vous sachiez ...
En plus de la litanie de malheurs avec ces classes, il y a un bug plutôt intéressant qui mérite d'être partagé.
J'utilise un ViewPager pour naviguer dans une arborescence d'éléments (sélectionnez un élément et le pager de vue s'anime en défilant vers la droite, et la branche suivante apparaît, revenez en arrière et le ViewPager défile dans la direction opposée pour revenir au nœud précédent) .
Le problème survient lorsque je pousse et dépose des fragments à la fin de FragmentStatePagerAdapter. Il est suffisamment intelligent pour remarquer que les éléments changent et suffisamment intelligent pour créer et remplacer un fragment lorsque l'élément a changé. Mais pas assez intelligent pour supprimer l'état de fragment, ou assez intelligent pour couper les états de fragment enregistrés en interne lorsque la taille de l'adaptateur change. Ainsi, lorsque vous affichez un élément et en poussez un nouveau à la fin, le fragment du nouvel élément obtient l'état enregistré du fragment pour l'ancien élément, ce qui a causé des ravages absolus dans mon code. Mes fragments contiennent des données qui peuvent nécessiter beaucoup de travail pour être récupérées sur Internet, donc ne pas enregistrer l'état n'était vraiment pas une option.
Je n'ai pas de solution de contournement propre. J'ai utilisé quelque chose comme ça:
Une solution imparfaite car la nouvelle instance de fragment reçoit toujours un bundle savedState, mais au moins elle ne contient pas de données périmées.
la source
Puisque les gens n'ont pas tendance à lire les commentaires, voici une réponse qui reproduit principalement ce que j'ai écrit ici :
la cause première du problème est le fait que le système Android n'appelle pas
getItem
pour obtenir des fragments qui sont réellement affichés, maisinstantiateItem
. Cette méthode essaie d'abord de rechercher et de réutiliser une instance de fragment pour un onglet donné dansFragmentManager
. Uniquement si cette recherche échoue (ce qui ne se produit que la première fois lors de saFragmentManager
création), ellegetItem
est appelée. Il s'agit pour des raisons évidentes de ne pas recréer des fragments (qui peuvent être lourds) par exemple à chaque fois qu'un utilisateur fait tourner son appareil.Pour résoudre ce problème, au lieu de créer des fragments avec
Fragment.instantiate
dans votre activité, vous devez le faire avecpagerAdapter.instantiateItem
et tous ces appels doivent être entourés d'startUpdate/finishUpdate
appels de méthode qui démarrent / valident respectivement la transaction de fragment.getItem
devrait être le lieu où les fragments sont réellement créés à l'aide de leurs constructeurs respectifs.la source