Tools und Leistung

Umsetzbare Strategien zur Reduzierung des Bitmap-Speicherbedarfs Ihrer App

Lautsprecher

Info

Über den Vortrag

Die Verwaltung des Bitmap-Arbeitsspeichers ist entscheidend, um das Risiko von Out-Of-Memory-Fehlern (OOM) in Android-Apps mit vielen visuellen Elementen zu minimieren. Entwickler wissen, dass sie die Bildgröße reduzieren müssen. Um jedoch eine langfristige App-Stabilität zu erreichen, müssen sie zwei weitere wichtige Säulen beherrschen: Wiederverwendung und Recycling. In dieser Session erhältst du eine praktische Anleitung zur Optimierung der Arbeitsspeicherleistung mit modernen Tools, Open-Source-Bibliotheken wie Glide und Coil sowie APIs auf Systemebene.

In dieser Session erfahren Sie, wie Sie mit Android Vitals Speicherregressionen in der Praxis diagnostizieren, erweiterte Caching-Muster (Reuse) mit Glide und Coil implementieren und mit dem onTrimMemory-Callback proaktiv Speicher freigeben (Recycle).

Zusammenfassung

  • Google Play führt Grenzwerte für die Arbeitsspeicherleistung ein, die ab Februar 2027 gelten. Wenn das 90. Perzentil überschritten wird, wirkt sich das direkt auf die Bewertung des Zustands Ihrer App aus und kann die Sichtbarkeit im Google Play Store verringern.
  • Bitmaps werden beim Decodieren nach Abmessungen und Farbtiefe erweitert, sodass sie in den meisten Apps am meisten Arbeitsspeicher verbrauchen. Schon wenige Bilder in voller Auflösung können Hunderte von Megabyte belegen.
  • Führen Sie ein Downsampling von Bildern durch und decodieren Sie nur sichtbare Bereiche, um den Arbeitsspeicherverbrauch zu reduzieren. Fordern Sie passende Bildabmessungen über ein CDN an oder verwenden Sie APIs wie BitmapRegionDecoder, anstatt übergroße Bilder in voller Auflösung zu laden.
  • Mehrere Image Loader vervielfachen die standardmäßige Nutzung des Heap-Cache und duplizieren gespeicherte Bitmaps. Standardisieren Sie auf einen Loader oder legen Sie explizite Speicherbudgets für vorhandene Loader fest.
  • Gib Bildspeicher frei, wenn deine App in den Hintergrund verschoben wird, indem du onTrimMemory verarbeitest. Beenden Sie Hintergrundabfragen und Animationen in onStop, um zu verhindern, dass der Arbeitsspeicher im Hintergrund immer mehr belegt wird.

Ressourcen

Gemini Notebook

Themen

  • bitmap-optimization
  • android-memory
  • android-vitals
  • image-caching
  • memory-profiler

Transkript

Einführung und Übersicht zum Bitmap-Arbeitsspeicher

  • Fung Lam

    Ja, wir haben noch etwa eine Stunde bis zum Mittagessen, also bleiben Sie bitte bei mir.

  • Ich bin Fung Lam, Developer Relations Engineer für Android in London.

  • Ja, danke.

  • Bevor ich anfange, darf ich eine Frage stellen?

  • Wie viele von euch haben sich dieses Jahr ein neues Smartphone gekauft?

  • Wow.

  • Wow.

  • Sei ehrlich, ich nehme es dir nicht übel, wenn du kein Pixel kaufst.

  • Das neue Smartphone ist teurer, oder?

  • Ja, das ist der Grund, warum einige von euch kein neues Smartphone gekauft haben.

  • Warum ist es teurer?

  • Weißt du es?

  • Nein.

  • RAM.

  • RAM, ja, ja.

  • Ja, der Arbeitsspeicher ist wirklich die Herausforderung für Android und für uns als Entwickler.

  • Deshalb möchte ich Ihnen heute zeigen, wie wir die Herausforderung in Bezug auf den Arbeitsspeicher (RAM) bewältigen können.

  • In früheren Sessions auf der Google I/O und der Google I/O Connect ging es um Arbeitsspeicher, die Speicherzuweisung durch das Betriebssystem, die Profilerstellung für Apps, die Codeoptimierung mit R8 und natürlich auch um Bitmaps.

  • Du konntest meine Fotos im Screenshot sehen.

  • Die Speicheroptimierung ist ein großes Thema, das verschiedene Bereiche wie Android Runtime, automatische Speicherbereinigung, Leistungstools, Arbeitsspeicherzuweisung für Objekte, Compiler usw. umfasst. In dieser 30-minütigen Sitzung gebe ich Ihnen einige Informationen dazu, wie Sie den Bitmap-Speicher optimieren können.

  • Sie fragen sich vielleicht, warum Bitmap?

  • Warum sollten wir uns auf den Bitmap-Arbeitsspeicher konzentrieren?

  • Weil ein Bild mehr als tausend Worte sagt.

  • Das stimmt, ich erkläre dir auch, warum.

  • Auf einem Smartphone-Display, z. B. auf einem Pixel 7, Pixel 9 oder Pixel 11 Pro, beträgt die Bildschirmauflösung etwa 2.500 × 1.200 oder 1.400.

  • Für jedes Pixel werden also 32 Bit Farbtiefe verwendet.

  • Für das Bild des gesamten Bildschirms, oh, ich habe nicht genug Finger, aber es werden etwa 15 MB Speicher benötigt, 50 MB.

  • Ein Wort im Betriebssystem ist 64 Bit lang, also 8 Byte.

  • Tausend Wörter sind nur 8 MB.

  • Ja, 8K.

  • Dieses Bild sagt also definitiv mehr als tausend Worte.

  • Ja, das ist wichtig.

  • Ein Bild ist bereits 15 MB groß.

  • Was ist, wenn Ihre App 10 oder 100 Bilder hat?

  • Es ist eine große Zahl.

  • Bitmaps sind in der Regel die größten Arbeitsspeicherverbraucher in Ihrer Anwendung.

  • Wenn das Bild im Arbeitsspeicher decodiert wird, wird es, wie bereits erwähnt, auf die Dimension und die Farbtiefe erweitert.

  • Es geht nicht darum, wie groß die Datei ist oder ob sie im JPEG- oder PNG-Format vorliegt.

  • Nein, sie wird decodiert und verwendet den Arbeitsspeicher.

  • Schon wenige Bilder in voller Auflösung können also Hunderte Megabyte Speicherplatz auf Ihrem Gerät belegen.

  • Zweitens muss das Betriebssystem bei sehr knappem Speicher mehr Energie aufwenden, um den Speicherverbrauch zu verwalten.

  • Das führt dazu, dass die Benutzeroberfläche ruckelt.

  • Oder das Betriebssystem beendet die App, um Speicherplatz freizugeben.

  • Andernfalls treten in Ihrer App Fehler oder Ausnahmen aufgrund von fehlendem Speicherplatz auf.

  • Die gute Nachricht ist, dass dieses Zifferblatt normalerweise mit wenig Aufwand ein großer Erfolg ist.

  • Die meisten Speicherlastspitzen sind auf häufige Fehler zurückzuführen, die sich vermeiden lassen. Wir können sie beheben, ohne die Bildqualität zu beeinträchtigen.

  • Jetzt wissen Sie, dass der Bitmap-Arbeitsspeicher für Ihre App wichtig ist – nicht nur für die reibungslose Ausführung, sondern auch für die Stabilität und die Nutzerbindung.

  • Heute gehen wir fünf einfache Schritte durch, mit denen Sie dafür sorgen können, dass Ihre App reibungslos funktioniert.

  • Zuerst sehen wir uns die technische Qualität von Google Play und die neue Anforderung an die technische Qualität an, einschließlich des bevorstehenden Speicherlimits und der Möglichkeit, das Limit einfach zu überprüfen.

  • Zweitens werde ich eine Fallstudie zu 500 Android-Apps vorstellen, in der es darum geht, wie sie den Arbeitsspeicher nutzen, ob sie das richtig machen oder ob wir etwas daraus lernen können.

  • Anschließend zeige ich dir, wie du die Arbeitsspeichernutzung für Bitmaps optimieren kannst.

  • Abschließend stellen wir Ihnen einige Tools vor, mit denen Sie Ihre eigene App überprüfen können.

