Arbeitsspeicher der App verwalten

Auf dieser Seite wird erläutert, wie Sie die Arbeitsspeichernutzung in Ihrer App proaktiv reduzieren können. Informationen dazu, wie das Android-Betriebssystem den Arbeitsspeicher verwaltet, finden Sie im Überblick zur Speicherverwaltung.

Der Arbeitsspeicher (RAM) ist eine wertvolle Ressource für jede Softwareentwicklungsumgebung und noch wertvoller für ein mobiles Betriebssystem, in dem der physische Arbeitsspeicher oft begrenzt ist. Sowohl die Android-Laufzeit (Android Runtime, ART) als auch die virtuelle Dalvik-Maschine führen automatische Speicherbereinigung durch. Das bedeutet jedoch nicht, dass Sie ignorieren können, wann und wo Ihre App Speicher zuweist und freigibt. Sie müssen weiterhin Speicherlecks vermeiden, die in der Regel dadurch entstehen, dass Objektverweise in statischen Mitgliedsvariablen beibehalten werden. Außerdem müssen Sie alle Reference-Objekte zum richtigen Zeitpunkt freigeben, wie durch Lebenszyklus-Callbacks definiert.

Code und Ressourcen der App verkleinern

Einige Ressourcen und Bibliotheken in Ihrem Code können Arbeitsspeicher belegen, ohne dass Sie es bemerken. Die Gesamtgröße Ihrer App, einschließlich Bibliotheken von Drittanbietern oder eingebetteter Ressourcen, kann sich auf den Speicherverbrauch Ihrer App auswirken. Sie können den Speicherverbrauch Ihrer App verbessern, indem Sie redundante, unnötige oder aufgeblähte Komponenten, Ressourcen und Bibliotheken aus Ihrem Code entfernen.

Gesamtgröße der App durch Aktivieren von R8 reduzieren

Der kompilierte Anwendungscode ist ein aktiver Teil des Laufzeitspeicherbedarfs. Jede Klasse, Methode, Bibliotheksabhängigkeit und Stringkonstante muss beim Ausführen in den RAM geladen werden. Je größer Ihre kompilierte Codebasis ist, desto mehr physischer RAM ist für Ihre App erforderlich.

Sie können R8 verwenden, um den Speicherbedarf Ihrer App zu reduzieren. R8 ist zwar traditionell dafür bekannt, die APK-Größe zu verringern, hat aber auch einen direkten, positiven Einfluss auf den Laufzeitspeicher (RAM). R8 analysiert den Bytecode Ihrer App, um nicht verwendeten Code zu entfernen, redundante Klassen zusammenzuführen, Methoden zu inline zu setzen und Kennungen zu minimieren. Da weniger kompilierter Bytecode aus der APK in den RAM geladen wird, verringert sich der gesamte grundlegende Speicherbedarf der App. Außerdem wird der RAM-Overhead durch die Reduzierung von Klassen-, Methoden- und Feldnamen in kürzere Kennungen direkt reduziert. Durch Optimierungen wie das Zusammenführen von Klassen und das umfangreiche Inlining von Methoden werden auch kostspielige Laufzeit-Lookups und Zuweisungsmuster ersetzt, was zu einem optimierten Heap- und Stapelspeicher führt.

Aufbewahrungsregeln verstehen

Keep-Regeln sind Konfigurationsanweisungen, die R8 mitteilen, welche Teile Ihres Codes bei der Optimierung beibehalten werden sollen. So wird verhindert, dass Code entfernt oder minimiert wird, auf den Ihre App angewiesen ist. Weitere Informationen finden Sie unter Regeln zum Beibehalten – Übersicht.

Schlecht geschriebene Keep-Regeln verhindern, dass R8 große Teile Ihrer Codebasis optimiert. Vermeiden Sie zu allgemeine Aufbewahrungsregeln und halten Sie sich an die folgenden Best Practices:

  • Globale Regeln, die Sie vermeiden sollten:
    • -dontoptimize: Deaktiviert die Optimierung für die gesamte App vollständig, was zu größeren und langsameren ausführbaren Dateien führt.
    • -dontshrink: Verhindert das Entfernen von ungenutztem Code und ungenutzten Ressourcen.
    • -dontobfuscate: Verhindert die Namensminimierung, wodurch wertvolle Speicherplatzersparnisse (insbesondere bei großen Apps) nicht genutzt werden können.
  • Paketweite Platzhalter vermeiden:Allgemeine Regeln wie -keep class com.example.package.** { *; } zwingen R8, jede Klasse, jedes Feld und jede Methode in diesem Paket beizubehalten. Dadurch wird die Möglichkeit von R8, Code in diesem Paket zu entfernen, zu optimieren oder zu minimieren, vollständig unterbunden.

  • Standard-R8-Konfigurationsdatei verwenden:Verwenden Sie immer proguard-android-optimize.txt.

