Arbeitsspeicher der App verwalten

Auf dieser Seite wird erläutert, wie Sie die Arbeitsspeichernutzung in Ihrer App proaktiv reduzieren können. Informationen zur Verwaltung des Arbeitsspeichers durch das Android-Betriebssystem finden Sie unter Übersicht über die Arbeitsspeicherverwaltung.

Random-Access Memory (RAM) ist eine wertvolle Ressource für jede Softwareentwicklungsumgebung, vor allem für mobile Betriebssysteme, in denen der physische Speicher häufig begrenzt ist. Obwohl sowohl die Android Runtime (ART) als auch die virtuelle Dalvik-Maschine eine routinemäßige automatische Speicherbereinigung durchführen, bedeutet dies nicht, dass Sie ignorieren können, wann und wo Ihre App Arbeitsspeicher zuweist und freigibt. Sie müssen weiterhin Speicherlecks vermeiden – in der Regel dadurch verursacht, dass Objektverweise in statischen Mitgliedsvariablen festgehalten werden – und alle Reference-Objekte zur entsprechenden Zeit freigeben, die durch Lebenszyklus-Callbacks definiert ist.

Überwachen Sie den verfügbaren Arbeitsspeicher und die Arbeitsspeichernutzung

Sie müssen die Probleme mit der Arbeitsspeichernutzung Ihrer App ermitteln, bevor Sie sie beheben können. Der Speicher-Profiler in Android Studio unterstützt Sie auf folgende Arten, Speicherprobleme zu finden und zu diagnostizieren:

  • Sehen Sie sich an, wie Ihre App Arbeitsspeicher im Zeitverlauf zuweist. Der Speicher-Profiler zeigt eine Echtzeitgrafik mit der Speichernutzung Ihrer Anwendung, der Anzahl der zugewiesenen Java-Objekte und des Zeitpunkts der automatischen Speicherbereinigung.
  • Initiieren Sie automatische Speicherbereinigungsereignisse und erstellen Sie einen Snapshot des Java-Heaps, während Ihre Anwendung ausgeführt wird.
  • Zeichne die Arbeitsspeicherzuweisungen deiner App auf, untersuche alle zugewiesenen Objekte, sieh dir den Stacktrace für jede Zuweisung an und springe zum entsprechenden Code im Android Studio-Editor.

Arbeitsspeicher als Reaktion auf Ereignisse freigeben

Android kann Arbeitsspeicher Ihrer App freigeben oder die App bei Bedarf vollständig beenden, um Arbeitsspeicher für wichtige Aufgaben freizugeben, wie unter Übersicht über die Speicherverwaltung erläutert. Sie können die ComponentCallbacks2-Schnittstelle in Ihren Activity-Klassen implementieren. So können Sie den Systemarbeitsspeicher weiter ausgleichen und vermeiden, dass das System den Anwendungsprozess beenden muss. Mit der bereitgestellten Callback-Methode onTrimMemory() kann die App auf speicherbezogene Ereignisse warten, wenn sie sich entweder im Vordergrund oder im Hintergrund befindet. Ihre App kann dann Objekte als Reaktion auf den App-Lebenszyklus oder auf Systemereignisse freigeben, die darauf hinweisen, dass das System Arbeitsspeicher freigeben muss.