Google Play-Qualitätsstandards und häufige Probleme

  • Fung Lam

    Erstens: die neue Qualitätsanforderung von Google Play.

  • Wie oft rufen Sie die Google Play Console auf?

  • Jeden Monat?

  • Wöchentlich?

  • Nie?

  • Lachen bedeutet „Ja“, oder?

  • Nein?

  • Nein.

  • Hast du den Bereich „Android Vitals“ besucht?

  • Ja, ja.

  • Ja, du weißt also definitiv etwas, bevor ich das mache.

  • Bei Google Play gibt es neue Qualitätsanforderungen für den Leistungsgrenzwert.

  • Wir von Android und Google Play möchten, dass Nutzer hochwertige Apps verwenden.

  • Dieser Schwellenwert ist also auch etwas, mit dem wir versuchen, den Endnutzern das Beste zu bieten.

  • Einfach ausgedrückt: „Wenn deine App im Hintergrund oder im sichtbaren Vordergrund ausgeführt wird, darf das 90. Perzentil der Arbeitsspeichernutzung diesen Grenzwert nicht überschreiten.

  • Andernfalls ist Ihre App im Google Play Store möglicherweise weniger sichtbar.“

  • Die Durchsetzung beginnt im Februar 2027 für Apps und Spiele.

  • Wenn dieser Grenzwert überschritten wird, hat das direkte Auswirkungen auf die Bewertung des Zustands Ihrer App.

  • Sehen Sie also in der Google Play Console nach, wie hoch Ihr 90. Perzentil heute ist.

  • Nehmen Sie die Verbesserung vor Ablauf der Frist vor.

  • Das ist die Animation.

  • „Hey, wenn du die Google Play Console aufrufst, siehst du dort Android Vitals.

  • Es gibt einen Teil des Gedächtnisses,

  • Und Sie können das 90. Perzentil sehen.

  • Wenn Sie darauf klicken, sehen Sie den durchschnittlichen Arbeitsspeicherverbrauch im Zeitverlauf.

  • Außerdem gibt es verschiedene Stufen nach RAM, Formfaktor, Bildschirmdichte und Betriebssystemversion, mit denen Sie Speicherengpässe auf bestimmten Einsteigergeräten oder in bestimmten App-Releases isolieren können.

  • Jetzt denken Sie vielleicht: „Danke, dass Sie mir gesagt haben, dass meine Apps den Schwellenwert überschreiten.

  • Aber ich weiß wirklich nicht, was los ist.

  • Würdest du mir helfen?“

  • Ja, deshalb bin ich hier, um Ihnen zu helfen.

  • Ich versuche, es Ihnen einfach zu machen. Stellen Sie sich vor, Sie sehen fern und plötzlich gibt es kein Bild oder keinen Ton mehr. Was würden Sie tun?

  • Sie nehmen die Bedienungsanleitung Ihres Fernsehers zur Hand und blättern zu den letzten Seiten, auf denen sich normalerweise die Anleitung zur Fehlerbehebung befindet. Dort steht dann: „Wenn Ihr Fernseher keinen Ton hat, versuchen Sie es mit dieser oder jener Taste.“

  • Ja, ich habe heute auch einige häufige Probleme festgestellt.

  • Und hoffentlich ist dies Ihre Anleitung zur Fehlerbehebung.

  • Normalerweise sollten wir beim Lesen des TV-Handbuchs auf Seite 1 beginnen, wo sich in der Regel die Diagnosetools befinden, die uns helfen, die Funktionsweise des Fernsehers zu verstehen.

  • Aber jetzt, da ich weiß, dass diese weniger interessant sind, möchte ich heute zuerst die Fehlerbehebung besprechen und später darauf zurückkommen.

  • Und ehrlich gesagt, so lese ich auch die Bedienungsanleitung für den Fernseher.

  • Um das häufigste Problem mit dem Bitmap-Speicher bei der Bitmap-Zuweisung im Android-Ökosystem zu finden, habe ich etwa 500 beliebte Android-Apps untersucht.

  • Ich starte die App, melde mich an, rufe verschiedene Seiten auf, gehe in die Tiefe und kehre zurück.

  • Ich teste die App auch auf verschiedenen Geräten mit unterschiedlichem RAM, z. B. auf einem Android Go-Gerät mit 2 GB Arbeitsspeicher, einem Einsteigergerät mit 4 GB, einem Mittelklassegerät mit 4, 6 oder 8 GB oder einem Premium-Foldable mit einem sehr großen Display, sehr guter Displayqualität und 16 GB Arbeitsspeicher.

  • Wir haben die Speichernutzung auf diesem Gerät in verschiedenen App-Status gemessen, den Heap-Dump erfasst und verschiedene Tools, einschließlich Perfetto AI Skill, verwendet, um die Zuweisung zu analysieren und gängige Anti-Pattern zu identifizieren, also die häufigen Fehler, die wir im großen Maßstab machen.

  • So hat sich der Arbeitsspeicher in verschiedenen Aktivitätszuständen verändert.

  • Mit dieser Analyse identifizieren wir über 15 häufige Probleme, von zu großen Bildern bis hin zu zunehmenden Hintergrund-Caches.

  • Das ist die zentrale Fallstudie, die wir heute vorstellen und besprechen werden.