Weitere Informationen zum Schreiben von Aufbewahrungsregeln finden Sie in der Übersicht zu Aufbewahrungsregeln. Informationen zu bestimmten Mustern, die Sie verwenden und vermeiden sollten, finden Sie unter Best Practices für die Verwaltung von Regeln.

Der R8 Configuration Analyzer bietet Einblicke in Ihre R8-Konfiguration und zeigt, wie sich jede Keep-Regel auf Ihre App auswirkt. Weitere Informationen dazu, wie Sie Regeln identifizieren, die die Optimierung blockieren, finden Sie unter R8 Configuration Analyzer.

Vorsicht bei der Verwendung externer Bibliotheken

Externer Bibliothekscode ist oft nicht für mobile Umgebungen geschrieben und kann ineffizient für die Arbeit an einem mobilen Client sein. Wenn Sie eine externe Bibliothek verwenden, müssen Sie sie möglicherweise für Mobilgeräte optimieren. Planen Sie diese Arbeit im Voraus und analysieren Sie die Bibliothek hinsichtlich der Codegröße und des RAM-Bedarfs, bevor Sie sie verwenden.

Selbst einige für Mobilgeräte optimierte Bibliotheken können aufgrund unterschiedlicher Implementierungen Probleme verursachen. Beispielsweise kann eine Bibliothek Lite-Protobufs und eine andere Micro-Protobufs verwenden, was zu zwei verschiedenen Protobuf-Implementierungen in Ihrer App führt. Das kann bei verschiedenen Implementierungen von Logging, Analysen, Frameworks zum Laden von Bildern, Caching und vielen anderen Dingen passieren, die Sie nicht erwarten.

Durch Optimieren Ihrer App mit R8 kann zwar ungenutzter Code aus Abhängigkeiten entfernt werden, die Effektivität ist jedoch oft durch die interne Konfiguration der Bibliothek begrenzt. Beispielsweise können breite Keep-Regeln oder die Verwendung von Reflection in einer Bibliothek verhindern, dass R8 den Code verkleinert, was zu einem größeren Speicherbedarf führt. Strategien zur Auswahl effizienter Bibliotheken finden Sie unter Bibliotheken mit Bedacht auswählen.

Verwenden Sie eine gemeinsam genutzte Bibliothek nicht für nur ein oder zwei Funktionen von Dutzenden. Sie sollten nicht unnötig viel Code und Overhead einbinden. Wenn Sie überlegen, ob Sie eine Bibliothek verwenden sollten, suchen Sie nach einer Implementierung, die Ihren Anforderungen weitgehend entspricht. Andernfalls können Sie auch eine eigene Implementierung erstellen.

Hilt oder Dagger 2 für die Abhängigkeitsinjektion verwenden

Frameworks für die Abhängigkeitsinjektion können den Code, den Sie schreiben, vereinfachen und eine adaptive Umgebung bereitstellen, die für Tests und andere Konfigurationsänderungen nützlich ist.

Wenn Sie ein Framework für die Abhängigkeitsinjektion in Ihrer App verwenden möchten, sollten Sie Hilt oder Dagger in Betracht ziehen. Hilt ist eine Bibliothek für die Abhängigkeitsinjektion für Android, die auf Dagger basiert. Dagger verwendet keine Reflexion, um den Code Ihrer App zu scannen. Sie können die statische Implementierung von Dagger zur Kompilierzeit in Android-Apps verwenden, ohne unnötige Laufzeitkosten oder Arbeitsspeichernutzung.

Andere Frameworks für die Abhängigkeitsinjektion, die Reflection verwenden, initialisieren Prozesse, indem sie Ihren Code nach Anmerkungen durchsuchen. Dieser Vorgang kann deutlich mehr CPU-Zyklen und RAM erfordern und beim Starten der App zu einer spürbaren Verzögerung führen.

