web-dev-qa-db-fra.com

Fragments retenus avec interface utilisateur et fuites de mémoire

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

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

30

Dans une Fragment avec interface utilisateur, vous enregistrez souvent quelques Views en tant qu'état d'instance pour accélérer l'accès. Par exemple, un lien vers votre EditText afin que vous n'ayez pas à le faire findViewById tout le temps.

Le problème est que View conserve une référence au contexte Activity. Maintenant, si vous conservez une View, vous conservez également une référence à ce contexte.

Ce n'est pas un problème si le contexte est toujours valide mais que 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é créera un nouveau contexte et les anciens contextes sont censés être récupérés. Mais il ne peut pas être nettoyé maintenant car 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 résoudre ce problème, vous devez effacer toutes les références à votre interface utilisateur dans onDestroyView. Une fois que l'instance Fragment est réutilisée, il vous sera demandé de créer une nouvelle interface utilisateur sur onCreateView. Il est également inutile de conserver l'interface utilisateur après onDestroyView. L'interface utilisateur ne sera pas utilisée.

Dans cet exemple, le correctif consiste à remplacer onDestroyView par

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

Et en plus de conserver des références à Views, vous ne devez évidemment pas conserver de références à Activity (par exemple, de onAttach - clean sur onDetach) ou de tout Context (sauf s'il s'agit du contexte Application.

84
zapl

Faites attention lorsque vous conservez certains objets associés à l'activité. 

Attention: Bien que vous puissiez renvoyer un objet, vous ne devez jamais transmettre un objet lié à l'activité, tel qu'un Drawable, un Adapter, un View ou autre objet associé à un contexte. Si vous le faites, toutes les vues et ressources de l'instance d'activité d'origine seront perdues. (Une fuite de ressources signifie que votre application les maintient en attente et qu'elles ne peuvent pas être récupérées, ce qui permet de perdre beaucoup de mémoire.)

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

3
Emanuel Canha

setRetainInstance(true) est utilisé pour conserver des occurrences de fragments dynamiques lors d'une reconstitution d'activité, telle qu'une rotation d'écran ou d'autres modifications de configuration. Cela ne signifie toutefois pas que le fragment sera conservé pour toujours par le système.

Lorsqu'une activité est arrêtée pour d'autres raisons, telles que le fait que l'utilisateur termine l'activité (c'est-à-dire qu'il appuie de nouveau), le fragment devrait être éligible pour la récupération de place.

2
wsanville

"SetRetainInstance" est utilisé pour maintenir 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, c'est-à-dire parce que 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 "onCreate".

Informations officielles: https://developer.Android.com/reference/Android/app/Fragment.html#setRetainInstance(boolean)

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

0
FacuArg