Fallstudien: Bitmaps skalieren und decodieren

  • Fung Lam

    Ja, die gesamte Fallstudie, die ich jetzt vorstelle, bezieht sich auf die Untersuchung von 500 Apps.

  • Da Sie hier sind, gehe ich davon aus, dass Sie alle Android lieben, oder?

  • Wir alle lieben Android.

  • Angenommen, einer von uns ist auch ein Android-Fan, aber kein Experte für die Android-Entwicklung.

  • Also erstellt er eine Android-App, mit der man Android-Figuren bestellen kann.

  • Aber es gibt einige Fehler.

  • Mal sehen, vielleicht ist das der erste Fehler.

  • „Auf der Produktseite haben wir ein zweispaltiges Layout mit verschiedenen Android-Figuren.

  • Die Bilder sind jeweils etwa 400 × 400 Pixel groß.

  • Die App lädt jedoch unabhängig von der Bildschirmgröße nur ein Bild vom Server herunter.

  • Es wird ein Bild mit 3.000 × 3.000 Pixeln heruntergeladen.

  • Ja, weil die Entwickler sagen: „Hey, Android ist so schön, so toll.

  • Ich möchte den Nutzern das Beste zeigen.“

  • Aber eigentlich nicht, denn die Größe wird durch die Dimension der Bildansicht begrenzt.

  • Selbst wenn Sie das große Bild herunterladen, wird es dem Nutzer also nicht optimal angezeigt.

  • Außerdem hat dieses große Bild zusätzlichen Arbeitsspeicher belegt.

  • Und selbst wenn wir die Daten herunterrechnen, wird dafür zusätzlicher Speicher benötigt.

  • Wie können wir diesem Entwickler helfen, das Problem zu beheben?

  • Wenn Sie sich mit CDNs auskennen, wissen Sie, dass ein CDN einige Bildvorgänge wie Zuschneiden, Größenanpassung, Drehen, Anwenden von Filtern und Umwandeln von quadratischen in runde Bilder verarbeiten kann.

  • Per HTTP-Anfrage kann das CDN etwas verarbeiten.

  • Selbst wenn auf Ihrem Server nur ein Bild serverseitig gehostet wird, können Sie ein CDN verwenden, um ein Bild mit angepasster Größe an Ihr Gerät zu senden.

  • Punkt 1: Wenn Sie das Bild anfordern, versuchen Sie, ein CDN zu verwenden, damit Sie das Bild in der ImageView-Größe anfordern.

  • Wenn das nicht möglich ist, weil es manchmal etwas größer als ImageView sein kann, denken Sie daran, es im Code herunterzurechnen, um die Größe anzupassen.

  • Wenn es also in den Speicher geladen wird, wird ein kleineres Bild geladen, nicht die volle Auflösung des Bildes.

  • Hier ist ein Codebeispiel für Coil.

    // 1. Ask CDN for the exact UI card dimensions (e.g. 540x675)
    val sizedUrl = "$cdnBaseUrl?w=$cardWidthPx&h=$cardHeightPx&fmt=webp"
    
    // 2. Enforce exact downsampling + Hardware Bitmap config
    AsyncImage(
        model = ImageRequest.Builder(LocalContext.current)
            .data(sizedUrl)
            .size(cardWidthPx, cardHeightPx) // Never use Size.ORIGINAL!
            .precision(Precision.EXACT)      // Downsample to exact card size
            .bitmapConfig(Bitmap.Config.HARDWARE)
            .build(),
        contentDescription = product.title
    )
  • Sie können die Größe als gelb markierten Text übergeben.

  • Ja, Sie können die Größe als HTTP in einer HTTP-Anfrage übergeben.

  • Und dann, hier, im hervorgehobenen Code, konnten Sie die Hervorhebung sehen, ja, ja, verwenden Sie Size.ORIGINAL nicht.

  • Ja, verwenden Sie die Breite und Höhe, damit sie als Parameter übergeben werden.

  • Verwenden Sie die Genauigkeit, um die Auflösung des Bildes auf die genaue Größe zu reduzieren.

  • Verwenden Sie nach Möglichkeit Bitmap.Config.HARDWARE, da diese auch weniger Arbeitsspeicher benötigen, weil das Bild direkt im VRAM gespeichert wird.

  • Ähnlich ist es bei dieser App: Es gibt eine neue Version von Android-Sammelfiguren. Es gibt ein langes Bild, das Nutzer nach links und rechts wischen können, um verschiedene Designs der neuen Android-Figur zu sehen.

  • Es gibt aber noch ein weiteres Problem.

  • Bei dem langen Bild befindet sich ein großer Teil außerhalb des Bildschirms. Das bedeutet, dass der Nutzer es nicht sehen kann, das Bild aber im Speicher geladen ist.

  • Könnten wir es besser machen?

  • Ja.

  • Wir könnten es in ein kleineres Bild zerlegen und nur das Bild laden, das auf dem Bildschirm angezeigt wird.

  • Diejenigen, die sich außerhalb des Bildschirms befinden, werden nicht geladen oder entladen.

  • Es wird also weniger Arbeitsspeicher verwendet.

  • In diesem Fall wurden etwa 60 % des Speichers eingespart.

  • Wenn Sie das Rechteck kleiner machen, sparen Sie natürlich mehr.

  • Wenn Sie Nutzern die Möglichkeit geben möchten, die Android-Figur heranzuzoomen, um jedes Detail zu sehen, ist es ein häufiger Fehler, ein sehr großes Bild zu laden, damit es herangezoomt werden kann.

  • Aber nein, das müssen Sie nicht tun.

  • Besser ist es, wenn der Nutzer das bereits geladene Bild mit niedriger Auflösung heranzoomt und Sie, wenn er die Finger nicht mehr zusammenzieht, die Koordinaten berechnen und dann nur diesen Bereich aus dem Bild mit hoher Auflösung laden.

  • Es wird also nur der sichtbare Bereich in den Arbeitsspeicher geladen.

  • Wie geht das?

  • Es gibt eine API namens BitmapRegionDecoder, mit der Sie einen bestimmten Bereich eines Bildes laden können.

  • Und natürlich gibt es auch einige Tools und Bibliotheken, die Ihnen helfen können.

  • Sie könnten auf GitHub danach suchen.

  • Eine Comic-App oder eine Karte verwendet diese Funktion bereits.

  • Sie könnten sie als Referenz verwenden.

  • Außerdem sind wir Menschen und machen Fehler.

  • KI kann Fehler machen.

  • Um Fehler zu vermeiden, können wir beim Laden von Bildern auch ein Limit hinzufügen.

  • In diesem Fall könnte ich beispielsweise sagen: „Hey, lass uns die Fenstergröße oder die Bildschirmabmessung als Limit verwenden und in eine min-Funktion einfügen, min der angeforderten Breite oder der gebundenen Breite.“

    // Query active window bounds instead of hardcoding 1080 / 1920
    val bounds = WindowMetricsCalculator.getOrCreate()
        .computeCurrentWindowMetrics(activity).bounds
    
    // Clamp target dimensions so Bitmap NEVER exceeds the active window
    val safeWidth  = minOf(requestedWidthPx,  bounds.width())
    val safeHeight = minOf(requestedHeightPx, bounds.height())
    
    val bitmap = Bitmap.createBitmap(safeWidth, safeHeight, config)
  • Mit dieser Funktion wird also bei jedem Laden eines Bildes die Grenze nie überschritten.

  • Es wird also kein Bild geladen, das größer als die Bildschirmauflösung ist.

  • Denken Sie daran, nur den sichtbaren Bereich des Bildes zu decodieren, um Speicherplatz zu sparen.