Achten Sie bei der Verwendung von Dependency Injection darauf, Speicherlecks zu vermeiden, indem Sie dafür sorgen, dass Objekte entsprechend ihrem Gültigkeitsbereich erstellt werden. Wenn Objekte länger als nötig beibehalten werden, indem sie an den falschen Lebenszyklus gebunden werden, kann dies zu Speicherlecks führen. Weitere Informationen finden Sie in der Anleitung zum Vermeiden von Speicherlecks mit Objekten mit Gültigkeitsbereich.

Bilder bewusst laden

Grafik-Bitmaps sind in der Regel die größten gängigen Objekte im Speicher Ihrer App. Auch wenn Sie mit komprimierten Dateien wie JPEGs arbeiten, muss die Datei in eine unkomprimierte Bitmap umgewandelt werden, um auf dem Bildschirm angezeigt zu werden. Eine kleine komprimierte Bilddatei kann zu einer sehr großen Bitmap werden.

Die meisten Bitmaps verwenden beispielsweise die Konfiguration ARGB_8888. Das bedeutet, dass für jedes Pixel 4 Byte Speicherplatz erforderlich sind – jeweils ein Byte für Rot, Grün, Blau und Alpha (Transparenz). Wenn Sie ein 100 KB großes JPEG in einer Ansicht mit 1.000 × 1.000 Pixeln anzeigen, benötigt die Bitmap 4 Byte für jedes der 1.000.000 Pixel, also insgesamt 4 MB Arbeitsspeicher.

Es gibt verschiedene Möglichkeiten, die Verwendung von Bildern zu optimieren. Wenn Sie beispielsweise Bibliotheken zum Laden von Bildern verwenden, können Sie Speicher freigeben, wenn er nicht benötigt wird. Informationen zum effizienten Umgang mit Bildern finden Sie unter Bitmap-Bilder optimieren.

Verfügbaren Arbeitsspeicher und Arbeitsspeichernutzung überwachen

Sie müssen die Probleme mit der Arbeitsspeichernutzung Ihrer App finden, bevor Sie sie beheben können. Der Memory Profiler von Android Studio hilft Ihnen, Speicherprobleme auf folgende Weise zu finden und zu diagnostizieren:

Der Memory Profiler ist auch in die LeakCanary-Bibliothek zur Speicherleckerkennung integriert. Mit LeakCanary können Sie die Analyse von Speicherlecks vom Testgerät auf Ihren Entwicklungscomputer verlagern, was Ihren Workflow erheblich beschleunigen kann. Weitere Informationen finden Sie in den Versionshinweisen zu Android Studio.

Es gibt weitere Tools, mit denen Sie Speicherprobleme anhand von Daten von Nutzern diagnostizieren können, die Ihre Produktions-App verwenden:

Speicher als Reaktion auf Ereignisse freigeben

Android kann Speicherplatz von Ihrer App zurückfordern oder Ihre App bei Bedarf vollständig beenden, um Speicherplatz für kritische Aufgaben freizugeben. Weitere Informationen finden Sie unter Speicherverwaltung – Übersicht. Um den Systemspeicher weiter auszugleichen und zu vermeiden, dass der Systemprozess Ihrer App beendet werden muss, können Sie die Schnittstelle ComponentCallbacks2 in Ihren Activity-Klassen implementieren. Die bereitgestellte Callback-Methode onTrimMemory() benachrichtigt Ihre App über Lebenszyklus- oder speicherbezogene Ereignisse, die eine gute Gelegenheit für Ihre App darstellen, die Arbeitsspeichernutzung freiwillig zu reduzieren. Wenn Sie Arbeitsspeicher freigeben, wird Ihre App möglicherweise seltener vom Low-Memory-Killer beendet.

