Android App im Speicher. Optimierungsbericht für Yandex.Luncher

Das leichte Android Go-System stellt erhöhte Anforderungen an vorinstallierte Anwendungen - Größe und Speicher. Wir standen vor der Herausforderung, diese Anforderungen zu erfüllen. Wir haben eine Reihe von Optimierungen durchgeführt und beschlossen, die Architektur unserer grafischen Shell - Yandex.Luncher - ernsthaft zu ändern. Der Leiter des Entwicklungsteams für mobile Anwendungen, Alexander Starchenko, teilte diese Erfahrung.


- Mein Name ist Alexander, ich komme aus St. Petersburg, aus dem Team, das Yandex.Loncher und Yandex.Phone entwickelt. Heute werde ich Ihnen erzählen, wie wir den Speicher in Launcher optimiert haben. Zunächst erkläre ich kurz, was Launcher ist. Als nächstes diskutieren wir die Gründe, warum wir den Speicher optimieren müssen. Danach werden wir überlegen, wie das Gedächtnis richtig gemessen wird und woraus es besteht. Dann lass uns weiter üben. Ich werde darüber sprechen, wie wir das Gedächtnis in Launcher optimiert haben und wie wir zu einer radikalen Lösung des Problems gekommen sind. Und am Ende werde ich darüber sprechen, wie wir die Speichernutzung überwachen und wie wir sie unter Kontrolle halten.



"Launcher" oder "Launcher" - nicht so wichtig. Wir bei Yandex haben ihn Launcher genannt, und im Bericht werde ich das Wort "Launcher" verwenden.



Ein weiterer wichtiger Punkt: Launcher ist über Voreinstellungen weit verbreitet, dh wenn Sie ein neues Telefon kaufen, kann sich Yandex.Loncher häufig als der einzige Anwendungsmanager und Home-Desktop-Manager auf Ihrem Telefon herausstellen.

Nun zu den Gründen, warum wir den Speicher optimieren müssen. Ich werde mit unserem Grund beginnen. Kurz gesagt, das ist Android Go. Und jetzt länger. Ende 2017 stellte Google Android Oreo und seine spezielle Version, die Android Oreo Go Edition, vor. Was ist das Besondere daran? Diese Version wurde für Low-End-Telefone mit bis zu einem Gigabyte RAM entwickelt. Was ist sie sonst noch besonders? Für Anwendungen, die auf dieser Android-Version vorinstalliert sind, stellt Google zusätzliche Anforderungen. Insbesondere - die Anforderungen an den RAM-Verbrauch. Grob gesagt wird einige Zeit nach dem Start der Speicher der Anwendung entfernt, und die Größe sollte für Launcher 30-50 Megabyte nicht überschreiten, abhängig von der Größe des Telefonbildschirms. 30 auf den kleinsten, 50 auf den großen Bildschirmen.

Es sollte auch beachtet werden, dass Google diesen Bereich weiterentwickelt und es bereits eine Android Pie Go-Edition gibt.

Welche anderen Gründe könnte es für die Optimierung der Speichernutzung geben? Erstens ist es weniger wahrscheinlich, dass Ihre Anwendung heruntergeladen wird. Zweitens wird es schneller funktionieren, da es weniger wahrscheinlich ist, dass der Garbage Collector funktioniert und der Speicher weniger häufig zugewiesen wird. Zusätzliche Objekte werden nicht erstellt, zusätzliche Ansichten werden nicht aufgeblasen usw. Nach unserer Erfahrung führt dies indirekt zu einer Verringerung der apk-Größe Ihrer Anwendung. All dies zusammen bietet Ihnen mehr Installationen und bessere Bewertungen bei Google Play.

Ok, jetzt wissen wir, warum wir den Speicher optimieren müssen. Mal sehen, mit welchen Mitteln man es misst und woraus es besteht.

Link von der Folie

Wahrscheinlich haben viele von Ihnen dieses Bild gesehen. Dies ist ein Screenshot von Android Studio Profile aus einer Speicheransicht. Dieses Tool wird auf developer.android.com ausführlich beschrieben. Wahrscheinlich haben viele von Ihnen sie benutzt. Wer nicht benutzt - versuchen.