Speicher, Duplikate und Cache verwalten

  • Fung Lam

    Und noch einmal: Diese App stammt nicht von einem Entwickler.

  • Tatsächlich ist es für mehrere Mitwirkende gedacht, die möglicherweise unterschiedliche Tools verwenden.

  • Ein Teil davon ist zum Beispiel die Verwendung von „Oh, tut mir leid“.

  • Ich fange noch einmal von vorn an.

  • Dasselbe gilt für diese App.

  • Hey, ich bin auf der Produktseite, ich habe eine Liste von Produkten und ich habe dieses Bild gesehen, ich füge es dem Einkaufswagen hinzu.

  • Ein paar Minuten später gehe ich zur Kasse und sehe dasselbe Bild wieder.

  • Aber ist das wirklich dasselbe Bild oder sind das zwei verschiedene Objekte im Speicher?

  • Ja, ein häufiger Fehler ist, dass die App viele Bilder lädt.

  • Allerdings gibt es auch viele doppelte Bilder im Speicher.

  • Wenn Sie einen Image Loader verwenden, können Sie einen Schlüssel für das Caching übergeben.

  • Wenn also dasselbe Bild verwendet wird, können Sie das bereits zugewiesene Bitmap-Objekt verwenden, anstatt ein neues Objekt zu erstellen.

  • In Android Studio können Sie mit dem Speicher-Profiler herausfinden, ob Ihre App doppelte Bitmaps enthält.

  • Ich habe das Thema in meiner Google I/O-Session behandelt. Sie können sich das Video dazu auf YouTube ansehen.

  • Wenn Sie Android Studio nicht verwenden, können Sie den Heap-Dump auch mit adb-Befehlen erfassen und dann mit der SQL-Sprache von Perfetto Heap Graph abfragen, ob Ihre App doppelte Bitmaps enthält.

  • Dies ist ein Codebeispiel in Coil, das zeigt, wie ein Schlüssel für das Bitmap-Caching hinzugefügt wird.

    // Strip volatile query tokens (?from=list / ?sig=...) for cache key
    val canonicalKey = MemoryCache.Key(productUrl.substringBefore("?"))
    
    // On Checkout Screen: reuse the 200x200 Bitmap already loaded in the List!
    AsyncImage(
        model = ImageRequest.Builder(context)
            .data(productUrl)
            .placeholderMemoryCacheKey(canonicalKey) // Instant zero-decode reuse!
            .memoryCacheKey(canonicalKey)
            .diskCacheKey(productUrl.substringBefore("?"))
            .build(),
        contentDescription = product.title
    )
  • Und jetzt, ja.

  • Ich kehre zum vorherigen Beispiel zurück.

  • Hallo, meine App hat einen anderen Entwickler.

  • Ein Teil davon verwendet Ansichten, also wird Glide als Bildladeprogramm verwendet.

  • Einige verwenden Coil, weil sie Compose nutzen.

  • Sie haben gerade erwähnt, dass der Image Loader beim Caching hilft. Ist es also ein Problem, wenn ich mehr als einen Image Loader habe?

  • Da stimmt definitiv etwas nicht.

  • Für den Image Loader wird standardmäßig X % der Heap-Speichergröße als Standard-Cache-Größe verwendet.

  • Wenn Sie also einen Image Loader haben, werden X % Ihres Speichers verwendet.

  • Wenn Sie zwei Image Loader haben, wird doppelt so viel Speicher benötigt.

  • Wenn Sie drei Bild-Loader haben, ist es 3X.

  • Wenn Ihr Image Loader nicht denselben Pool verwendet, passiert dasselbe.

  • Wie auf der Produktseite lade ich ein Bild mit Glide und auf der Zahlungsseite wird dasselbe Bild mit Coil geladen.

  • In der Erinnerung sind dann aber immer noch zwei Bilder enthalten.

  • Das ist nicht gut.

  • Ja, um das Problem zu beheben, verwenden wir idealerweise nur einen Image Loader oder einen gemeinsamen Pool für das Caching.

  • Aber was, wenn das zu viel Entwicklungsarbeit ist und ich es nicht schaffe?

  • Eine einfachere Lösung wäre, zu messen, wie viel Prozent des Bildes von diesem Bild-Loader verarbeitet werden und wie viel Prozent von einem anderen Bild-Loader, und dann die Gewichtung für die verschiedenen Bild-Loader festzulegen, so wie in diesem Code.

    val totalBudgetBytes = computeAdaptiveCacheBytes(context)
    val numberOfLoaders = 2 // e.g., Team A (Glide) + Team B (Coil)
    
    // Approach A (With Stats): Weight by image traffic (70% Team A / 30% Team B)
    val loaderABudget = (totalBudgetBytes * 0.70).toInt()
    val loaderBBudget = (totalBudgetBytes * 0.30).toInt()
    
    // Approach B (Simple Fix without stats): Divide by number of loaders!
    val simpleSplitBudget = totalBudgetBytes / numberOfLoaders
    
    MemoryCache.Builder()
        .maxSizeBytes(simpleSplitBudget)
        .build()
  • Ich könnte loaderABudget auf 70 % und loaderBBudget auf 30 % festlegen. Das wäre eine weitere Möglichkeit.

  • Wenn Sie wirklich nicht wissen, wie viele Bilder von jedem Loader verarbeitet werden, ist es trotzdem besser, sie einfach aufzuteilen.

  • „Hey, ich habe zwei Image Loader, also teile ich durch 2; drei Image Loader, also teile ich durch 3, anstatt den Standardwert zu verwenden, da sonst der Arbeitsspeicher überlastet wird.“

  • Und nehmen wir an, dass wir auf der Produktseite erwarten, dass die Nutzer scrollen, um mehr vom Produkt zu sehen.

  • Normalerweise würden wir vielleicht 50 Bilder oder 50 MB an Bildern im Cache speichern.

  • Was aber, wenn die App in den Hintergrund wechselt?

  • Wenn die App im Hintergrund ausgeführt wird, surft der Nutzer möglicherweise nicht.

  • Wenn der Nutzer nicht im Internet surft, sollte ich dann trotzdem diese Menge an Cache-Bildern im Speicher behalten?

  • Nein, wir sollten es freilassen.

  • Ja, wir sollten den Callback „onTrimMemory“ berücksichtigen.

  • Ja, viele Apps implementieren oder berücksichtigen den onTrimMemory-Callback derzeit nicht.

  • Für den onTrimMemory-Callback gibt es zwei Signale, die wir berücksichtigen sollten.

  • Eine davon ist UI_HIDDEN. Das ist so, als hätte der Nutzer die Startbildschirmtaste gedrückt. Es ist nicht sofort nach dem Drücken der Startbildschirmtaste, sondern eine gewisse Zeit danach. Das bedeutet: „Oh, Ihre App befindet sich tatsächlich in einem Zustand, in dem für den Nutzer keine Benutzeroberfläche sichtbar ist.“

  • Und wir könnten erwarten: „Hey, der Nutzer verwendet deine App nicht.

  • Sie sollten die Ressourcen für das Betriebssystem freigeben.“

  • Wir sollten also die Cache-Größe reduzieren.

  • Der andere ist TRIM_MEMORY_BACKGROUND. Das bedeutet, dass die App schon lange im Hintergrund ist und das Betriebssystem jetzt wirklich Ressourcen benötigt.

  • In dieser Phase sollten Sie so viel Speicher wie möglich freigeben, fast 100 %.

  • Dadurch verringert sich auch die Wahrscheinlichkeit, dass Ihre App vom Betriebssystem beendet wird.

  • Weil es so ist: „Hey, sie wollen Speicher, du gibst ihn ihnen, dann kümmert sich das Betriebssystem darum.“

  • Wenn nicht, hat das Betriebssystem keine Wahl und muss etwas tun.