Ihre Implementierung von onTrimMemory() sollte sich ausschließlich auf die Ereignisse TRIM_MEMORY_UI_HIDDEN und TRIM_MEMORY_BACKGROUND konzentrieren. Ab Android 14 liefert das System keine Benachrichtigungen mehr für die anderen, alten Konstanten. Diese Konstanten wurden in Android 15 offiziell eingestellt.)

  • TRIM_MEMORY_UI_HIDDEN: Dieses Signal gibt an, dass die Benutzeroberfläche Ihrer App nicht mehr im Blickfeld des Nutzers ist. Dieser Übergang bietet die Möglichkeit, erhebliche Speicherzuweisungen freizugeben, die ausschließlich an die Benutzeroberfläche gebunden sind, z. B. Bitmaps, Videowiedergabepuffer oder komplexe Animationsressourcen.

  • TRIM_MEMORY_BACKGROUND: Dieses Signal gibt an, dass sich Ihr Prozess im Hintergrund befindet und nun für die Beendigung infrage kommt, um den globalen Arbeitsspeicherbedarf des Systems zu decken. Wenn Sie die Dauer verlängern möchten, in der sich Ihr Prozess im Cache-Zustand befindet, und die Anzahl der Kaltstarts der App reduzieren möchten, sollten Sie alle Ressourcen, die leicht rekonstruiert werden können, sobald der Nutzer seine Sitzung fortsetzt, aggressiv freigeben.

In diesem Codebeispiel wird gezeigt, wie Sie den onTrimMemory()-Callback implementieren, um auf verschiedene speicherbezogene Ereignisse zu reagieren:

Kotlin

import android.content.ComponentCallbacks2
// Other import statements.

class MainActivity : AppCompatActivity(), ComponentCallbacks2 {

    // Other activity code.

    /**
     * Release memory when the UI becomes hidden or when system resources become low.
     * @param level the memory-related event that is raised.
     */
    override fun onTrimMemory(level: Int) {

        if (level >= ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN) {
            // Release memory related to UI elements, such as bitmap caches.
        }

        if (level >= ComponentCallbacks2.TRIM_MEMORY_BACKGROUND) {
            // Release memory related to background processing, such as by
            // closing a database connection.
        }
    }
}

Java

import android.content.ComponentCallbacks2;
// Other import statements.

public class MainActivity extends AppCompatActivity
    implements ComponentCallbacks2 {

    // Other activity code.

    /**
     * Release memory when the UI becomes hidden or when system resources become low.
     * @param level the memory-related event that is raised.
     */
    public void onTrimMemory(int level) {

        if (level >= ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN) {
            // Release memory related to UI elements, such as bitmap caches.
        }

        if (level >= ComponentCallbacks2.TRIM_MEMORY_BACKGROUND) {
            // Release memory related to background processing, such as by
            // closing a database connection.
        }
    }
}

Prüfen, wie viel Speicherplatz Sie benötigen

Damit mehrere Prozesse gleichzeitig ausgeführt werden können, legt Android ein festes Limit für die Heap-Größe fest, die jeder App zugewiesen wird. Das genaue Limit für die Heap-Größe variiert je nach Gerät und hängt davon ab, wie viel RAM insgesamt auf dem Gerät verfügbar ist. Wenn Ihre App die Heap-Kapazität erreicht und versucht, mehr Arbeitsspeicher zuzuweisen, löst das System eine OutOfMemoryError aus.

Um zu vermeiden, dass der Arbeitsspeicher ausgeht, können Sie das System abfragen, um zu ermitteln, wie viel Heapspeicher auf dem aktuellen Gerät verfügbar ist. Sie können das System nach dieser Zahl fragen, indem Sie getMemoryInfo() aufrufen. Dadurch wird ein ActivityManager.MemoryInfo-Objekt zurückgegeben, das Informationen zum aktuellen Speicherstatus des Geräts enthält, einschließlich des verfügbaren Arbeitsspeichers, des gesamten Arbeitsspeichers und des Speicherschwellenwerts. Das ist der Speicherstand, bei dem das System beginnt, Prozesse zu beenden. Das ActivityManager.MemoryInfo-Objekt macht auch lowMemory verfügbar. Das ist ein einfacher boolescher Wert, der angibt, ob auf dem Gerät nur noch wenig Speicherplatz verfügbar ist.

Das folgende Beispielcode-Snippet zeigt, wie Sie die Methode getMemoryInfo() in Ihrer App verwenden.

Kotlin

fun doSomethingMemoryIntensive() {

    // Before doing something that requires a lot of memory,
    // check whether the device is in a low memory state.
    if (!getAvailableMemory().lowMemory) {
        // Do memory intensive work.
    }
}

