In der Android-Entwicklung sind Speicherlecks ein relativ häufiges Problem. Kinder, die etwas Erfahrung in der Android-Programmierung haben, sollten darauf gestoßen sein, aber warum treten Speicherlecks auf? Welche Auswirkungen haben Speicherlecks?
Wenn bei der Android-Programmentwicklung ein Objekt nicht mehr benötigt wird und recycelt werden sollte, ein anderes verwendetes Objekt jedoch einen Verweis darauf enthält, was dazu führt, dass es nicht recycelt werden kann, führt dies dazu, dass Objekte, die recycelt werden sollten, nicht recycelt werden können recycelt werden und im Heap-Speicher verbleiben, und es kommt zu einem Speicherverlust.
Welche Auswirkungen haben Speicherverluste? Dies ist eine der Hauptursachen für die Anwendung von OOM. Da der vom Android-System jeder Anwendung zugewiesene Speicher begrenzt ist, führt es bei vielen Speicherlecks in einer Anwendung zwangsläufig dazu, dass der von der Anwendung benötigte Speicher das vom System zugewiesene Speicherlimit überschreitet, was zu einem Speicherüberlauf führt führt zum Absturz der Anwendung.
Nachdem wir die Ursachen und Auswirkungen von Speicherlecks verstanden haben, müssen wir häufig auftretende Speicherlecks beherrschen und versuchen, sie bei der zukünftigen Entwicklung von Android-Programmen zu vermeiden.
Speicherzuweisung in Java
Statischer Speicherbereich: Er wird während der Kompilierung zugewiesen und bleibt während der gesamten Ausführung des Programms bestehen. Es speichert hauptsächlich statische Daten und Konstanten.
Stapelbereich: Wenn eine Methode ausgeführt wird, werden lokale Variablen im Methodenkörper im Speicher des Stapelbereichs erstellt und der Speicher wird nach Beendigung der Methode automatisch freigegeben.
Heap-Bereich: speichert normalerweise neu erstellte Objekte. Vom Java Garbage Collector recycelt.
Einführung in vier Referenztypen
Starke Referenz: JVM würde lieber OOM werfen, als GC Objekte mit starken Referenzen recyceln zu lassen;
Weiche Referenz (SoftReference): Ein Objekt, das dies tut wird nur zurückgegeben, wenn der Speicherplatz nicht ausreicht;
Schwache Referenz (WeakReference): Sobald beim GC ein Objekt mit nur einer schwachen Referenz gefunden wird, unabhängig davon, ob der aktuelle Speicherplatz ausreicht oder nicht, wird sein Speicher gelöscht wird recycelt;
Phantomreferenz: Sie kann von GC jederzeit recycelt werden. Wenn der Garbage Collector bereit ist, ein Objekt zu recyceln, wird es vorher recycelt Geben Sie den Speicher des Objekts ein und fügen Sie diese virtuelle Referenz der damit verbundenen Referenzwarteschlange hinzu. Das Programm kann lernen, ob das Objekt recycelt wird, indem es ermittelt, ob in der Referenzwarteschlange eine virtuelle Referenz auf das Objekt vorhanden ist. Kann als Flag für GC zum Recyceln von Objekten verwendet werden.
Der Speicherverlust, über den wir oft sprechen, bedeutet, dass das neue Objekt nicht von GC recycelt werden kann, das heißt, es ist eine starke Referenz:
Das Wichtigste Leistung bei Auftreten eines Speicherlecks Aufgrund von Speicherjitter nimmt der verfügbare Speicher langsam ab:
Das Speicherleck-Analysetool MAT in Andriod
MAT (Memory Analyzer Tools) ist ein Eclipse-Plugin, ein schnelles, funktionsreiches JAVA-Heap-Analysetool, das uns dabei helfen kann, Speicherlecks zu finden und den Speicherverbrauch zu reduzieren.
So überwachen Sie Speicherlecks in QQ und Qzone
Die Speicherlecks in QQ und Qzone übernehmen die SNGAPM-Lösung. SNGAPM ist eine einheitliche Lösung zur Leistungsüberwachung und -analyse Terminal und meldet sie. Gehen Sie zu einem Backend, das die Überwachungsinformationen aggregiert und als Diagramm anzeigt, die analytischen Informationen analysiert, Aufträge übermittelt und den Entwickler benachrichtigt.
SNGAPM besteht aus einer App (MagnifierApp) und einem Webserver (MagnifierServer);
MagnifierApp ist eine Zwischenplattform, die die Erkennungskomponente (LeakInspector) und die automatisierte Cloud-Analyse (MagnifierCloud) bei der automatischen Speicherleckerkennung verbindet. Sie lädt den MagnifierServer automatisch aus dem Speicherauszug von LeakInspector hoch >
MagnifierServer-Hintergrundanalyseaufgaben werden regelmäßig an MagnifierCloud übermittelt Nach Abschluss der MagnifierCloud-Analyse werden die Daten im Magnifier-Web aktualisiert und Entwickler werden in Form von Fehlertickets benachrichtigt.Häufige Fälle von Speicherverlusten
Fall 1. Speicherverlust durch Singleton
Die statischen Eigenschaften von Singleton führen dazu, dass sein Lebenszyklus genauso lang ist wie der der Anwendung.
Lösung:
Ändern Sie die Referenzmethode dieses Attributs in eine schwache Referenz;
Wenn Sie Context übergeben, verwenden Sie ApplicationContext;
Beispiel: durchgesickert Codefragment
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; }
Lösung: Verwenden Sie 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); }
Fall 2. InnerClass anonyme innere Klasse
In Java gibt es sowohl nicht statische innere Klassen als auch anonyme Klassen Verweisen Sie möglicherweise auf die äußere Klasse, zu der sie gehören, statische innere Klassen jedoch nicht. Wenn diese nicht statische Instanz der inneren Klasse einige zeitaufwändige Vorgänge ausführt, werden die umgebenden Objekte nicht recycelt, was zu Speicherverlusten führt.
Lösung:
Ändern Sie die innere Klasse in eine statische innere Klasse;
Wenn in der Aktivität ein starker Verweis auf ein Attribut vorhanden ist, ändern Sie die Referenzmethode des Attributs auf eine schwache Referenz ;
Wenn das Unternehmen es zulässt, beenden Sie diese zeitaufwändigen Aufgaben, wenn die Aktivität onDestory ausgeführt wird;
Beispiel:
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(); } }
Lösung:
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(); } }
Fall 3. Falsche Verwendung des Aktivitätskontexts
Es gibt normalerweise zwei Arten von Kontextobjekten, die in Android-Anwendungen verwendet werden können: Aktivität und Anwendung. Wenn eine Klasse oder Methode ein Kontextobjekt erfordert, ist es üblich, das erste als Kontextparameter zu verwenden. Dies bedeutet, dass das View-Objekt einen Verweis auf die gesamte Aktivität und damit alle Verweise auf die Aktivität verwaltet.
Gehen Sie von einem Szenario aus, in dem die Anwendung über ein relativ großes Bitmap-Bild verfügt und es viel Zeit in Anspruch nimmt, das Bild bei jeder Drehung neu zu laden. Um die Geschwindigkeit der Bildschirmrotation und der Aktivitätserstellung zu verbessern, besteht die einfachste Methode darin, eine statische Dekoration für dieses Bitmap-Objekt zu verwenden. Wenn ein Drawable an eine Ansicht gebunden ist, wird das View-Objekt tatsächlich zu einer Callback-Mitgliedsvariablen des Drawable. Der Lebenszyklus statischer Variablen ist länger als der von Aktivitäten. Wenn der Bildschirm gedreht wird, kann die Aktivität daher nicht recycelt werden, was zu einem Speicherverlust führt.
Lösung:
Verwenden Sie ApplicationContext anstelle von ActivityContext, da ApplicationContext mit der Existenz der Anwendung vorhanden ist und nicht vom Lebenszyklus der Aktivität abhängt;
für Context Die Referenz sollte ihren eigenen Lebenszyklus nicht überschreiten und das Schlüsselwort „static“ für den Kontext sorgfältig verwenden. Wenn Threads im Kontext vorhanden sind, müssen diese rechtzeitig in onDestroy() gestoppt werden.
Beispiel:
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); }
Lösung:
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); }
Fall 4. Speicherverlust durch Handler
Wenn eine verzögerte Aufgabe im Handler Oder die Warteschlange der auf die Ausführung wartenden Aufgaben ist zu lang. Da die Nachricht einen Verweis auf den Handler enthält und der Handler einen potenziellen Verweis auf seine externe Klasse enthält, bleibt diese Referenzbeziehung bestehen, bis die Nachricht verarbeitet wird Die Aktivität konnte nicht verarbeitet werden, was zu einem Speicherverlust führte.
Lösung:
Sie können die Handler-Klasse in eine separate Klassendatei einfügen oder eine statische innere Klasse verwenden, um Lecks zu vermeiden.
Wenn Sie sie innerhalb der Datei aufrufen möchten Handler Wenn die Aktivität gefunden wird, können Sie eine schwache Referenz innerhalb des Handlers verwenden, um auf die Aktivität zu verweisen. Verwenden Sie Static + WeakReference, um die Referenzbeziehung zwischen dem Handler und der Aktivität zu trennen.
Lösung:
@Override protected void doOnDestroy() { super.doOnDestroy(); if (mHandler != null) { mHandler.removeCallbacksAndMessages(null); } mHandler = null; mRenderCallback = null; }
Fall 5. Verlust des registrierten Zuhörers
系统服务可以通过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)。