Was ist hier gut? Es ist immer zur Hand. Es ist bequem im Entwicklungsprozess zu verwenden. Es hat jedoch einige Nachteile. Hier sind nicht alle Zuordnungen Ihrer Anwendung sichtbar. Beispielsweise sind heruntergeladene Schriftarten hier nicht sichtbar. Mit Hilfe dieses Tools ist es auch unpraktisch zu sehen, welche Klassen in den Speicher geladen werden, und Sie können dieses Tool nicht im automatischen Modus verwenden, dh Sie können keine Art von automatischem Test basierend auf dem Android Studio-Profil konfigurieren.

Links von der Folie: erstens , zweitens

Das folgende Tool existiert seit der Android-Entwicklung in Eclipse, kurz Memory Analyzer, MAT. Es wird als eigenständige Anwendung bereitgestellt und ist mit Speicherabbildern kompatibel, die Sie in Android Studio speichern können.

Dazu benötigen Sie ein kleines Dienstprogramm, einen professionellen Konverter. Es kommt mit der Android Go Edition und hat mehrere Vorteile. Beispielsweise können Pfade zu gs-Wurzeln erstellt werden. Es hat uns sehr geholfen, genau zu sehen, welche Klassen von Launcher geladen werden und wann sie geladen werden. Mit dem Android Studio Profiler konnten wir dies nicht tun.



Das nächste Tool ist das Dienstprogramm dumpsys, insbesondere dumpsys meminfo. Hier sehen Sie einen Teil der Ausgabe dieses Befehls. Es bietet ein ziemlich hohes Wissen über den Speicherverbrauch. Es hat jedoch bestimmte Vorteile. Es ist bequem im automatischen Modus zu verwenden. Sie können problemlos Tests konfigurieren, die diesen Befehl einfach aufrufen. Außerdem wird der Speicher für alle Prozesse sofort angezeigt. Und zeigt alle Standorte. Soweit wir wissen, verwendet Google den Testwert dieses Tools im Testprozess.

Lassen Sie uns anhand eines Ausgabebeispiels kurz beschreiben, woraus der Anwendungsspeicher besteht. Der erste ist Java Heap, alle Speicherorte Ihres Java- und Kotlin-Codes. Normalerweise ist dieser Abschnitt groß genug. Als nächstes kommt der Native Heap. Hier sind Zuordnungen aus dem nativen Code. Auch wenn Sie in Ihrer Anwendung keinen expliziten nativen Code verwenden, sind hier Zuordnungen vorhanden, da viele Android-Objekte - dieselbe Ansicht - nativen Speicher zuweisen. Der nächste Abschnitt ist Code. Alles, was mit dem Code zu tun hat, wird hier angezeigt: Bytecode, Schriftarten. Code kann auch sehr groß sein, wenn Sie viele nicht optimierte Bibliotheken von Drittanbietern verwenden. Das Folgende ist der Software-Stack aus Java und nativem Code, normalerweise klein. Als nächstes kommt der Grafikspeicher. Dazu gehören Oberflächen, Texturen, dh der Speicher, der sich zwischen der CPU und der GPU verteilt, wird zum Rendern verwendet. Als nächstes folgt der Abschnitt Private Andere. Dies schließt alles ein, was nicht in die obigen Abschnitte fällt, alles, was das System nicht darüber streuen konnte. Normalerweise handelt es sich hierbei um native Zuordnungen. Als nächstes folgt der Abschnitt System. Dies ist der Teil des Systemspeichers, der Ihrer Anwendung zugeordnet ist.

Und am Ende haben wir GESAMT, das ist die Summe aller aufgelisteten Abschnitte. Wir wollen es reduzieren.



Was ist sonst noch wichtig über die Speichermessung zu wissen? Erstens kontrolliert unsere Anwendung nicht alle Zuordnungen vollständig. Das heißt, wir als Entwickler haben nicht die volle Kontrolle darüber, welcher Code heruntergeladen wird.

Folgendes. Der Anwendungsspeicher kann viel springen. Während des Messvorgangs können Sie starke Messunterschiede feststellen. Dies kann sowohl auf die benötigte Zeit als auch auf verschiedene Szenarien zurückzuführen sein. In dieser Hinsicht ist es sehr wichtig, das Gedächtnis unter den gleichen Bedingungen zu optimieren, wenn wir es optimieren und analysieren. Idealerweise auf demselben Gerät. Noch besser, wenn Sie die Möglichkeit haben, den Garbage Collector aufzurufen.

Großartig. Wir wissen, warum wir das Gedächtnis optimieren müssen, wie wir es richtig messen können und woraus es besteht. Lassen Sie uns üben, und ich werde Ihnen erklären, wie wir den Speicher in Launcher optimiert haben.