Im folgenden Beispiel kannst du 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) {

        // Determine which lifecycle or system event is raised.
        when (level) {

            ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN -> {
                /*
                   Release any UI objects that currently hold memory.

                   The user interface moves to the background.
                */
            }

            ComponentCallbacks2.TRIM_MEMORY_RUNNING_MODERATE,
            ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW,
            ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL -> {
                /*
                   Release any memory your app doesn't need to run.

                   The device is running low on memory while the app is running.
                   The event raised indicates the severity of the memory-related event.
                   If the event is TRIM_MEMORY_RUNNING_CRITICAL, then the system
                   begins stopping background processes.
                */
            }

            ComponentCallbacks2.TRIM_MEMORY_BACKGROUND,
            ComponentCallbacks2.TRIM_MEMORY_MODERATE,
            ComponentCallbacks2.TRIM_MEMORY_COMPLETE -> {
                /*
                   Release as much memory as the process can.

                   The app is on the LRU list and the system is running low on memory.
                   The event raised indicates where the app sits within the LRU list.
                   If the event is TRIM_MEMORY_COMPLETE, the process is one of the
                   first to be terminated.
                */
            }

            else -> {
                /*
                  Release any non-critical data structures.

                  The app receives an unrecognized memory level value
                  from the system. Treat this as a generic low-memory message.
                */
            }
        }
    }
}

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) {

        // Determine which lifecycle or system event is raised.
        switch (level) {

            case ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN:

                /*
                   Release any UI objects that currently hold memory.

                   The user interface moves to the background.
                */

                break;

            case ComponentCallbacks2.TRIM_MEMORY_RUNNING_MODERATE:
            case ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW:
            case ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL:

                /*
                   Release any memory your app doesn't need to run.

                   The device is running low on memory while the app is running.
                   The event raised indicates the severity of the memory-related event.
                   If the event is TRIM_MEMORY_RUNNING_CRITICAL, then the system
                   begins stopping background processes.
                */

                break;

            case ComponentCallbacks2.TRIM_MEMORY_BACKGROUND:
            case ComponentCallbacks2.TRIM_MEMORY_MODERATE:
            case ComponentCallbacks2.TRIM_MEMORY_COMPLETE:

                /*
                   Release as much memory as the process can.

                   The app is on the LRU list and the system is running low on memory.
                   The event raised indicates where the app sits within the LRU list.
                   If the event is TRIM_MEMORY_COMPLETE, the process is one of the
                   first to be terminated.
                */

                break;

            default:
                /*
                  Release any non-critical data structures.

                  The app receives an unrecognized memory level value
                  from the system. Treat this as a generic low-memory message.
                */
                break;
        }
    }
}

Prüfen, wie viel Arbeitsspeicher benötigt wird

Android legt ein festes Limit für die Heap-Größe fest, um mehrere laufende Prozesse zu ermöglichen. Das genaue Heap-Größenlimit hängt von den Geräten ab, je nachdem, wie viel RAM insgesamt auf dem Gerät verfügbar ist. Wenn Ihre App die Heap-Kapazität erreicht und versucht, mehr Arbeitsspeicher zuzuweisen, gibt das System eine OutOfMemoryError aus.

Um zu vermeiden, dass nicht genügend Arbeitsspeicher zur Verfügung steht, können Sie das System abfragen, um festzustellen, wie viel Heap-Speicherplatz auf dem aktuellen Gerät verfügbar ist. Sie können das System nach dieser Zahl abfragen, indem Sie getMemoryInfo() aufrufen. Dadurch wird ein ActivityManager.MemoryInfo-Objekt zurückgegeben, das Informationen zum aktuellen Arbeitsspeicherstatus des Geräts enthält, einschließlich des verfügbaren Arbeitsspeichers, des Gesamtarbeitsspeichers und des Arbeitsspeicherschwellenwerts, also der Arbeitsspeicherebene, bei der das System damit beginnt, Prozesse zu beenden. Das ActivityManager.MemoryInfo-Objekt macht auch lowMemory verfügbar. Dabei handelt es sich um einen einfachen booleschen Wert, der Ihnen mitteilt, ob auf dem Gerät nur noch wenig Speicherplatz zur Verfügung steht.

Das folgende Beispiel für ein Code-Snippet zeigt, wie Sie die Methode getMemoryInfo() in Ihrer Anwendung 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;
}

Speichereffizientere Codekonstrukte verwenden

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

Dienste sparsam verwenden

Wir empfehlen dringend, Dienste nicht weiter auszuführen, wenn dies nicht erforderlich ist. Unnötige Dienste auszuführen, ist einer der schlimmsten Fehler bei der Arbeitsspeicherverwaltung, die Android-Apps machen können. Wenn Ihre Anwendung einen Dienst benötigt, der im Hintergrund ausgeführt werden kann, sollte sie nur dann ausgeführt werden, wenn sie 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, zieht das System es vor, den Prozess für diesen Dienst weiter auszuführen. Dieses Verhalten macht Dienstprozesse sehr teuer, da der von einem Dienst verwendete RAM für andere Prozesse nicht verfügbar ist. Dies reduziert die Anzahl der im Cache gespeicherten Prozesse, die das System im LRU-Cache behalten kann, und macht den Anwendungswechsel weniger effizient. Wenn der Arbeitsspeicher knapp wird und das System nicht genügend Prozesse aufrechterhalten kann, um alle derzeit ausgeführten Dienste zu hosten, kann dies sogar zu einer Kompromittierung des Systems führen.