Zunahme des Arbeitsspeichers im Hintergrund verhindern

  • Fung Lam

    Ein weiterer großer Fehler, den ich sehe, ist: „Hey, ich habe gerade erwähnt, dass Ihre App weniger Arbeitsspeicher verwenden sollte, wenn sie im Hintergrund ausgeführt wird.“

  • Ich habe jedoch festgestellt, dass Apps im Hintergrund tatsächlich mehr Arbeitsspeicher verbrauchen.

  • Warum?

  • Deshalb recherchiere ich.

  • Ich habe versucht, herauszufinden, warum.

  • Ein Grund ist: „In ihrer App gibt es Animationen oder ein Karussell.

  • Sie laden beispielsweise alle 15 Sekunden ein neues Bild herunter und zeigen es dem Nutzer an.“

  • Der Entwickler hat die Animation jedoch nicht gestoppt, wenn die App in den Hintergrund wechselt.

  • Auch wenn die App im Hintergrund ausgeführt wird, läuft der Code weiter, ruft neue Bilder ab, decodiert sie und verbraucht mehr Arbeitsspeicher.

  • Das sollte die App also eigentlich nicht tun.

  • Ja.

  • Alle Animationen, das Caching oder nicht relevante Aufgaben sollten beendet werden, wenn die App in den Hintergrund wechselt.

  • Wenn Sie also mit dem Activity-Lebenszyklus vertraut sind, gibt es die Ereignisse „onPause“ und „onStop“. Implementieren Sie „onStop“, „onPause“ und „onDestroyView“, um die Animation zu stoppen, die Bild-Pull-Anfragen zu stoppen oder andere Dinge zu stoppen, die sich auf die Arbeit beziehen, die keine Aufmerksamkeit des Nutzers erfordert.

    override fun onStop() {
        super.onStop()
        // 1. Stop animations so they don't decode next frames in background!
        bannerAnimationView.cancelAnimation()
        animatedImageDrawable?.stop()
    
        // 2. Cancel next-image buffering / prefetch queues!
        nextImagesBufferingJob?.cancel()
        Glide.with(this).pauseRequests()
    }
    
    override fun onDestroyView() {
        super.onDestroyView()
        bannerAnimationView.clearAnimation() // Release resources!
    }

