Android: AlertDialog provoque une fuite de mémoire
Mon application affiche un AlertDialog
avec un ListView
à l'intérieur. Tout a bien fonctionné chignon puis j'ai décidé de tester ce pour des fuites de mémoire. Après l'exécution de l'application pour un certain temps, j'ai ouvert MAT et a généré une Fuite des Suspects rapport. MAT trouvé plusieurs fuites:
Une instance de "com.android.interne.app.AlertController$RecycleListView" chargé par "<système de chargeur de classe>" occupe ...
J'ai passé beaucoup de temps à chercher la raison de cette fuite. La révision du Code ne m'a pas aidé et j'ai commencé une recherche sur google. C'est ce que j'ai trouvé:
J'ai décidé de vérifier si ce bug se reproduit ou pas. Pour cela j'ai créé un petit programme qui se compose de deux activités. MainActivity
est un enrty point. Il ne contient que des boutons qui s'exécute LeakedActivity
. Le dernier est juste un AlertDialog
dans son onCreate()
méthode. Voici le code:
public class MainActivity extends Activity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
findViewById(R.id.button).setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
startActivity(
new Intent(MainActivity.this, LeakedActivity.class));
}
});
}
}
public class LeakedActivity extends Activity {
private static final int DIALOG_LEAK = 0;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (savedInstanceState == null) {
showDialog(DIALOG_LEAK);
}
}
@Override
protected Dialog onCreateDialog(int id) {
if (id == DIALOG_LEAK) {
return new AlertDialog.Builder(this)
.setTitle("Title")
.setItems(new CharSequence[] { "1", "2" },
new OnClickListener() {
private final byte[] junk = new byte[10*1024*1024];
@Override
public void onClick(DialogInterface dialog, int which) {
//nothing
}
})
.create();
}
return super.onCreateDialog(id);
}
}
MAT les rapports de cette application fuites com.android.internal.app.AlertController$RecycleListView
chaque fois que le AlertDialog
est rejeté, et la LeakedActivity
est fini.
Je ne trouve pas d'erreur dans ce petit programme. Il ressemble à un cas très simple d'utilisation AlertDialog
et il faut bien travailler mais il semble qu'il ne l'est pas. Donc je voudrais savoir comment faire pour éviter les fuites de mémoire lors de l'utilisation de AlertDialog
s avec les éléments. Et pourquoi n'est-ce pas ce problème été encore fixée? Merci à l'avance.
Vous devez vous connecter pour publier un commentaire.
(2/12/2012): voir mise à JOUR ci-dessous.
Ce problème n'est en fait causée par la
AlertDialog
, mais davantage liée à laListView
. Vous pouvez reproduire le même problème en utilisant l'activité suivante:Faire pivoter l'appareil à plusieurs reprises, et vous obtiendrez OOM.
Je n'ai pas le temps d'enquêter sur ce qui est la vraie cause (je sais ce qui est passe, mais pas clair pourquoi ça se passe; peut-être un bug, ou la conception). Mais voici une solution de contournement que vous pouvez faire, au moins pour éviter le OOM dans votre cas.
Tout d'abord, vous aurez besoin de garder une référence à votre fuite
AlertDialog
. Vous pouvez le faire dans leonCreateDialog()
. Lorsque vous utilisezsetItems()
, leAlertDialog
permettra de créer en interne unListView
. Et lorsque vous définissez laonClickListener()
dans votresetItems()
appel, à l'interne, il sera affecté à laListView
onItemClickListener()
.Puis, dans la fuite de l'activité
onDestroy()
, définir laAlertDialog
'sListView
'sonItemClickListener()
ànull
, qui permettra de libérer la référence à l'auditeur une faire toute la mémoire allouée à l'intérieur de cette auditeur pour être admissibles à la GC. De cette façon, vous n'obtiendrez pas OOM. C'est juste un solution de contournement et la vraie solution devrait en fait être intégrées dans leListView
.Voici un exemple de code pour votre
onDestroy()
:Mise à JOUR (2/12/2012): Après enquête, ce problème est en fait pas particulièrement liées à
ListView
ni àOnItemClickListener
, mais le fait que la GC ne se produit pas immédiatement et ont besoin de temps pour décider laquelle des objets sont admissibles et prêt pour la GC. Essayez ceci:Faire pivoter une couple de fois, et vous obtiendrez le OOM. Le problème, c'est après l'avoir fait pivoter, le
junk
est toujours retenu parce que GC n'a pas et ne peut pas arriver encore (si vous utilisez le TAPIS, vous verrez que cejunk
est toujours conservé par le bouton de l'écouteur de profondeur vers le bas à partir de la GCroot, et il faudra du temps pour que le GC de décider si cettejunk
est éligible et peut être GCed.) Mais dans le même temps, une nouvellejunk
doit être créé après la rotation, et en raison de la mem alloc taille (10M par junk), cela va entraîner OOM.La solution est de casser toutes les références à l'auditeur (le
junk
propriétaire), dans ce cas, à partir du bouton, qui fait pratiquement l'auditeur comme un GCroot avec seulement court chemin à l'indésirable et de faire le GC décider plus vite pour récupérer la jonque de la mémoire. Cela peut être fait dans leonDestroy()
:J'ai eu exécuter votre code et quand j'ai d'abord appuyer sur le bouton, il est de montrer la LeakedActivity avec des dialogues et un onClick c'est enlever dialogue, mais l'activité reste en premier plan avec un écran noir. En appuyant sur la touche de retour et puis de nouveau à partir de l'activité qu'il est en montrant la mémoire d'une erreur d'exception:
Puis j'ai enlevé la ligne
private final byte[] junk = new byte[10*1024*1024];
à partir de la boîte de dialogue code après que de tels problèmes n'existe....je ne sais pas pourquoi, si quelqu'un peut mettre cette chose dans les mots, merci à lui/elle..junk
champ a été ajouté à remplir le segment de mémoire plus rapide. Il doit être GC ed après laLeakedActivity
est terminé, mais il ne l'est pas. Et c'est le point.Vous avez besoin de rejeter ou annuler la boîte de dialogue à partir de la méthode onClick comme illustré dans l'exemple ici : Les boîtes de dialogue d'alerte dans Android