Arbeitsspeicher der App verwalten

Auf dieser Seite wird beschrieben, wie Sie die Speichernutzung in Ihrer App proaktiv reduzieren können. Informationen dazu, wie das Android-Betriebssystem den Speicher 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 Speicher oft begrenzt ist. Sowohl die Android-Laufzeit (Android Runtime, ART) als auch die virtuelle Dalvik-Maschine führen routinemäßige Garbage Collection 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 durch das Beibehalten von Objektverweisen in statischen Mitgliedsvariablen verursacht werden, und 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 Speicher 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 physischen RAM benötigt Ihre App.

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 dem APK in den RAM geladen wird, wird der gesamte grundlegende Speicherbedarf der App verringert. Außerdem wird durch die Minimierung von Klassen-, Methoden- und Feldnamen in kürzere Kennungen der RAM-Overhead 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 während 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 Keep-Regeln – Ü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, 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.
  • Platzhalter für das gesamte Paket vermeiden:Weitreichende 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 über Aufbewahrungsregeln. Spezifische Muster, die Sie verwenden und vermeiden sollten, finden Sie unter Best Practices für Aufbewahrungsregeln.

Der R8 Configuration Analyzer bietet Informationen zu Ihrer R8-Konfiguration und dazu, wie sich die einzelnen Keep-Regeln auf Ihre App auswirken. 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.

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

Durch die Optimierung 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 weit gefasste 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 von Dutzenden von Funktionen. Sie sollten nicht zu viel Code und Overhead einbinden, die Sie nicht verwenden. 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 Dependency Injection-Bibliothek 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 Annotationen durchsuchen. Dieser Prozess 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 Umfang definiert werden. Wenn Objekte länger als nötig beibehalten werden, indem sie an den falschen Lebenszyklus gebunden werden, kann dies zu Speicherlecks führen.

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 Arbeitsspeicher-bezogene 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 infrage kommt, um beendet zu werden, damit der globale Arbeitsspeicherbedarf des Systems gedeckt werden kann. Wenn Sie die Dauer verlängern möchten, in der sich Ihr Prozess im Cache-Zustand befindet, und die Anzahl der Kaltstarts von Apps verringern möchten, sollten Sie alle Ressourcen, die leicht rekonstruiert werden können, wenn 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 Speicherplatz 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 diesem Wert 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 Speichers, des Gesamtspeichers und des Speicherschwellenwerts – des Speicherniveaus, 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 wahrnehmbare LMKs (Low-Memory-Kills) treten auf, wenn der Arbeitsspeicher des Systems kritisch niedrig ist. Wenn der Arbeitsspeicher niedrig ist, beendet der lmkd-Daemon (Low-Memory-Killer) Prozesse basierend auf ihrem 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 Werte 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 dieser Wert für sichtbare Apps reserviert ist, führt ihre 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 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 Geräteleistung zu gewährleisten.

ProfilingManager zum Erfassen von Arbeitsspeicherproblemen verwenden

Die Android-Plattform bietet ProfilingManager, eine erweiterte Observability-API, mit der Sie Nutzerdaten in der Produktionsumgebung basierend auf von Ihnen festgelegten Triggern erfassen können. So können Sie 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 Arbeitsspeicherlimits ü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 Heapspeicherauszüge oder Heapspeicherprofile 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 nur sparsam nutzen

Wir empfehlen dringend, Dienste nicht unnötig laufen zu lassen. Das ist einer der größten Fehler, die eine Android-App beim Speichermanagement 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 auszuführen. Dieses Verhalten macht Dienstprozesse sehr ressourcenintensiv, da der von einem Dienst verwendete RAM für andere Prozesse nicht verfügbar ist. Dadurch verringert sich die Anzahl der im LRU-Cache gespeicherten Prozesse, die das System verwalten kann, was das Wechseln zwischen Apps weniger effizient macht. Es kann sogar zu Thrashing im System kommen, wenn der Speicher knapp ist und das System nicht genügend Prozesse verwalten kann, um alle derzeit ausgeführten Dienste zu hosten.

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 Ressourcen-Footprint Ihrer App reduzieren beschrieben, führt eine größere kompilierte Codebasis direkt zu einem höheren physischen RAM-Bedarf Ihrer App. 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 geleakten Objekts nicht freigeben kann. Um Speicherlecks zu vermeiden, sollten Sie ein lebenszyklusbewusstes Design implementieren.

Memory Churn vermeiden

Ereignisse der automatischen Speicherbereinigung wirken sich nicht auf die Leistung Ihrer App aus. Viele Garbage Collection-Ereignisse, die in kurzer Zeit auftreten, können jedoch schnell den Akku entladen und die Zeit zum Einrichten von Frames aufgrund der erforderlichen Interaktionen zwischen dem Garbage Collector 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 zu einer großen Anzahl von automatischen Speicherbereinigungsereignissen führen. In der Praxis beschreibt 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 Funktion onDraw() 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 Arbeitsspeicher-Churn hoch ist, bevor Sie sie beheben.

Nachdem Sie die Problembereiche in Ihrem Code identifiziert haben, versuchen Sie, die Anzahl der Zuweisungen in leistungsrelevanten Bereichen zu reduzieren. Erwägen Sie, Elemente aus inneren Schleifen zu entfernen oder in eine factory-basierte 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. Auch wenn Pools Zuweisungen vermeiden, führen sie zu anderen Mehraufwendungen. Die Verwaltung des Pools erfordert beispielsweise 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.