// Get a MemoryInfo object for the device's current memory status.
private fun getAvailableMemory(): ActivityManager.MemoryInfo {
    val activityManager = getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
    return ActivityManager.MemoryInfo().also { memoryInfo ->
        activityManager.getMemoryInfo(memoryInfo)
    }
}

Java

public void doSomethingMemoryIntensive() {

    // Before doing something that requires a lot of memory,
    // check whether the device is in a low memory state.
    ActivityManager.MemoryInfo memoryInfo = getAvailableMemory();

    if (!memoryInfo.lowMemory) {
        // Do memory intensive work.
    }
}

// Get a MemoryInfo object for the device's current memory status.
private ActivityManager.MemoryInfo getAvailableMemory() {
    ActivityManager activityManager = (ActivityManager) this.getSystemService(ACTIVITY_SERVICE);
    ActivityManager.MemoryInfo memoryInfo = new ActivityManager.MemoryInfo();
    activityManager.getMemoryInfo(memoryInfo);
    return memoryInfo;
}

Beenden von Apps aufgrund von wenig Arbeitsspeicher überwachen

Vom Nutzer wahrgenommene Low-Memory-Kills (LMKs) treten auf, wenn der Systemspeicher kritisch niedrig wird. Wenn der Arbeitsspeicher knapp wird, beendet der lmkd-Daemon (Low Memory Killer) Prozesse basierend auf ihrer oom_adj_score. Apps, die im Cache gespeichert sind oder einen Dienst ohne zugehörige Benutzeroberfläche (z. B. einen Job) ausführen, haben die höchsten Punktzahlen und werden zuerst beendet. Wenn der Arbeitsspeicher weiterhin kritisch niedrig ist, muss der Daemon Arbeitsspeicher von Prozessen mit einem oom_adj_score von 0 freigeben. Da diese Punktzahl für sichtbare Apps reserviert ist, führt die Beendigung zu einem sofortigen, nicht ordnungsgemäßen Prozessende. Für den Endnutzer sieht es so aus, als wäre die App abgestürzt. Oft werden dabei die standardmäßigen Mechanismen zum Speichern des Lebenszyklusstatus umgangen, was zu einem Verlust des Nutzerfortschritts führt.

Das Beenden von Vordergrundprozessen ist ein wichtiger Schwerpunkt bei Android Vitals, da es als zuverlässiger Proxy für Speicherverwaltungsprobleme dient. Eine LMK-Rate von über 1% deutet auf einen kritischen Bedarf an sofortigen Maßnahmen hin. Eine niedrige Rate ist jedoch nicht unbedingt ein Indikator für einen guten Zustand. Eine niedrige vom Nutzer wahrgenommene LMK-Rate kann bedeuten, dass der LMK-Daemon häufig Prozesse beendet, während sie im Hintergrund ausgeführt werden. Dies beeinträchtigt die Leistung beim „Warmstart“ und die flüssige Ausführung von Multitasking. Daher empfehlen wir, die Best Practices für den Arbeitsspeicher unabhängig von Ihrem aktuellen LMK-Wert einzuhalten, um langfristige Stabilität und den Gerätezustand zu gewährleisten.

ProfilingManager zum Erfassen von Arbeitsspeicherproblemen verwenden

Die Android-Plattform bietet ProfilingManager, eine erweiterte API zur Beobachtbarkeit, mit der Sie Nutzerdaten in der Produktionsumgebung basierend auf von Ihnen festgelegten Triggern erfassen können. So lassen sich schwer reproduzierbare Speicherprobleme erkennen.

Zwei neue Trigger, die mit Android 17 eingeführt wurden, sind besonders nützlich, um Arbeitsspeicherprobleme zu erkennen:

  • TRIGGER_TYPE_OOM gibt an, dass die App eine OutOfMemoryError ausgelöst hat. Sie wird beim nächsten Start der App nach dem Absturz ausgelöst, wenn sich die App für Profiling-Trigger registriert.
  • TRIGGER_TYPE_ANOMALY wird ausgelöst, wenn das System anomales Verhalten der App erkennt. Dies kann unter anderem durch übermäßige Arbeitsspeichernutzung ausgelöst werden. Sie wird ausgelöst, nachdem die App eine übermäßige Arbeitsspeichernutzung aufweist, bevor das System Maßnahmen ergreift, um den betreffenden Prozess zu beenden. Wenn die App beispielsweise die in Android 17 eingeführten Speicherlimits überschreitet, werden TRIGGER_TYPE_ANOMALY ausgelöst, bevor das System die App beendet.

