Nachdem Sie sich mit der Android-Speicherverwaltung vertraut gemacht und Tools zur Messung der Speichernutzung Ihres Spiels eingerichtet haben, besteht der nächste Schritt darin, die Speichernutzung aktiv zu reduzieren und zu optimieren. Wenn Sie die strengen Limits von Android einhalten, kann das System Ihr Spiel nicht schließen, lange Startzeiten werden vermieden und Ihr Spiel läuft auf allen Geräten gut.
In diesem Leitfaden werden praktische Techniken zur Reduzierung des Speicherbedarfs Ihres Spiels vorgestellt. Dabei liegt der Schwerpunkt auf der Optimierung auf Asset-Ebene, enginespezifischen Konfigurationen und Best Practices für die Speicherverwaltung.
Speichernutzung in Unity reduzieren
Aufgrund des architektonischen Designs von Unity behält die Engine native Blockzuweiser und den verwalteten Heap bei, um sie wiederzuverwenden, anstatt sie sofort an das Betriebssystem zurückzugeben. Das gilt auch, nachdem Assets freigegeben wurden. Insbesondere bleibt der virtuelle Adressraum (reservierter Speicher) für die Lebensdauer des Prozesses reserviert und der physische Speicher (RSS) wird erst nach mehreren Zyklen der automatischen Speicherbereinigung (GC) und des Trimmens zurückgefordert. Folglich kann ein vorübergehender Speicherpeak dazu führen, dass der residente Speicher lange Zeit überlastet bleibt, auch nachdem die tatsächliche Nutzung zurückgegangen ist. Dieses Verhalten erhöht das Risiko von Abstürzen aufgrund von Speichermangel auf Low-End-Geräten und beeinträchtigt die allgemeine Laufzeitstabilität.
Daher muss die Speicheroptimierung von Unity auf drei Säulen basieren, die auf diese Engine-Verhaltensweisen zugeschnitten sind:
- Arbeitsspeichernutzung kontrollieren, um Spitzen bei der Arbeitsspeichernutzung von vornherein zu vermeiden.
- Texturformate und Shader-Varianten verwalten, um zu verhindern, dass unnötige Assets und native Ressourcen instanziiert werden.
- Laufzeit-Codestrukturen refaktorieren, um unnötige Zuweisungen im verwalteten Heap zu vermeiden und so die Häufigkeit der automatischen Speicherbereinigung und die Heap-Erweiterung zu minimieren.
Weitere Informationen finden Sie unter Speicheroptimierung in Unity.
Speichernutzung in der Unreal Engine reduzieren
In der Unreal Engine können Rendering-Pipelines mit hoher Wiedergabetreue und komplexe Objektabhängigkeitsdiagramme sowohl den anonymen RSS als auch den dateibasierten Speicherdruck erheblich erhöhen. Insbesondere führen harte Referenzen und tiefe Blueprint-Vererbungshierarchien dazu, dass nicht verwendete verbundene Assets in den Speicher geladen werden. Darüber hinaus tragen übermäßige Shader-Permutationen, nicht optimierte Textur-Streaming-Pools und nicht komprimierte ELF-Verschiebungstabellen zu einer hohen Speichernutzung bei und erhöhen das Risiko von LMK-Beendigungen (Low Memory Killer).
Daher muss die Speicheroptimierung der Unreal Engine auf drei Säulen basieren, die auf diese Engine-Verhaltensweisen zugeschnitten sind:
- Daten und Logik entkoppeln und harte oder starke Referenzen durch weiche oder schwache Referenzen ersetzen.
- Nicht verwendete mobile Beleuchtungsfunktionen und Permutationsoptionen entfernen, um Pipeline-Statusobjekte (PSOs) und redundante Renderziele zu minimieren. Außerdem ASTC-Komprimierung anwenden und Textur-Streaming-Pools mit Geräteprofilen anpassen.
- RELR- und APS-Verschiebungstabellenkomprimierung aktivieren, um die Größe der ELF-Binärdatei zu reduzieren und den physischen Speicherbedarf zur Laufzeit zu verringern.
Weitere Informationen finden Sie unter Speicheroptimierung in der Unreal Engine.
Optimierung für mehrere Prozesse
Die Arbeitsspeichernutzung von im Cache gespeicherten Prozessen wird bei der Berechnung der Speicherlimits nicht berücksichtigt, da sie keine Auswirkungen auf aktive Apps hat. Wenn Sie den Dienst in einem separaten isolierten Prozess ausführen, kann der Hauptprozess so schnell wie möglich in einen im Cache gespeicherten Zustand übergehen, wodurch die Leistung Ihres Spiels verbessert wird.
Weitere Informationen finden Sie unter Prozessstatus und Arbeitsspeicher verfolgen, Dienstprozess mit Unity isolieren und Dienstprozess mit der Unreal Engine isolieren.
Arbeitsspeichernutzung in Diensten reduzieren, die für Nutzer sichtbar sind
Ihr Spiel muss möglicherweise Logik in einem Dienst ausführen, der für Nutzer sichtbar ist, z. B. um einen großen Download abzuschließen oder für Hintergrund-Voice-Chat-Systeme. Mit diesen Strategien können Sie die Arbeitsspeichernutzung in diesen Szenarien verwalten und reduzieren.
Strategien für große Downloads
Diese Strategien können für große Downloads gelten, die auch dann fortgesetzt werden sollen, wenn der Nutzer Ihr Spiel minimiert hat.
1. Downloadprozess isolieren
Was: Sorgen Sie dafür, dass das Betriebssystem den von Ihrer App nicht verwendeten Speicher sofort zurückfordern kann, indem Sie den Download in einem separaten Prozess ausführen. Es ist möglich, dass der Speicherzuweiser Speicherpoolseiten beibehält und den anonymen RSS-Speicher künstlich hoch hält, auch nachdem Arrays freigegeben wurden. Wenn Sie den Dienst und den Prozess explizit beenden oder verlassen, wird der Speicher an den Betriebssystempool zurückgegeben und Ihr Hauptprozess bleibt davon unberührt.
In Unity: Lagern Sie den Download an einen nativen Android-
Serviceaus, der mit einem Prozess wieandroid:process=":downloader"in einem benutzerdefinierten Manifest deklariert wird, und rufen Sie ihn mit derAndroidJavaClass-JNI von Unity auf. Beenden Sie den Prozess, wenn der Download abgeschlossen ist. Weitere Informationen finden Sie unter Wahrnehmbaren Dienst in einem separaten Prozess mit Unity ausführen.In der Unreal Engine: Deklarieren Sie einen benutzerdefinierten Android-
Servicemit einem Prozess wieandroid:process=":downloader"mit der Unreal Plugin Language und lösen Sie ihn mit C++-JNI aus. Beenden Sie den Prozess, wenn der Download abgeschlossen ist. Weitere Informationen finden Sie unter Wahrnehmbaren Dienst in einem separaten Prozess mit der Unreal Engine ausführen für weitere detaillierte Anleitungen.Für natives Android: Deklarieren Sie einen
Servicein derAndroidManifestmit einem Prozess wieandroid:process=":downloader". Führen Sie den Download in diesem isolierten Prozess aus und rufen SieProcess.killProcess(Process.myPid())auf, wenn der Download abgeschlossen ist.
So hilft das: Die Dauer, in der Speicher belegt ist, wird reduziert und der Download kann fortgesetzt werden, während der vom größeren Hauptprozess verwendete Speicher freigegeben wird.
2. Downloads direkt auf die Festplatte streamen
Was: Streamen Sie Daten direkt vom Netzwerk-Socket auf die Festplatte, indem Sie einen kleinen, wiederverwendbaren Puffer mit fester Größe verwenden, anstatt Netzwerkantworten in einem großen Array zu sammeln, bevor Sie sie schreiben.
In Unity: Verwenden Sie
DownloadHandlerBuffernicht für Asset-Bundles oder große Dateien, da dadurch ein nativer Speicherpuffer zugewiesen wird, der der Datei größe entspricht (anonymer RSS-Speicher). Verwenden Sie stattdessenDownloadHandlerFile, um Byte nativ in einem Hintergrundthread auf die Festplatte zu streamen.In der Unreal Engine: Leiten Sie eingehende Datenblöcke mit
SetResponseBodyReceiveStream()direkt in einFArchive(dateibasiertes Archiv mit dem File Manager der Unreal Engine) weiter, anstatt Nutzlasten aus einemIHttpRequestan einTArray<uint8>anzuhängen.Für natives Android: Leiten Sie den
InputStreammit einem gepoolten Puffer an einenFileOutputStreamweiter, anstatt.readBytes()oder.string()für eine HTTP-Antwort aufzurufen.
So hilft das: Reduziert die maximale Arbeitsspeichernutzung
3. Dekomprimierung heruntergeladener Dateien streamen
Was: Wenn Ihr Download komprimiert ist, umschließen Sie Ihren Netzwerkeingabestream mit einem
Streaming-Dekompressor wie ZipInputStream, anstatt die
Datei herunterzuladen, in den RAM zu laden und dann zu extrahieren.
So hilft das: Reduziert die maximale Arbeitsspeichernutzung
4. An das Betriebssystem delegieren
Was: Um die Speicherverwaltung im Hintergrund ganz zu vermeiden, delegieren Sie die Arbeit an die nativen Android-APIs.
WorkManagerist der moderne, empfohlene Wrapper für denJobSchedulerauf Betriebssystemebene. Unter Android 14 und höher verarbeitetWorkManagervom Nutzer ausgelöste Downloads automatisch als UIDT-Aufgabe (User-Initiated Data Transfer). Diese wird im Prozess Ihrer App ausgeführt. Sie müssen also weiterhin direkt auf die Festplatte streamen, um die Speichernutzung zu minimieren. UIDT schützt Ihre App vor Abstürzen aufgrund von Speichermangel, indem das Betriebssystem den Download bei begrenzten Systemressourcen pausieren und fortsetzen kann.DownloadManagerwird in einem separaten Systemprozess ausgeführt und die Arbeitsspeichernutzung für den Download wird nicht Ihrer App zugeschrieben. Ihre App erhält eine Broadcast-Benachrichtigung, wenn die Datei heruntergeladen und bereit ist.
So hilft das: WorkManager hilft bei Szenarien mit wenig Arbeitsspeicher und DownloadManager reduziert die Arbeitsspeichernutzung Ihrer App.
Strategien für Hilfsdienste
Diese Strategien können für Hilfsdienste gelten, die ein Spiel parallel zum Hauptspielprozess ausführt, z. B. für den Voice-Chat im Hintergrund.
1. Prozess isolieren
Was: Entkoppeln Sie die Funktion, z. B. Ihre Voice-Chat-Lösung, von der Hauptspiel-Engine. Sie können beispielsweise die Mikrofonerfassung und das Netzwerk-Streaming in einem Android-Dienst im Vordergrund ausführen, der einem separaten Prozess zugewiesen ist (im Manifest deklariert, z. B. mit android:process=":voice").
So hilft das: Wenn die App minimiert ist, kann der ressourcenintensive Hauptspielprozess in einen im Cache gespeicherten Zustand mit niedrigerer Priorität übergehen, während der weniger ressourcenintensive Hilfsdienst im Zustand „Dienst, der für Nutzer sichtbar ist“ verbleibt.
2. Nicht verwendeten In-Process-Speicher entfernen
Was: Wenn der Hilfsdienst zu tief in die Spiel-Engine integriert ist, um ihn zu trennen, versuchen Sie, den Prozess so schnell wie möglich zu entlasten, sobald das Spiel in den Hintergrund verschoben oder minimiert wird. Leeren Sie Textur-Caches, entfernen Sie nicht benötigte Szenen, setzen Sie die Engine-Tick- und Render-Raten auf 0 und rufen Sie explizit die automatische Speicherbereinigung auf.
In Unity: Führen Sie diese Bereinigung aus, wenn
OnApplicationPause()ausgelöst wird.Resources.UnloadUnusedAssets()kann hilfreich sein.In der Unreal Engine: Binden Sie die Bereinigungslogik an den Delegaten
ApplicationWillEnterBackgroundDelegate.Für natives Android: Führen Sie die Bereinigung in
onPause()oderonStop()aus. Das Betriebssystem versucht möglicherweise, Ihre Implementierung vononTrimMemory()aufzurufen, bevor es Prozesse mit hoher Speichernutzung beendet.
So hilft das: Reduziert die Arbeitsspeichernutzung, die nicht erforderlich ist, wenn das Spiel nicht im Vordergrund ist.