Android-Speicherleck

Wie richtiges Lifecycle-Management Android-Speicherlecks verhindern kann

OutOfMemoryException ist ein häufiger und frustrierender Fehler und eine der Hauptursachen für das unerwartete Herunterfahren einer App.

„Warum passiert das jetzt, wenn die App gestern noch perfekt funktionierte?“ Es ist eine Frage, die sowohl Anfänger als auch fortgeschrittene Android-Entwickler auf der ganzen Welt verwirrt.

Es gibt eine Vielzahl möglicher Ursachen für OutOfMemory Exceptions, aber eine der häufigsten ist das Speicherleck – die Zuweisung von Speicher in der App, der nie freigegeben wird. In diesem Artikel wird erläutert, wie dieses Risiko durch effektives Lifecycle-Management minimiert werden kann – ein wichtiger, aber oft übersehener Teil des Entwicklungsprozesses.

TL;DR

In diesem Artikel besprechen wir:

  • Die Gründe für Android-Speicherlecks
  • Grundlegende Schritte, um sie zu verhindern
  • Ausgereiftere Formen des Lebenszyklusmanagements
  • Tools, die den Lebenszyklus Ihres Produkts verwalten können

Warum kommt es bei Android zu RAM-Speicherlecks?

Das Problem ist einfach. Bestimmte Objekte sollten nur eine bestimmte Lebensdauer haben, und wenn ihre Nutzungsdauer abgelaufen ist, müssen sie entfernt werden.

Theoretisch sollte dieser Speicher entsorgt werden, wenn ein Prozess mit onStop oder onDestroy beendet wird. Ein Missbrauch der Objektreferenzierung kann jedoch den Garbage Collector daran hindern, unbenutzte Objekte freizugeben. Wenn zum Beispiel das unbenutzte Objekt A auf das unbenutzte Objekt B verweist, hat man am Ende zwei unnötige Objekte, die der Garbage Collector niemals freigeben wird, da sie sich gegenseitig referenzieren.

Gängige Tricks, um dies zu verhindern

Entwickler können eine Reihe von Schritten unternehmen, um zu verhindern, dass tote Aktivitäten im Speicher gefangen werden.

  1. Registriere/entferne BroadcastReceiver in onResume()/onPause() oder onStart()/onStop()
  2. Verwende NIEMALS statische Variablen für Views/Activities/Contexts
  3. Singletons, die Referenzen auf den Context halten müssen, sollten applicationContext() verwenden oder sie in WeakReference verpacken
  4. Sei vorsichtig mit anonymen und nichtstatischen inneren Klassen vorsichtig sein, da sie eine implizite Referenz zu ihrer umschließenden Klasse enthalten.
    1. Verwenden Sie statische innere Klassen anstelle von anonymen, wenn sie die übergeordnete Klasse überdauern werden (wie Handler).
    2. Wenn die innere oder anonyme Klasse kündbar ist (wie AsyncTask, Thread, RxSubscriptions), kündigen Sie sie, wenn die Aktivität zerstört wird.

Die Android Lifecycle Aware Components

Nachdem Sie die oben genannten grundlegenden Schritte durchgeführt haben, ist es Zeit für etwas viel Wichtigeres: Der Lebenszyklus der Aktivitäten der App. Wenn wir den Lebenszyklus nicht richtig verwalten, bleiben wir am Ende am Speicher hängen, wenn er nicht mehr gebraucht wird.

Dabei gibt es viele verschiedene Aufgaben. Für jede Aktivität müssen wir Threads unterbrechen, Abonnements in RxJava loswerden, AsyncTask-Referenzen aufheben und sicherstellen, dass die Referenz dieser Aktivität (und jeder anderen mit ihr verbundenen Aktivität) korrekt entfernt wird. All diese Aufgaben können einen großen Teil der Zeit des Entwicklers in Anspruch nehmen.

Noch komplizierter wird die Sache durch den Model View Presenter (MVP), das Architekturmuster, das häufig zum Aufbau der Benutzeroberfläche in Android verwendet wird. MVP ist jedoch nützlich, um die Geschäftslogik von der View zu entkoppeln.

Im MVP-Muster sind sowohl View als auch Presenter abstrakte Implementierungen eines Vertrags für ein Verhalten zwischen ihnen. Die gebräuchlichste Art, MVP zu implementieren, ist die Verwendung einer Activity/eines Fragments als Implementierung für die View und eine einfache Implementierung für den Presenter, der daran gewöhnt ist, einen Verweis auf die View zu haben.

Als Ergebnis haben wir also eine View mit einem Verweis auf den Presenter und einen Presenter mit einem Verweis auf die View (Hinweis: Wir haben hier ein mögliches Leck).

In Anbetracht dieser potenziellen Schwierigkeiten ist es wichtig, dass wir eine geeignete Verwaltungsstruktur einrichten, um den überschüssigen Speicher, der während des Lebenszyklus entsteht, zu entfernen. Es gibt mehrere bewährte Möglichkeiten, dies zu tun:

Erstellen von lebenszyklusfähigen Komponenten mit Android Arch Lifecycle auf Android Studio

Lebenszyklusfähige Komponenten sind intelligent. Sie können auf eine Änderung des Lebenszyklusstatus einer anderen Komponente, wie z. B. Aktivitäten oder Fragmente, reagieren, indem sie z. B. Speicher freisetzen. Das bedeutet, dass der Code leichter und viel speichereffizienter ist.