Weitere Informationen zur programmatischen Registrierung und zum Abrufen von Triggern mit ProfilingManager finden Sie in der Dokumentation zum triggerbasierten Profiling.

Sie können auch app-gesteuerte Profilerstellung verwenden, um Start- und End-Trace-Punkte manuell zu definieren. Wir empfehlen, dies zu tun, um Heap-Dumps oder Heap-Profile manuell in Bereichen zu erfassen, in denen Sie Speicherlecks oder eine übermäßige Arbeitsspeichernutzung vermuten.

Speichereffizientere Codekonstrukte verwenden

Einige Android-Funktionen, Java-Klassen und Codekonstrukte benötigen mehr Arbeitsspeicher als andere. Sie können den von Ihrer App verwendeten Speicherplatz minimieren, indem Sie in Ihrem Code effizientere Alternativen auswählen.

Dienste sparsam nutzen

Wir empfehlen dringend, Dienste nicht unnötig im Hintergrund laufen zu lassen. Das Ausführen unnötiger Dienste ist einer der schlimmsten Fehler, die eine Android-App bei der Speicherverwaltung machen kann. Wenn Ihre App einen Dienst benötigt, um im Hintergrund zu arbeiten, lassen Sie ihn nur dann laufen, wenn er einen Job ausführen muss. Beenden Sie den Dienst, wenn er seine Aufgabe abgeschlossen hat. Andernfalls kann es zu einem Speicherleck kommen.

Wenn Sie einen Dienst starten, versucht das System, den Prozess für diesen Dienst am Laufen zu halten. Dieses Verhalten macht Dienstprozesse sehr teuer, da der von einem Dienst verwendete RAM für andere Prozesse nicht verfügbar ist. Dadurch wird die Anzahl der im LRU-Cache gespeicherten Prozesse reduziert, die das System behalten kann, wodurch das Wechseln zwischen Apps weniger effizient wird. Bei wenig Arbeitsspeicher kann es sogar zu Thrashing im System kommen, wenn das System nicht genügend Prozesse für alle aktuell ausgeführten Dienste aufrechterhalten kann.

Im Allgemeinen sollten Sie persistente Dienste vermeiden, da sie den verfügbaren Speicher ständig belasten. Wir empfehlen stattdessen eine alternative Implementierung wie WorkManager. Weitere Informationen zur Verwendung von WorkManager zum Planen von Hintergrundprozessen finden Sie unter Persistent work.

Optimierte Datencontainer verwenden

Einige der von der Programmiersprache bereitgestellten Klassen sind nicht für die Verwendung auf Mobilgeräten optimiert. Die generische Implementierung HashMap kann beispielsweise speicherineffizient sein, da für jede Zuordnung ein separates Eintragsobjekt erforderlich ist.

Das Android-Framework enthält mehrere optimierte Datencontainer, darunter SparseArray, SparseBooleanArray und LongSparseArray. Die SparseArray-Klassen sind beispielsweise effizienter, da das System den Schlüssel und manchmal auch den Wert nicht automatisch in ein Objekt verpacken muss. Dadurch werden pro Eintrag ein oder zwei weitere Objekte erstellt.

Bei Bedarf können Sie jederzeit zu Roh-Arrays wechseln, um eine schlanke Datenstruktur zu erhalten.

Vorsicht bei Code-Abstraktionen

Entwickler verwenden Abstraktionen häufig als gute Programmierpraxis, da sie die Flexibilität und Wartung des Codes verbessern können. Abstraktionen erfordern jedoch in der Regel mehr Code. Wie unter Code- und Ressourcenbedarf Ihrer App reduzieren beschrieben, erhöht eine größere kompilierte Codebasis direkt den physischen RAM, den Ihre App benötigt. Wenn Ihre Abstraktionen keinen wesentlichen Vorteil bieten, sollten Sie sie vermeiden.

Lite-Protokollpuffer für serialisierte Daten verwenden