Das war zunächst die Situation. Wir hatten drei Prozesse, die insgesamt etwa 120 Megabyte zugewiesen haben. Dies ist fast viermal mehr, als wir erhalten möchten.



In Bezug auf die Zuweisung des Hauptprozesses gab es einen großen Abschnitt von Java Heap, viele Grafiken, großen Code und einen ziemlich großen nativen Heap.



Zuerst gingen wir das Problem ziemlich naiv an und beschlossen, einige Empfehlungen von Google aus einigen Quellen zu befolgen und zu versuchen, das Problem schnell zu lösen. Wir haben auf die Synthesemethoden hingewiesen, die während des Kompilierungsprozesses generiert werden. Wir hatten mehr als zweitausend von ihnen. In ein paar Stunden haben wir sie alle gelöscht und den Speicher entfernt.



Und sie haben im Code-Bereich einen Gewinn von ungefähr ein oder zwei Megabyte. Großartig.

Als nächstes wandten wir uns der Aufzählung zu. Wie Sie wissen, ist Enum eine Klasse. Und wie Google schließlich zugab, ist enum nicht sehr speichereffizient. Wir haben alle Aufzählungen in InDef und StringDef übersetzt. Hier können Sie mir widersprechen, dass ProgArt hier helfen wird. Tatsächlich wird ProgArt jedoch nicht alle Aufzählungen durch primitive Typen ersetzen. Es ist besser, es selbst zu tun. Übrigens hatten wir mehr als 90 Aufzählungen, ziemlich viel.



Diese Optimierung hat bereits Tage gedauert, da die meisten manuell durchgeführt werden mussten und wir im Java-Heap-Bereich etwa drei bis sechs Megabyte gewonnen haben.

Als nächstes machten wir auf die Sammlung aufmerksam. Wir haben ziemlich standardmäßige Java-Sammlungen wie HashMap verwendet. Wir hatten mehr als 150 davon und alle wurden zu Beginn von Launcher erstellt. Wir haben sie durch SparseArray, SimpleArrayMap und ArrayMap ersetzt und begonnen, Sammlungen mit einer vorgegebenen Größe zu erstellen, damit keine leeren Slots zugewiesen werden. Das heißt, wir übergeben die Größe der Sammlung an den Konstruktor.



Dies gab auch einen gewissen Gewinn, und diese Optimierung dauerte auch Tage, von denen wir die meisten manuell durchführten.

Dann haben wir einen genaueren Schritt gemacht. Wir haben gesehen, dass wir drei Prozesse haben. Wie wir wissen, benötigt selbst ein leerer Prozess in Android ungefähr 8-10 Megabyte Speicher, ziemlich viel.

Details zu den Prozessen wurden von meinem Kollegen Arthur Vasilov mitgeteilt. Vor nicht allzu langer Zeit war auf der Mosdroid-Konferenz sein Bericht , auch über Android Go.



Was hatten wir nach diesen Optimierungen? Auf dem Haupttestgerät beobachteten wir einen Speicherverbrauch im Bereich von 80 bis 100 Megabyte, nicht schlecht genug, aber immer noch nicht genug. Wir haben begonnen, den Speicher auf anderen Geräten zu messen. Wir haben festgestellt, dass auf schnelleren Geräten der Speicherverbrauch viel höher war. Es stellte sich heraus, dass wir viele verschiedene ausstehende Initialisierungen hatten. Nach einiger Zeit hat Launcher einige Ansichten aufgeblasen, einige Bibliotheken initiiert usw.



Was haben wir getan Zuerst gingen wir die Ansicht durch, alle Layouts. Alle Ansichten, die mit sichtbarer Sicht aufgeblasen wurden, wurden entfernt. Sie brachten sie in separate Layouts und begannen, sie programmgesteuert aufzublasen. Diejenigen, die wir nicht brauchten, hörten wir im Allgemeinen auf zu blasen, bis der Benutzer sie brauchte. Wir haben auf die Bildoptimierung geachtet. Wir haben das Laden von Bildern gestoppt, die der Benutzer derzeit nicht sieht. Im Fall von Launcher waren dies Bilder-Symbole von Anwendungen in der vollständigen Liste der Anwendungen. Bis zur Eröffnung versenden wir sie nicht. Dies gab uns einen sehr guten Gewinn in der Grafiksektion.

Wir haben auch unsere Bildcaches im Speicher überprüft. Es stellte sich heraus, dass nicht alle optimal waren und nicht alle Bilder, die dem Bildschirm des Telefons entsprachen, auf dem Launcher ausgeführt wurde, im Speicher gespeichert waren.