Arch Lifecycle ist eine neue Bibliothek von Android, die eine Reihe von Werkzeugen zum Erstellen von Lifecycle-fähigen Komponenten bietet. Die Bibliothek arbeitet auf abstrakte Weise, was bedeutet, dass Lifecycle-Besitzer sich nicht mehr um die Verwaltung des Lebenszyklus spezifischer Aufgaben und Aktivitäten kümmern müssen.

Die wichtigsten Tools und Definitionen von Arch Lifecycle sind wie folgt:

  • LifeCycle: Ein Sortiersystem, das definiert, welche Objekte einen Android-Lebenszyklus haben, und es dann ermöglicht, sie zu überwachen.
  • LifecycleObserver: Eine reguläre Schnittstelle, die jedes als Android-Lebenszyklus identifizierte Objekt überwacht und eine einfache Formel verwendet, um jedes wichtige Lebenszyklusereignis zu behandeln.
  • @OnLifecycleEvent: Eine Annotation, die in Klassen verwendet werden kann, die die LifecycleObserver-Schnittstelle implementieren. Sie ermöglicht es uns, wichtige Lifecycle-Ereignisse festzulegen, die die kommentierte Methode auslösen, wenn sie gestartet wird. Hier ist eine Liste aller Ereignisse, die gesetzt werden können:
    • BEI_JEDER
    • BEI_ERSTELLEN
    • BEI_ZERSTÖREN
    • BEI_PAUSE
    • BEI_FORTSETZEN
    • BEI_START
    • ON_STOP
  • LifecycleOwner ist standardmäßig für jede Android Komponente implementiert, deren Lebenszyklus verwaltet werden kann, und gibt dem Entwickler die Kontrolle über jedes Ereignis.

Mit diesen Tools sind wir in der Lage, alle sauberen Tasks an ihre Besitzer (in unserem Fall Presenter) zu senden, sodass wir einen sauberen, entkoppelten Code ohne Lecks haben (zumindest in der Presenter-Schicht).

Hier ist eine super-einfache Implementierung, um Ihnen zu zeigen, wovon wir sprechen:

interface View: MVPView, LifecycleOwnerclass RandomPresenter : Presenter<View>, LifecycleObserver { private lateinit var view: View override fun attachView(view: View) { this.view = view view.lifecycle.addObserver(this) } @OnLifecycleEvent(Lifecycle.Event.On_DESTROY) fun onClear() {//TODO: clean }

Verwendung von Android Arch View Models als Presenter und LiveData

Eine weitere Möglichkeit, Speicherlecks durch Missmanagement im Lebenszyklus zu vermeiden, ist die Verwendung der neuen ViewModels aus der Arch Components Library.

Ein ViewModel ist eine abstrakte Klasse, die eine einzige Funktion namens onClear implementiert, die automatisch aufgerufen wird, wenn ein bestimmtes Objekt entfernt werden muss. Das ViewModel wird vom Framework generiert und ist mit dem Lebenszyklus des Erstellers verbunden (als zusätzlicher Bonus ist es mit Dagger supereinfach zu injizieren)

Neben der Verwendung von ViewModel bietet LiveData auch einen wichtigen Kommunikationskanal. Das Produkt folgt einem reaktiven, leicht zu beobachtenden Muster, was bedeutet, dass es von einzelnen Objekten beobachtet werden kann (was zu weniger gekoppeltem Code führt).

Das Großartige hier ist, dass LiveData von einem Lifecycle-Eigentümer beobachtet werden kann, so dass die Datenübertragung immer vom Lifecycle verwaltet wird – und wir können sicherstellen, dass jede Referenz beibehalten wird, während wir sie verwenden.

Verwendung von LeakCanary und Bugfender

Zusätzlich zu den vorgenannten Schritten möchten wir zwei wichtige Hilfsmittel empfehlen: LeakCanary, ein beliebtes Tool zur Überwachung von Lecks, und unseren eigenen Bugfender.

LeakCanary ist eine Speichererkennungsbibliothek für Android und Java. Es ist Open-Source, so dass eine große Gemeinschaft dahinter steht, und es informiert Sie nicht nur über ein Leck, sondern auch über die wahrscheinliche Ursache.

Bugfender, unser Remote-Protokollierungstool, ermöglicht es Ihnen, einzelne LeakTraces zu debuggen und eine Klasse namens DisplayLeakService zu erweitern, die uns wissen lässt, wenn ein Leck auftritt. Dann können wir es ganz einfach mit Bugfender protokollieren.

public class LeakUploadService extends DisplayLeakService { override fun afterDefaultHandling(heapDump: HeapDump, result: AnalysisResult, leakInfo: String) { if (result.leakFound) { Bugfender.d("LeakCanary", result.toString()) } }}

Darüber hinaus erhalten die Benutzer alle anderen Vorteile von Bugfender, einschließlich der 24/7-Protokollierung (auch wenn das Gerät offline ist), integrierter Absturzberichte und einer einfach zu bedienenden Webkonsole, die einen Drill-Down in einzelne Geräte für eine bessere Kundenbetreuung ermöglicht.

Für weitere Informationen zu Bugfender klicken Sie bitte hier.

Leave a Reply