Protocol Buffers (Protobufs) sind ein sprach- und plattformneutraler, erweiterbarer Mechanismus, der von Google für die Serialisierung strukturierter Daten entwickelt wurde – ähnlich wie XML, aber kleiner, schneller und einfacher. Wenn Sie Protobufs für Ihre Daten verwenden, sollten Sie immer Lite-Protobufs in Ihrem clientseitigen Code verwenden. Reguläre Protobufs generieren sehr ausführlichen Code, was den Code-Footprint Ihrer App im RAM erhöht (siehe Code-Footprint Ihrer App verwalten und optimieren) und zur Vergrößerung der APK-Größe beiträgt.

Weitere Informationen finden Sie in der Protobuf-Readme-Datei.

Vorsicht vor Speicherlecks

Eine unsachgemäße Referenzverwaltung kann zu Speicherlecks führen, bei denen Objekte ihre Nutzungsdauer überschreiten und der Garbage Collector den Speicher des Leckobjekts nicht freigeben kann. Um Speicherlecks zu vermeiden, sollten Sie ein lebenszyklusbewusstes Design implementieren.

Weitere Informationen finden Sie unter Arbeitsspeicherlecks.

Memory Churn vermeiden

Ereignisse der automatischen Speicherbereinigung wirken sich nicht auf die Leistung Ihrer App aus. Viele automatische Speicherbereinigungsereignisse, die in kurzer Zeit auftreten, können jedoch schnell den Akku entladen und die Zeit für die Einrichtung von Frames aufgrund der erforderlichen Interaktionen zwischen der automatischen Speicherbereinigung und den App-Threads geringfügig verlängern. Je mehr Zeit das System mit der automatischen Speicherbereinigung verbringt, desto schneller entlädt sich der Akku.

Häufig kann Memory Churn dazu führen, dass eine große Anzahl von automatischen Speicherbereinigungen stattfindet. In der Praxis beschreibt der Begriff „Memory Churn“ die Anzahl der zugewiesenen temporären Objekte, die in einem bestimmten Zeitraum auftreten.

Beispielsweise können Sie mehrere temporäre Objekte in einer for-Schleife zuweisen. Alternativ können Sie neue Paint- oder Bitmap-Objekte in der onDraw()-Funktion einer Ansicht erstellen. In beiden Fällen erstellt die App schnell eine große Anzahl von Objekten. Diese können schnell den gesamten verfügbaren Speicher in der jungen Generation belegen und eine automatische Speicherbereinigung erzwingen.

Mit dem Speicher-Profiler können Sie die Stellen in Ihrem Code finden, an denen die Memory Churn hoch ist, bevor Sie sie beheben.

Nachdem Sie die Problembereiche in Ihrem Code identifiziert haben, sollten Sie versuchen, die Anzahl der Zuweisungen in leistungsrelevanten Bereichen zu reduzieren. Erwägen Sie, Elemente aus inneren Schleifen zu entfernen oder in eine fabrikbasierte Zuweisungsstruktur zu verschieben.

Sie können auch prüfen, ob Objektpools für den Anwendungsfall von Vorteil sind. Bei einem Objektpool wird eine Objektinstanz nicht auf den Boden fallen gelassen, sondern in einen Pool freigegeben, wenn sie nicht mehr benötigt wird. Wenn das nächste Mal eine Objektinstanz dieses Typs benötigt wird, können Sie sie aus dem Pool abrufen, anstatt sie zuzuweisen.

Bewerten Sie die Leistung gründlich, um festzustellen, ob ein Objektpool in einer bestimmten Situation geeignet ist. Es gibt Fälle, in denen sich Objektpools negativ auf die Leistung auswirken können. Durch Pools werden zwar Zuweisungen vermieden, es entstehen aber andere Gemeinkosten. Die Verwaltung des Pools erfordert beispielsweise in der Regel eine Synchronisierung, die einen nicht unerheblichen Overhead verursacht. Außerdem kann das Löschen der gepoolten Objektinstanz, um Speicherlecks während der Freigabe zu vermeiden, und die anschließende Initialisierung während der Erfassung einen gewissen Overhead verursachen.

Wenn Sie mehr Objektinstanzen im Pool zurückhalten als nötig, wird auch die automatische Speicherbereinigung belastet. Objektpools reduzieren zwar die Anzahl der Garbage Collection-Aufrufe, erhöhen aber den Aufwand für jeden Aufruf, da er proportional zur Anzahl der aktiven (erreichbaren) Byte ist.