Das 3R-Framework für die Gedächtnisgesundheit

  • Fung Lam

    Ich weiß, dass ich schon viele Fälle mit Ihnen besprochen habe, aber ich habe gesagt: „Ich habe mehr als 15 Fälle.“

  • Wenn ich Ihnen alle Fälle auf einmal nenne, vergessen Sie vielleicht alles.

  • Wenn Sie wieder zu Hause sind, suchen Sie einfach bei Google nach „Hey, wie kann ich meine Speicherprobleme beheben?“.

  • Ich mache es Ihnen also leicht, sich daran zu erinnern.

  • Ja.

  • Ich werde sie mit einem sehr bekannten 3R-Framework verknüpfen, damit Sie sie sich besser merken können.

  • Ich denke, jeder sollte von diesem 3R-Framework gehört haben.

  • Die erste ist „Reduzieren“: Wie lässt sich der Speicherbedarf beim Laden eines Bildes reduzieren?

  • Wie bereits erwähnt, können wir die Auflösung des Bildes reduzieren, um die richtige Größe zu erhalten, wenn wir es vom Server abrufen.

  • Wir optimieren die Farbtiefe oder verwenden nach Möglichkeit Hardware-Bitmaps.

  • Wenn Sie kein CDN verwenden, sollten Sie es ausprobieren.

  • Wenn wir also das Bild vom Server abrufen, wird nur das Bild abgerufen, das am besten zur Größe passt.

  • Wiederverwendung: Wir verwenden den Cache, damit das in den Arbeitsspeicher geladene Bild wiederverwendet wird und wir kein doppeltes Bitmap-Bild im Arbeitsspeicher haben.

  • Und natürlich sollten Sie versuchen, nicht mehrere Bildlader in Ihrem Code zu haben, da dies weniger effizient ist.

  • Reduce, reuse und was ist das dritte?

  • Weißt du es?

  • Recyceln.

  • Erneut versuchen?

  • Recyceln, ja, recyceln.

  • Ja, wir haben in der Grundschule gelernt, wie man reduziert, wiederverwendet und recycelt, aber wir haben in der Grundschule nicht gelernt, wie man programmiert.

  • Ja, recyceln, ja.

  • Ja, es geht darum, wie der Arbeitsspeicher freigegeben wird, damit andere Anwendungen oder das Betriebssystem ihn nutzen können.

  • Es gibt auch Informationen zum Recycling.

  • Zuerst habe ich das onTrimMemory-Ereignis erwähnt. Es gibt zwei Signale, die das Betriebssystem sendet, wenn die Benutzeroberfläche ausgeblendet ist oder das Betriebssystem dringend Ressourcen benötigt.

  • Ja, versuche, diese Anfrage zu bearbeiten.

  • Und wenn Sie Animationen, Bildlader oder andere Aufgaben im Vordergrund ausführen, sollten Sie diese natürlich beenden, wenn das Ereignis „onPause“ (onStop) eintritt.

  • Also: reduzieren, wiederverwenden, recyceln.

  • Leicht zu merken, oder?

