2012-11-16 2 views
27

J'ai lu que le réglage .setOnRetainInstance(true) sur des fragments présentant une interface utilisateur peut entraîner des fuites de mémoire.Fragments conservés avec interface utilisateur et fuites de mémoire

Quelqu'un pourrait-il expliquer pourquoi et comment cela se produirait? Je n'ai trouvé aucune explication détaillée nulle part.

+0

Juste pour documenter le sujet, voici les discussions similaires: http://stackoverflow.com/q/11182180/693752 – Snicolas

+0

http: // stackoverflow. com/q/11160412/693752 – Snicolas

Répondre

75

Dans un Fragment avec l'interface utilisateur, vous économisez souvent quelques View s en tant qu'état d'instance pour accélérer l'accès. Par exemple un lien vers votre EditText de sorte que vous n'avez pas à findViewById tout le temps. Le problème est qu'un View conserve une référence au contexte Activity. Maintenant, si vous conservez un View, vous conservez également une référence à ce contexte.

Cela ne pose aucun problème si le contexte est toujours valide, mais le cas typique de conservation est le redémarrage de l'activité. Très souvent pour une rotation d'écran par exemple. La récréation d'activité va créer un nouveau contexte et les anciens contextes sont destinés à être collectés. Mais il ne peut pas être collecté maintenant puisque votre Fragment a toujours une référence à l'ancien.

L'exemple suivant montre comment ne pas le faire

public class LeakyFragment extends Fragment { 

    private View mLeak; // retained 

    @Override 
    public void onCreate(Bundle savedInstanceState) { 
     super.onCreate(savedInstanceState); 
     setRetainInstance(true); 
    } 

    @Override 
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { 
     mLeak = inflater.inflate(R.layout.whatever, container, false); 
     return mLeak; 
    } 

    @Override 
    public void onDestroyView() { 
     super.onDestroyView(); 
     // not cleaning up. 
    } 
} 

Pour se débarrasser de ce problème, vous devez effacer toutes les références à votre interface utilisateur dans onDestroyView. Une fois l'instance Fragment réutilisée, il vous sera demandé de créer une nouvelle interface utilisateur au onCreateView. Il est également inutile de conserver l'interface utilisateur après onDestroyView. L'Ui ne va pas être utilisé.

Le correctif dans cet exemple est simplement en train de changer onDestroyView à

@Override 
public void onDestroyView() { 
    super.onDestroyView(); 
    mLeak = null; // now cleaning up! 
} 

Et en plus de garder les références à View s vous devriez évidemment pas garder les références à la Activity (par exemple de onAttach - propre à onDetach) ou Context tout (sauf si c'est le contexte Application).

+0

Vous avez des idées, comment gérer, la montagne de pointeurs NULL cela pourrait causer? J'ai plusieurs écouteurs d'animation, des threads, et chacun utilise une des références, qui est annulée dans onDestroyView. Donc, chaque fois que j'utilise une de ces références, je dois d'abord vérifier null. C'est très gênant. – Tamas

+1

@Tamas Les auditeurs, les threads, ... sont tous très bien pour rester référencés tant qu'ils ne gardent pas de référence à quoi que ce soit ou fait référence à 'Activity'. S'ils ont quelque chose comme ça référencé et que l'activité est recréée en utilisant cela, cela ne donnera rien de valable, donc vous devrez le mettre à jour de toute façon. – zapl

+1

Exemple @Tamas: http://pastebin.com/8A18kMym Vous devez essentiellement propager 'onAttach' /' onDetach' à tout ce qui fait référence au contexte, et 'onCreateView' /' onDestroyView' à tout ce qui conserve des références aux vues. – zapl

2

setRetainInstance(true) est utilisé pour conserver des instances de fragments dynamiques pendant une récréation d'activité, telle qu'une rotation d'écran ou d'autres changements de configuration. Cela ne signifie pas que le fragment sera conservé pour toujours par le système. Lorsqu'une activité est terminée pour d'autres raisons, telles que l'utilisateur finissant l'activité (c'est-à-dire en appuyant de nouveau), le fragment doit être éligible pour la récupération de place.

3

Faites attention lorsque vous retenez certains objets couplés à l'activité.

Attention: Alors que vous pouvez retourner un objet, vous ne devriez jamais passer un objet qui est lié à l'activité, comme un Drawable, un adaptateur , un Voir ou tout autre objet qui est associé avec un contexte. Si vous le faites, il fuira toutes les vues et ressources de l'instance d'activité d'origine. (Des fuites de ressources signifient que votre application les conserve et qu'elles ne peuvent pas être récupérées, donc beaucoup de mémoire peut être perdue.)

http://developer.android.com/guide/topics/resources/runtime-changes.html#RetainingAnObject

-7

vous pouvez overide onDestroy() et appeler garbage collector.

@Override 
public void onDestroy() { 
    super.onDestroy(); 
    System.gc(); 
    System.gc(); 
} 
+3

20% de chances que ça fonctionne: P 2x 'System.gc()' pour la sécurité? Ne comptez jamais sur 'gc()' –

0

Le paramètre "setRetainInstance" est utilisé pour conserver l'état du fragment lorsque l'activité est recréée. Selon la documentation officielle: si nous utilisons "setRetainInstance", 2 méthodes du cycle de vie du fragment ne seront pas exécutées (onCreate, onDestroy). Cependant, les vues contenues dans le fragment seront recréées, car le cycle de vie sera exécuté à partir de "onCreateView". Dans ces cas, si nous avons enregistré des données dans "onSaveInstanceState", nous devrions le demander dans "onActivityCreated" au lieu de dans "onCreate".

information Oficial: https://developer.android.com/reference/android/app/Fragment.html#setRetainInstance(boolean)

Plus d'info: https://inthecheesefactory.com/blog/fragment-state-saving-best-practices/en