Danach begannen wir, den Codeabschnitt zu analysieren und stellten fest, dass wir von irgendwoher ziemlich viele ziemlich schwere Klassen hatten. Es stellte sich heraus, dass dies hauptsächlich Bibliotheksklassen sind. In einigen Bibliotheken haben wir einige seltsame Dinge gefunden. Eine der Bibliotheken hat HashMap erstellt und in einem statischen Initialisierer mit einer ausreichend großen Anzahl von Objekten verstopft.



Eine andere Bibliothek lud auch Audiodateien in einen statischen Block, der etwa 700 Kilobyte Speicherplatz belegte.



Wir haben die Initialisierung solcher Bibliotheken eingestellt und erst begonnen, mit ihnen zu arbeiten, wenn diese Funktionen von den Benutzern wirklich benötigt werden. Alle diese Optimierungen dauerten mehrere Wochen. Wir haben viel getestet und überprüft, dass wir keine zusätzlichen Probleme eingeführt haben. Aber wir haben auch einen ziemlich guten Gewinn erzielt, etwa 25 von 40 Megabyte in den Abschnitten Native, Heap, Code und Java Heap.

Das war aber nicht genug. Der Speicherverbrauch ist immer noch nicht auf 30 Megabyte gesunken. Es schien, als hätten wir alle Optionen für einige einfache automatische und sichere Optimierungen ausgeschöpft.

Wir haben uns entschlossen, radikale Lösungen in Betracht zu ziehen. Hier sahen wir zwei Optionen - die Erstellung einer separaten Lite-Anwendung oder die Verarbeitung der Launcher-Architektur und den Übergang zu einer modularen Architektur mit der Möglichkeit, Launcher-Assemblys ohne zusätzliche Module zu erstellen. Die erste Option ist ziemlich lang und teuer. Höchstwahrscheinlich führt die Erstellung einer solchen Anwendung zu einer vollständigen separaten Anwendung für Sie, die vollständig unterstützt und entwickelt werden muss. Andererseits ist die Option mit einer modularen Architektur auch ziemlich teuer, ziemlich riskant, aber dennoch schneller, da Sie bereits mit einer bekannten Codebasis arbeiten und bereits über eine Reihe von automatischen Komponententests, Integrationstests und manuellen Tests verfügen Fälle.

Es sollte beachtet werden, dass Sie unabhängig von der gewählten Option einen Teil der Funktionen Ihrer Anwendung in der Version für Android Go irgendwie aufgeben müssen. Es ist in Ordnung. Google macht dasselbe in seinen Go-Apps.

Nachdem wir eine modulare Architektur implementiert hatten, lösten wir unsere Speicherprobleme ziemlich zuverlässig und begannen, Tests auch auf Geräten mit einem kleinen Bildschirm zu bestehen, dh wir reduzierten den Speicherverbrauch auf 30 Megabyte.



Ein bisschen über die Speicherüberwachung, darüber, wie wir die Speichernutzung unter Kontrolle halten. Zunächst richten wir statische Analysegeräte ein, die in Fällen, in denen wir Enum verwenden, Synthesemethoden erstellen oder nicht optimierte Sammlungen verwenden, denselben Fehler enthalten.

Weiter schwieriger. Wir haben automatische Integrationstests eingerichtet, die Launcher auf Emulatoren ausführen und nach einer Weile den Speicherverbrauch verringern. Wenn es sich stark vom vorherigen Build unterscheidet, werden Warnungen und Warnungen ausgelöst. Dann untersuchen wir das Problem und veröffentlichen keine Änderungen, die die Verwendung des Launcher-Speichers erhöhen.

Zusammenfassend. Es gibt verschiedene Tools zur Überwachung des Speichers und zur Messung des Speichers für einen schnellen und effizienten Betrieb. Es ist besser, sie alle zu verwenden, da sie ihre Vor- und Nachteile haben.

Radikale Lösungen mit modularer Architektur erwiesen sich für uns als zuverlässiger und effizienter. Wir bedauern, dass wir sie nicht sofort genommen haben. Aber die Schritte, über die ich ganz am Anfang des Berichts gesprochen habe, waren nicht umsonst. Wir haben festgestellt, dass die Hauptversion der Anwendung den Speicher optimal nutzt, um schneller zu arbeiten. Vielen Dank.

Source: https://habr.com/ru/post/de448966/


All Articles