Diagnose-Toolkit für Entwickler und KI-Analyse

  • Fung Lam

    Jetzt kommen wir zum weniger interessanten Teil der Bedienungsanleitung des Fernsehers.

  • Wie können wir verschiedene Toolkits verwenden, um dieses Speicherproblem effektiv zu erkennen und zu messen?

  • Die erste Möglichkeit sind die Google Play Console, Android Vitals und die Reporting API.

  • Ja, sehen Sie sich das 90. Perzentil in der Google Play Console an.

  • Wo bist du jetzt?

  • Oder verwenden Sie die Reporting API, wenn Sie die Webseite nicht besuchen.

  • Sie können den Wert weiterhin über die API abrufen.

  • Ja, die Grenzwerte sind 200 MB und 400 MB.

  • Achten Sie darauf, dass Sie den Grenzwert nicht überschreiten.

  • Es gibt auch ADB-Befehle, mit denen Sie beispielsweise den Heap-Dump erfassen oder ein Ereignis senden können, um zu simulieren, dass sich Ihre App jetzt in der Phase UI_HIDDEN befindet.

    adb shell dumpsys meminfo <pkg>  # watch Native Heap!
    adb shell am send-trim-memory <pkg> UI_HIDDEN
  • Wenn Sie Android Studio verwenden, ist das gut.

  • Trotzdem ist er immer ein guter Freund für dich.

  • Sie können den Profiler von Android Studio verwenden, um den Heap-Dump zu erfassen und eine Benutzeroberfläche zu erhalten, auf der Sie die tatsächliche Arbeitsspeichernutzung und die Arbeitsspeicherzuweisung sehen können.

  • Und natürlich sprechen wir über KI mit Gemini in Android Studio oder anderen verfügbaren Android-Skills.

  • Sie können damit den Heap-Dump analysieren und mithilfe von Perfetto-SQL-Traces die Bitmap finden, um normalerweise den Cache-Inhaber zu ermitteln.

  • Wird mein Bild von Glide oder Coil geladen?

  • Gibt es in meiner Anwendung weitere Speicherlecks?

  • Einige von Ihnen sagen vielleicht: „Ich verwende gar keine IDE mehr.

  • Ich bin Prompt Engineer.

  • Ich sende einfach einen Prompt mit dem KI-Agenten, um den Code zu erstellen.“

  • Ja, ich weiß.

  • In meiner Freizeit beschäftige ich mich auch mit Vibe Coding.

  • Ich zeige Ihnen das nur als Beispiel.

  • Ja, das ist nur ein Teil des Prompts, den ich für meine Recherche zu 500 Apps verwende.

  • Ja, nur ein Teil davon.

  • Sie wissen, dass das Display nicht groß ist.

  • Wenn ich mehr hinzufüge, ist es kaum zu sehen.

  • Aber es gibt Ihnen eine Vorstellung davon, wie Sie KI verwenden können, um die Speichernutzung, einschließlich Heap-Dumps, zu automatisieren, zu erfassen, zu analysieren und Ihnen die Ergebnisse bereitzustellen.

  • Und ja, ich könnte hier noch einen Satz hinzufügen: „Bitte stellen Sie mir auch den Code-Fix für meinen Code bereit.“

  • Ja, ich hoffe, dass das Ihre Ängste abbauen kann. Sagen Sie sich: „Hey, das Gedächtnis ist ein großes Gebiet.

  • Ich habe an der Universität im Rahmen meines Informatikstudiums weder Betriebssysteme noch Computerarchitektur studiert.

  • Aber eigentlich ist das gar nicht so beängstigend oder schwierig.“

  • Ja, so können Sie Ihre App ganz einfach analysieren.

  • Bitte probieren Sie es aus.

  • Aber natürlich macht KI Fehler. Daher ist es nach wie vor wichtig, die Grundlagen der Arbeitsspeichernutzung und der Speicherzuweisung durch das Betriebssystem zu kennen.

  • Deshalb sollten Sie sich meinen Vortrag anhören und unser Video auf YouTube ansehen.

