Dans le développement Android, les fuites de mémoire sont un problème relativement courant. Les enfants qui ont une certaine expérience en programmation Android auraient dû le rencontrer, mais pourquoi les fuites de mémoire se produisent-elles ? Quels sont les effets des fuites de mémoire ?
Dans le développement de programmes Android, lorsqu'un objet n'est plus nécessaire et doit être recyclé, mais qu'un autre objet en cours d'utilisation contient une référence à celui-ci, ce qui l'empêche d'être recyclé, cela conduit à ce que les objets qui devraient être recyclés ne peuvent pas être recyclé et rester dans la mémoire tas, et une fuite de mémoire se produit.
Quels sont les effets des fuites de mémoire ? C’est l’une des principales causes d’application du MOO. Étant donné que la mémoire allouée par le système Android pour chaque application est limitée, lorsqu'il y a de nombreuses fuites de mémoire dans une application, la mémoire requise par l'application dépassera inévitablement la limite de mémoire allouée par le système, ce qui provoquera un débordement de mémoire et provoque le crash de l'application.
Après avoir compris les causes et les effets des fuites de mémoire, ce que nous devons faire est de maîtriser les fuites de mémoire courantes et d'essayer de les éviter lors du développement futur de programmes Android.
Allocation de mémoire en Java
Zone de stockage statique : Elle est allouée lors de la compilation et existe tout au long de l'exécution du programme. Il stocke principalement des données statiques et des constantes ;
Zone de pile : lorsqu'une méthode est exécutée, les variables locales à l'intérieur du corps de la méthode seront créées dans la mémoire de la zone de pile et la mémoire sera automatiquement libérée une fois la méthode terminée ;
Heap Area : stocke généralement les objets produits par de nouveaux. Recyclé par le ramasse-miettes Java.
Introduction à quatre types de référence
Référence forte : JVM préfère lancer le MOO plutôt que de laisser GC recycler les objets avec des références fortes
Référence douce (SoftReference) : un objet qui le fera ; n'est renvoyé que lorsque l'espace mémoire est insuffisant ;
Référence faible (WeakReference) : lors du GC, une fois qu'un objet avec seulement une référence faible est trouvé, que l'espace mémoire actuel soit suffisant ou non, sa mémoire sera recyclé ;
Référence fantôme : Il peut être recyclé par GC à tout moment. Lorsque le garbage collector est prêt à recycler un objet, s'il constate qu'il a encore une référence virtuelle, il sera recyclé avant. entrant dans la mémoire de l'objet, ajoutez cette référence virtuelle à la file de références qui lui est associée. Le programme peut savoir si l'objet est sur le point d'être recyclé en déterminant s'il existe une référence virtuelle à l'objet dans la file d'attente de référence. Peut être utilisé comme indicateur pour que GC recycle l'objet.
La fuite de mémoire dont nous parlons souvent signifie que le nouvel Objet ne peut pas être recyclé par GC, c'est-à-dire qu'il s'agit d'une référence forte :
Le principal manifestations lorsque des fuites de mémoire se produisent En raison de la gigue de la mémoire, la mémoire disponible diminue lentement :
L'outil MAT pour analyser les fuites de mémoire dans Andriod
MAT (Memory Analyzer Tools) est un plug-in Eclipse. Il s'agit d'un outil d'analyse de tas JAVA rapide et riche en fonctionnalités, qui peut nous aider à détecter les fuites de mémoire et à réduire la consommation de mémoire.
Comment surveiller les fuites de mémoire dans QQ et Qzone
Les fuites de mémoire dans QQ et Qzone adoptent la solution SNGAPM SNGAPM est une solution unifiée pour la surveillance et l'analyse des performances. terminal et le signale Accédez à un backend, qui regroupe les informations de surveillance et les affiche sous forme de graphique, analyse les informations d'analyse et soumet les commandes, et informe les développeurs
SNGAPM se compose d'une application (MagnifierApp) et d'un serveur Web (MagnifierServer ; );
MagnifierApp est une plate-forme intermédiaire qui connecte le composant de détection (LeakInspector) et l'analyse automatisée du cloud (MagnifierCloud) dans la détection automatique des fuites de mémoire. Elle télécharge automatiquement le MagnifierServer à partir du dump mémoire de LeakInspector ; 🎜>Les tâches d'analyse en arrière-plan de MagnifierServer seront soumises régulièrement à MagnifierCloud
Une fois l'analyse de MagnifierCloud terminée, les données seront mises à jour sur le Web de la loupe et les développeurs seront informés sous la forme de tickets de bug.
Cas courants de fuite de mémoire
cas 1. Fuite de mémoire causée par un singleton
Les caractéristiques statiques du singleton font que son cycle de vie est aussi long que l'application.
Solution :
Changez la méthode de référence de cet attribut en une référence faible ;
Si vous passez en Context, utilisez ApplicationContext ;
exemple : fuite Fragment de code
private static ScrollHelper mInstance; private ScrollHelper() { } public static ScrollHelper getInstance() { if (mInstance == null) { synchronized (ScrollHelper.class) { if (mInstance == null) { mInstance = new ScrollHelper(); } } } return mInstance; } /** * 被点击的view */ private View mScrolledView = null; public void setScrolledView(View scrolledView) { mScrolledView = scrolledView; }
Solution : utilisez WeakReference
private static ScrollHelper mInstance; private ScrollHelper() { } public static ScrollHelper getInstance() { if (mInstance == null) { synchronized (ScrollHelper.class) { if (mInstance == null) { mInstance = new ScrollHelper(); } } } return mInstance; } /** * 被点击的view */ private WeakReference<View> mScrolledViewWeakRef = null; public void setScrolledView(View scrolledView) { mScrolledViewWeakRef = new WeakReference<View>(scrolledView); }
cas 2. Classe interne anonyme InnerClass
En Java, les classes internes non statiques et les classes anonymes sont Faites potentiellement référence à la classe externe à laquelle elles appartiennent, cependant, les classes internes statiques ne le font pas. Si cette instance de classe interne non statique effectue des opérations fastidieuses, les objets environnants ne seront pas recyclés, ce qui entraînera des fuites de mémoire.
Solution :
Changer la classe interne en classe interne statique ;
S'il y a une référence forte à un attribut dans l'activité, changez la méthode de référence de l'attribut à une référence faible ;
Si l'entreprise le permet, mettre fin à ces tâches chronophages lorsque l'Activité s'exécute surDestory;
exemple :
public class LeakAct extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.aty_leak); test(); } //这儿发生泄漏 public void test() { new Thread(new Runnable() { @Override public void run() { while (true) { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } }).start(); } }
Solution :
public class LeakAct extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.aty_leak); test(); } //加上static,变成静态匿名内部类 public static void test() { new Thread(new Runnable() { @Override public void run() { while (true) { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } }).start(); } }
cas 3. Utilisation incorrecte du Contexte d'Activité
Il existe généralement deux types d'objets Contexte qui peuvent être utilisés dans les applications Android : Activité et Application. Une pratique courante lorsqu'une classe ou une méthode nécessite un objet Context consiste à utiliser le premier comme paramètre Context. Cela signifie que l'objet View conserve une référence à l'ensemble de l'activité, et donc toutes les références à l'activité.
Supposons un scénario dans lequel l'application dispose d'une image de type Bitmap relativement grande et qu'il faut beaucoup de temps pour recharger l'image à chaque fois qu'elle est pivotée. Afin d'améliorer la vitesse de rotation de l'écran et de création d'activité, la méthode la plus simple consiste à utiliser une décoration statique sur cet objet Bitmap. Lorsqu'un Drawable est lié à une View, l'objet View devient en fait une variable membre de rappel du Drawable. Le cycle de vie des variables statiques est plus long que celui de l'activité. Par conséquent, lors de la rotation de l'écran, l'activité ne peut pas être recyclée, provoquant une fuite de mémoire.
Solution :
Utilisez ApplicationContext au lieu d'ActivityContext, car ApplicationContext existera avec l'existence de l'application et ne dépend pas du cycle de vie de l'activité
pour Context ; La référence ne doit pas dépasser son propre cycle de vie et utiliser le mot-clé "static" pour Context avec précaution. S'il y a des threads dans le Context, ils doivent être arrêtés dans onDestroy() à temps.
exemple :
private static Drawable sBackground; @Override protected void onCreate(Bundle state) { super.onCreate(state); TextView label = new TextView(this); label.setText("Leaks are bad"); if (sBackground == null) { sBackground = getDrawable(R.drawable.large_bitmap); } label.setBackgroundDrawable(sBackground); setContentView(label); }
Solution :
private static Drawable sBackground; @Override protected void onCreate(Bundle state) { super.onCreate(state); TextView label = new TextView(this); label.setText("Leaks are bad"); if (sBackground == null) { sBackground = getApplicationContext().getDrawable(R.drawable.large_bitmap); } label.setBackgroundDrawable(sBackground); setContentView(label); }
cas 4. Fuite de mémoire causée par le gestionnaire
Lorsqu'il y a une tâche retardée dans le Gestionnaire Ou la file d'attente des tâches en attente d'exécution est trop longue Étant donné que le message contient une référence au gestionnaire et que le gestionnaire contient une référence potentielle à sa classe externe, cette relation de référence restera jusqu'à ce que le message soit traité, ce qui entraînera le problème. L'activité n'a pas pu être traitée. Le garbage collector l'a collectée, provoquant une fuite de mémoire.
Solution :
Vous pouvez placer la classe Handler dans un fichier de classe séparé, ou utiliser une classe interne statique pour éviter les fuites
Si vous souhaitez l'appeler à l'intérieur du fichier ; Handler Activity, vous pouvez utiliser une référence faible à l’intérieur du gestionnaire pour pointer vers l’activité. Utilisez Static WeakReference pour déconnecter la relation de référence entre le gestionnaire et l’activité.
Solution :
@Override protected void doOnDestroy() { super.doOnDestroy(); if (mHandler != null) { mHandler.removeCallbacksAndMessages(null); } mHandler = null; mRenderCallback = null; }
cas 5. Fuite de l'auditeur enregistré
系统服务可以通过Context.getSystemService 获取,它们负责执行某些后台任务,或者为硬件访问提供接口。如果Context 对象想要在服务内部的事件发生时被通知,那就需要把自己注册到服务的监听器中。然而,这会让服务持有Activity 的引用,如果在Activity onDestory时没有释放掉引用就会内存泄漏。
解决方案:
使用ApplicationContext代替ActivityContext;
在Activity执行onDestory时,调用反注册;
mSensorManager = (SensorManager) this.getSystemService(Context.SENSOR_SERVICE);
Solution:
mSensorManager = (SensorManager) getApplicationContext().getSystemService(Context.SENSOR_SERVICE);
下面是容易造成内存泄漏的系统服务:
InputMethodManager imm = (InputMethodManager) context.getApplicationContext().get SystemService(Context.INPUT_METHOD_SERVICE);
Solution:
protected void onDetachedFromWindow() { if (this.mActionShell != null) { this.mActionShell.setOnClickListener((OnAreaClickListener)null); } if (this.mButtonShell != null) { this.mButtonShell.setOnClickListener((OnAreaClickListener)null); } if (this.mCountShell != this.mCountShell) { this.mCountShell.setOnClickListener((OnAreaClickListener)null); } super.onDetachedFromWindow(); }
case 6. Cursor,Stream没有close,View没有recyle
资源性对象比如(Cursor,File文件等)往往都用了一些缓冲,我们在不使用的时候,应该及时关闭它们,以便它们的缓冲及时回收内存。它们的缓冲不仅存在于 java虚拟机内,还存在于java虚拟机外。如果我们仅仅是把它的引用设置为null,而不关闭它们,往往会造成内存泄漏。因为有些资源性对象,比如SQLiteCursor(在析构函数finalize(),如果我们没有关闭它,它自己会调close()关闭),如果我们没有关闭它,系统在回收它时也会关闭它,但是这样的效率太低了。因此对于资源性对象在不使用的时候,应该调用它的close()函数,将其关闭掉,然后才置为null. 在我们的程序退出时一定要确保我们的资源性对象已经关闭。
Solution:
调用onRecycled()
@Override public void onRecycled() { reset(); mSinglePicArea.onRecycled(); }
在View中调用reset()
public void reset() { if (mHasRecyled) { return; } ... SubAreaShell.recycle(mActionBtnShell); mActionBtnShell = null; ... mIsDoingAvatartRedPocketAnim = false; if (mAvatarArea != null) { mAvatarArea.reset(); } if (mNickNameArea != null) { mNickNameArea.reset(); } }
case 7. 集合中对象没清理造成的内存泄漏
我们通常把一些对象的引用加入到了集合容器(比如ArrayList)中,当我们不需要该对象时,并没有把它的引用从集合中清理掉,这样这个集合就会越来越大。如果这个集合是static的话,那情况就更严重了。
所以要在退出程序之前,将集合里的东西clear,然后置为null,再退出程序。
解决方案:
在Activity退出之前,将集合里的东西clear,然后置为null,再退出程序。
Solution
private List<EmotionPanelInfo> data; public void onDestory() { if (data != null) { data.clear(); data = null; } }
case 8. WebView造成的泄露
当我们不要使用WebView对象时,应该调用它的destory()函数来销毁它,并释放其占用的内存,否则其占用的内存长期也不能被回收,从而造成内存泄露。
解决方案:
为webView开启另外一个进程,通过AIDL与主线程进行通信,WebView所在的进程可以根据业务的需要选择合适的时机进行销毁,从而达到内存的完整释放。
case 9. 构造Adapter时,没有使用缓存的ConvertView
初始时ListView会从Adapter中根据当前的屏幕布局实例化一定数量的View对象,同时ListView会将这些View对象 缓存起来。
当向上滚动ListView时,原先位于最上面的List Item的View对象会被回收,然后被用来构造新出现的最下面的List Item。
这个构造过程就是由getView()方法完成的,getView()的第二个形参View ConvertView就是被缓存起来的List Item的View对象(初始化时缓存中没有View对象则ConvertView是null)。