Im Allgemeinen sollten Sie keine persistenten Dienste verwenden, da sie dauerhaft den verfügbaren Arbeitsspeicher erfordern. Wir empfehlen stattdessen die Verwendung einer alternativen Implementierung wie WorkManager. Weitere Informationen zur Planung von Hintergrundprozessen mit WorkManager finden Sie unter Persistente Arbeit.

Optimierte Datencontainer verwenden

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

Das Android-Framework umfasst mehrere optimierte Datencontainer, darunter SparseArray, SparseBooleanArray und LongSparseArray. Die SparseArray-Klassen sind beispielsweise effizienter, da sie vermeiden, dass das System den Schlüssel und manchmal den Wert automatisch verpacken muss, wodurch ein oder zwei weitere Objekte pro Eintrag erstellt werden.

Bei Bedarf können Sie für eine schlanke Datenstruktur jederzeit zu Roh-Arrays wechseln.

Vorsicht bei Codeabstraktionen

Entwickler verwenden oft Abstraktionen als bewährte Programmiermethode, da sie die Flexibilität und Wartung von Code verbessern können. Abstraktionen sind jedoch erheblich kostspieliger, da sie im Allgemeinen mehr Code erfordern, der ausgeführt werden muss, sowie mehr Zeit und RAM für die Zuordnung des Codes im Speicher. Wenn Ihre Abstraktionen nicht besonders vorteilhaft sind, sollten Sie sie vermeiden.

Lite-Protokollpuffer für serielle Daten verwenden

Protokollzwischenspeicher (Protobufs) sind ein sprachneutraler, plattformneutraler, erweiterbarer Mechanismus, der von Google zur Serialisierung strukturierter Daten entwickelt wurde – ähnlich wie XML, aber kleiner, schneller und einfacher. Wenn Sie Protobufs für Ihre Daten nutzen, verwenden Sie immer Lite-Protobufs in Ihrem clientseitigen Code. Reguläre Protokollzwischenspeicher generieren extrem ausführlichen Code, der viele Probleme in Ihrer App verursachen kann, z. B. eine erhöhte RAM-Nutzung, eine deutliche Erhöhung der APK-Größe und eine langsamere Ausführung.

Weitere Informationen finden Sie im Protobuf-Readme.

Speicherabwanderung vermeiden

Ereignisse für die automatische Speicherbereinigung haben keinen Einfluss auf die Leistung Ihrer App. Viele Ereignisse für die automatische Speicherbereinigung, die innerhalb eines kurzen Zeitraums auftreten, können den Akku jedoch schnell entladen und die Einrichtungszeit für Frames aufgrund der erforderlichen Interaktionen zwischen dem automatischen Speicher und den App-Threads leicht erhöhen. Je mehr Zeit das System für die automatische Speicherbereinigung benötigt, desto schneller wird der Akku.

Häufig kann die Speicherabwanderung zu einer großen Anzahl von Ereignissen der automatischen Speicherbereinigung führen. In der Praxis beschreibt die Speicherabwanderung die Anzahl der zugewiesenen temporären Objekte, die in einer bestimmten Zeit auftreten.

So können Sie beispielsweise mehrere temporäre Objekte innerhalb 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 und mit großem Volumen viele Objekte. Diese können schnell den gesamten verfügbaren Arbeitsspeicher der jungen Generation verbrauchen und ein automatisches Speicherbereinigungsereignis erzwingen.

Verwenden Sie den Speicher-Profiler, um die Stellen im Code zu finden, an denen die Speicherabwanderung hoch ist, bevor Sie sie beheben können.

Nachdem Sie die Problembereiche in Ihrem Code identifiziert haben, versuchen Sie, die Anzahl der Zuweisungen in leistungskritischen Bereichen zu reduzieren. Ziehen Sie Elemente aus inneren Schleifen heraus oder verschieben Sie sie in eine fabrikbasierte Zuweisungsstruktur.

Außerdem können Sie prüfen, ob Objektpools für den Anwendungsfall vorteilhaft sind. Mit einem Objektpool, anstatt eine Objektinstanz auf den Boden zu setzen, geben Sie sie in einen Pool frei, wenn er 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 die Leistung durch Objektpools verschlechtern kann. Auch wenn Pools Zuweisungen vermeiden, führen sie zu weiteren Aufwand. Die Verwaltung des Pools umfasst beispielsweise in der Regel eine Synchronisierung, die einen nicht vernachlässigbaren Aufwand verursacht. Auch das Löschen der gemeinsamen Objektinstanz, um Speicherlecks während des Release zu vermeiden, und die anschließende Initialisierung während der Erfassung kann einen Overhead ungleich null haben.