Wichtigste Erkenntnisse und Zusammenfassung

  • Fung Lam

    Fassen wir also zusammen: Wir haben etwas zum Mitnehmen.

  • Erstens: Reduzieren.

  • Achten Sie darauf, dass das Bild die richtige Größe hat.

  • Sie teilen das Bild in kleinere Bilder auf, damit nicht alles in den Arbeitsspeicher geladen wird, sondern nur das Bild, das für den Nutzer sichtbar ist.

  • Verwenden Sie nach Möglichkeit immer die Hardware-bitmapConfig oder die Hardware-RGB_565.

  • Wiederverwendung: Sie sollten eine Cache-Strategie haben und einen Image Loader verwenden, um die Anzahl der doppelten Bitmaps im Arbeitsspeicher zu reduzieren.

  • Und natürlich sollten Sie nicht mehrere Bitmap-Bilder verwenden, auch wenn Sie versuchen, die Standard-Cache-Größe dieser Image Loader zu überschreiben.

  • Und wir verwenden sie nicht wieder, wir recyceln sie nicht.

  • Ja.

  • Die Speicherverwaltung Ihrer App sollte sich an den App-Status anpassen.

  • Wenn die Benutzeroberfläche ausgeblendet ist, sollten Sie weniger Arbeitsspeicher verwenden.

  • Wenn das Betriebssystem Sie wirklich auffordert, den Arbeitsspeicher freizugeben, geben Sie so viel wie möglich frei.

  • Und die Ressourcen werden nicht mehr beansprucht, egal ob es sich um Bitmap-, Strom- oder Rechenressourcen handelt.

  • Ja, Banner-Timer, Animation und Bild-Pooling anhalten.

  • Und schließlich sollten Sie die Leistung im Blick behalten.

  • Rufen Sie die Google Play Console regelmäßig auf.

  • Achten Sie darauf, dass es sich um eine Übung handelt.

  • Ja, so können Sie den aktuellen Status des 90. Perzentils der Speichernutzung Ihrer App in der gesamten Nutzerpopulation nachvollziehen.

  • So können Sie Probleme frühzeitig erkennen.

  • Ja.

  • Für die lokale Profilerstellung können Sie den Speicher-Profiler von Android Studio verwenden.

  • Wir haben auch einige ADB-Befehle.

  • Wir haben auch einen Android-Skill.

  • Wenn Sie Statistiken zu echten Nutzern sehen möchten, ist die Google Play Console die richtige Wahl.

  • Außerdem gibt es die Google Play Reporting API, mit der Sie die Daten über die API abrufen können, ohne sich in der Play Console anmelden zu müssen.

  • Weitere Informationen finden Sie wie immer unter developer.android.com/memory.

  • Reduzieren, wiederverwenden und recyceln – mit diesem Gerät.

  • Wir könnten ein besseres… Sie könnten… Ich hoffe, wir können gemeinsam ein besseres Android-Ökosystem aufbauen, um die Herausforderungen zu meistern, vor denen wir stehen.

  • Vielen Dank und viel Spaß beim Programmieren.

  • Ich habe bald Sprechstunde draußen. Wenn Sie Fragen haben, kommen Sie gerne vorbei.

  • Und der nächste Redner hat noch etwas Zeit. Bitte warten Sie, bis er auf die Bühne kommt. Vielen Dank.

Code

Code-Snippets

Kotlin-Snippet bei 11:50 in „Fallstudien: Bitmaps skalieren und decodieren“
// 1. Ask CDN for the exact UI card dimensions (e.g. 540x675)
val sizedUrl = "$cdnBaseUrl?w=$cardWidthPx&h=$cardHeightPx&fmt=webp"

// 2. Enforce exact downsampling + Hardware Bitmap config
AsyncImage(
    model = ImageRequest.Builder(LocalContext.current)
        .data(sizedUrl)
        .size(cardWidthPx, cardHeightPx) // Never use Size.ORIGINAL!
        .precision(Precision.EXACT)      // Downsample to exact card size
        .bitmapConfig(Bitmap.Config.HARDWARE)
        .build(),
    contentDescription = product.title
)
Kotlin-Snippet bei 14:45 in „Fallstudien: Bitmaps skalieren und decodieren“
// Query active window bounds instead of hardcoding 1080 / 1920
val bounds = WindowMetricsCalculator.getOrCreate()
    .computeCurrentWindowMetrics(activity).bounds

// Clamp target dimensions so Bitmap NEVER exceeds the active window
val safeWidth  = minOf(requestedWidthPx,  bounds.width())
val safeHeight = minOf(requestedHeightPx, bounds.height())

val bitmap = Bitmap.createBitmap(safeWidth, safeHeight, config)
Kotlin-Snippet bei 16:51 in „Managing memory, duplicates & cache“
// Strip volatile query tokens (?from=list / ?sig=...) for cache key
val canonicalKey = MemoryCache.Key(productUrl.substringBefore("?"))

// On Checkout Screen: reuse the 200x200 Bitmap already loaded in the List!
AsyncImage(
    model = ImageRequest.Builder(context)
        .data(productUrl)
        .placeholderMemoryCacheKey(canonicalKey) // Instant zero-decode reuse!
        .memoryCacheKey(canonicalKey)
        .diskCacheKey(productUrl.substringBefore("?"))
        .build(),
    contentDescription = product.title
)
Kotlin-Snippet bei 18:17 in „Managing memory, duplicates & cache“
val totalBudgetBytes = computeAdaptiveCacheBytes(context)
val numberOfLoaders = 2 // e.g., Team A (Glide) + Team B (Coil)

// Approach A (With Stats): Weight by image traffic (70% Team A / 30% Team B)
val loaderABudget = (totalBudgetBytes * 0.70).toInt()
val loaderBBudget = (totalBudgetBytes * 0.30).toInt()

// Approach B (Simple Fix without stats): Divide by number of loaders!
val simpleSplitBudget = totalBudgetBytes / numberOfLoaders

MemoryCache.Builder()
    .maxSizeBytes(simpleSplitBudget)
    .build()
Kotlin-Snippet bei 22:05 in „Preventing background memory growth“
override fun onStop() {
    super.onStop()
    // 1. Stop animations so they don't decode next frames in background!
    bannerAnimationView.cancelAnimation()
    animatedImageDrawable?.stop()

    // 2. Cancel next-image buffering / prefetch queues!
    nextImagesBufferingJob?.cancel()
    Glide.with(this).pauseRequests()
}

override fun onDestroyView() {
    super.onDestroyView()
    bannerAnimationView.clearAnimation() // Release resources!
}
Shell-Snippet bei 25:17 in „Developer diagnostic toolkit & AI analysis“
adb shell dumpsys meminfo <pkg>  # watch Native Heap!
adb shell am send-trim-memory <pkg> UI_HIDDEN