Wenn mehr Objektinstanzen im Pool als erforderlich zurückgehalten werden, wird die automatische Speicherbereinigung ebenfalls belastet. Während Objektpools die Anzahl der Aufrufe der automatischen Speicherbereinigung reduzieren, erhöhen sie letztendlich den Arbeitsaufwand für jeden Aufruf, da dieser proportional zur Anzahl der (erreichbaren) Livebyte ist.

Entfernen von arbeitsspeicherintensiven Ressourcen und Bibliotheken

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

APK-Gesamtgröße reduzieren

Sie können die Arbeitsspeichernutzung Ihrer App erheblich reduzieren, indem Sie die Gesamtgröße Ihrer App reduzieren. Bitmapgröße, Ressourcen, Animationsframes und Drittanbieterbibliotheken können alle zur Größe Ihrer App beitragen. Android Studio und das Android SDK bieten mehrere Tools, mit denen Sie die Größe Ihrer Ressourcen und externen Abhängigkeiten reduzieren können. Diese Tools unterstützen moderne Methoden zur Codekomprimierung, z. B. die R8-Kompilierung.

Weitere Informationen zum Verringern der App-Gesamtgröße finden Sie unter App-Größe reduzieren.

Hilt oder Dagger 2 für Abhängigkeitsinjektionen verwenden

Abhängigkeitsinjektions-Frameworks können den von Ihnen geschriebenen Code vereinfachen und eine adaptive Umgebung bereitstellen, die für Tests und andere Konfigurationsänderungen nützlich ist.

Wenn Sie in Ihrer App ein Abhängigkeitsinjektions-Framework verwenden möchten, sollten Sie Hilt oder Dagger nutzen. Hilt ist eine Abhängigkeitsinjektionsbibliothek für Android, die auf Dagger ausgeführt wird. Dagger verwendet keine Reflexion, um den Code Ihrer App zu scannen. Sie können die statische Kompilierungszeit von Dagger in Android-Apps ohne unnötige Laufzeitkosten oder Arbeitsspeichernutzung verwenden.

Andere Frameworks für Abhängigkeitsinjektionen, die Prozesse zur Spiegelungsinitialisierung verwenden, scannen Ihren Code auf Anmerkungen. Dieser Prozess kann erheblich mehr CPU-Zyklen und RAM erfordern und zu einer deutlichen Verzögerung beim Start der Anwendung führen.

Vorsicht bei der Verwendung externer Bibliotheken

Externer Bibliothekscode wird häufig nicht für mobile Umgebungen geschrieben und kann für die Arbeit an einem mobilen Client ineffizient sein. Wenn Sie eine externe Bibliothek verwenden, müssen Sie diese möglicherweise für Mobilgeräte optimieren. Planen Sie diese Arbeit im Voraus und analysieren Sie die Bibliothek in Bezug auf Codegröße und RAM-Speicherplatz, 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 verwenden, während eine andere Mikro-Protobufs verwendet. Dies führt zu zwei unterschiedlichen Protokollpuffer-Implementierungen in Ihrer Anwendung. Dies kann bei verschiedenen Implementierungen von Logging, Analysen, Frameworks zum Laden von Bildern, Caching und vielen anderen Dingen passieren, die Sie nicht erwarten.

ProGuard kann zwar mit den richtigen Flags APIs und Ressourcen entfernen, die großen internen Abhängigkeiten einer Bibliothek jedoch nicht. Die in diesen Bibliotheken gewünschten Funktionen erfordern möglicherweise Abhängigkeiten auf niedrigerer Ebene. Dies wird besonders problematisch, wenn Sie eine Activity-Unterklasse aus einer Bibliothek verwenden, die ein breites Spektrum an Abhängigkeiten haben kann, wenn Bibliotheken eine Reflexion verwenden. Dies ist üblich und erfordert eine manuelle Anpassung von ProGuard, damit es funktioniert.

Vermeiden Sie die Nutzung einer gemeinsam genutzten Bibliothek nur für ein oder zwei von Dutzenden Funktionen. Laden Sie nicht zu viel Code und Aufwand hoch, den Sie nicht brauchen. Wenn Sie überlegen, ob Sie eine Bibliothek verwenden möchten, achten Sie auf eine Implementierung, die Ihrer Anforderung am besten entspricht. Andernfalls können Sie Ihre eigene Implementierung erstellen.