Technologieaustausch

Android-Leistungsoptimierung, Speicheroptimierung

2024-07-12

한어Русский языкEnglishFrançaisIndonesianSanskrit日本語DeutschPortuguêsΕλληνικάespañolItalianoSuomalainenLatina

Android-Leistungsoptimierung, Speicheroptimierung

Speicherproblem

  • Gedächtniszerstörung
  • Speicherleck
  • Speicherüberlauf

Gedächtniszerstörung

Unter Memory Thrashing versteht man die Erstellung und Zerstörung einer großen Anzahl von Objekten in kurzer Zeit, was zu häufigen Garbage Collection-Aktivitäten (Garbage Collection, GC) führt. Diese häufige GC-Aktivität beansprucht viele CPU-Ressourcen und kann zu Anwendungsverzögerungen oder Leistungseinbußen führen.

Leistung: Die Speicherkurve ist gezackt.

Speicherleck

Ein Speicherverlust tritt auf, wenn eine Anwendung Verweise auf Objekte enthält, die nicht mehr benötigt werden, was dazu führt, dass diese Objekte vom Garbage Collector nicht recycelt werden und somit Speicherplatz belegen, der hätte freigegeben werden können. Im Laufe der Zeit führen Speicherlecks dazu, dass immer weniger Speicher verfügbar ist, was schließlich zu Anwendungsabstürzen oder Leistungseinbußen führen kann.

Speicherüberlauf

Ein Speicherüberlauf tritt auf, wenn eine Anwendung versucht, mehr Speicherplatz zuzuweisen, das System die Anforderung jedoch nicht erfüllen kann, weil nicht mehr genügend Speicherplatz zum Zuweisen vorhanden ist. Dies führt normalerweise dazu, dass die Anwendung eine OutOfMemoryError-Ausnahme auslöst.

Erkennungstool

  • Speicherprofiler
  • Speicheranalysator
  • LeakCanary

Speicherprofiler

  • Memory Profiler ist das Speicheranalysetool, das mit Android Studio geliefert wird.
  • Echtzeitdiagramm, das die Programmspeichernutzung zeigt.
  • Identifizieren Sie Speicherlecks, Thrashing und mehr.
  • Bietet die Möglichkeit, Heap-Dumps zu erfassen, GC zu erzwingen und Speicherzuweisungen zu verfolgen.

Speicheranalysator

  • Leistungsstarkes Java-Heap-Analysetool zum Auffinden von Speicherlecks und Speichernutzung.
  • Erstellen Sie Gesamtberichte, analysieren Sie Probleme und mehr.

LeakCanary

  • Automatische Erkennung von Speicherlecks.
    • LeakCanary erkennt automatisch Lecks in diesen Objekten: Aktivität, Fragment, Ansicht, ViewModel, Service.
  • Offizielle Website: https://github.com/square/leakcanary

Speicherverwaltungsmechanismus

Java

Java-Speicherstruktur: Heap, Stapel der virtuellen Maschine, Methodenbereich, Programmzähler, lokaler Methodenstapel.

Java-Speicherrecycling-Algorithmus:

  • Mark-and-Sweep-Algorithmus:
    1. Markieren Sie Gegenstände, die recycelt werden müssen
    2. Recyceln Sie alle markierten Objekte gleichmäßig.
  • Kopieralgorithmus:
    1. Teilen Sie den Speicher in zwei gleich große Blöcke auf.
    2. Nachdem ein Speicherblock aufgebraucht ist, werden die verbleibenden Objekte in einen anderen Block kopiert.
  • Markierungs- und Sortieralgorithmus:
    1. Der Markierungsprozess ist der gleiche wie beim „Mark-and-Sweep“-Algorithmus.
    2. Überlebensobjekte bewegen sich an ein Ende.
    3. Löschen Sie den verbleibenden Speicher.
  • Generationssammlungsalgorithmus:
    • Kombinieren Sie die Vorteile mehrerer Erfassungsalgorithmen.
    • Die Überlebensrate von Objekten der neuen Generation ist gering und es wird ein Kopieralgorithmus verwendet.
    • Die Überlebensrate von Objekten der alten Generation ist hoch und es wird der Mark-Sort-Algorithmus verwendet.

Nachteile des Mark-Clear-Algorithmus: Markieren und Löschen sind nicht effizient und erzeugen eine große Anzahl diskontinuierlicher Speicherfragmente.

Replikationsalgorithmus: einfach zu implementieren und effizient auszuführen. Nachteile: Die Hälfte des Platzes wird verschwendet.

Mark-Compact-Algorithmus: Vermeiden Sie eine durch Mark-Clean verursachte Speicherfragmentierung und vermeiden Sie die Verschwendung von Speicherplatz beim Kopieralgorithmus.

Android

Die elastische Zuordnung des Android-Speichers, der Zuordnungswert und der Maximalwert werden von bestimmten Geräten beeinflusst.

Der Dalvik-Recycling-Algorithmus und der ART-Recycling-Algorithmus sind beide Garbage-Collection-Mechanismen, die für die Speicherverwaltung im Android-Betriebssystem verwendet werden.

Dalvik-Recycling-Algorithmus:

  • Mark-and-Sweep-Algorithmus.
  • Der Vorteil liegt in der einfachen Umsetzung. Der Nachteil besteht darin, dass die Ausführung der Anwendung während der Markierungs- und Löschphase angehalten wird, was dazu führt, dass die Anwendung vorübergehend einfriert und die Benutzererfahrung beeinträchtigt wird.

Kunstrecycling-Algorithmus:

  • Algorithmus zur Komprimierung der Garbage Collection. Basierend auf dem Mark-Sweep-Algorithmus wurden Verbesserungen vorgenommen, um die Pausenzeiten während der Speicherbereinigung zu reduzieren.
  • Gleichzeitige Markierung: ART führt eine gleichzeitige Markierungsphase ein, was bedeutet, dass sie gleichzeitig mit der Ausführung der Anwendung erfolgen kann. Dies reduziert die Pausenzeiten aufgrund der Speicherbereinigung.
  • Bereinigung und Komprimierung: Während der Bereinigungsphase löscht ART nicht nur nicht markierte Objekte, sondern komprimiert auch den Speicher, was bedeutet, dass überlebende Objekte zusammen verschoben werden, um die Speicherfragmentierung zu reduzieren. Dadurch wird die Speicherverwaltung effizienter und die Wahrscheinlichkeit von Fehlern bei der Speicherzuweisung verringert.
  • Adaptive Sammlung: ART führt außerdem das Konzept der adaptiven Sammlung ein, was bedeutet, dass die Häufigkeit und Art der Speicherbereinigung automatisch an das Verhalten und die Speichernutzungsmuster der Anwendung angepasst wird. Dadurch kann sich ART besser an unterschiedliche Anwendungsanforderungen anpassen.

LMK-Mechanismus:

  • Low Memory Killer-Mechanismus.
  • Seine Hauptfunktion besteht darin, einige Hintergrundprozesse gemäß einer bestimmten Prioritätsrichtlinie zu beenden, wenn der Systemspeicher nicht ausreicht, um Speicher freizugeben und die Stabilität und Reaktionsfähigkeit des Systems sicherzustellen.

lösen

Problem mit Speicherüberlastung

Simulationsproblemcode
public class ShakeActivity extends AppCompatActivity {

    private static Handler mHandler = new Handler() {
        @Override
        public void handleMessage(@NonNull Message msg) {
            super.handleMessage(msg);
            String str = "";
            for (int i = 0; i < 10000000; i++) {
                str += i;
            }
            mHandler.sendEmptyMessageDelayed(1, 30);
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_shake);
    }

    public void startClick(View view) {
        mHandler.sendEmptyMessage(1);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        mHandler.removeCallbacksAndMessages(null);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
Verwenden Sie zur Erkennung das Memory Profiler-Tool

Memory Profiler kann Speicherzuordnungen anzeigen. Klicken Sie auf „Java/Kotlin-Zuordnungen aufzeichnen“.

Fügen Sie hier eine Bildbeschreibung ein

Bedeutung des oben Gesagten:

  • Java: Durch Java- oder Kotlin-Code zugewiesener Speicher.
  • Nativ: Durch C- oder C++-Code zugewiesener Speicher.
  • Grafik: Der von der Grafikpufferwarteschlange verwendete Speicher, um Pixel auf dem Bildschirm anzuzeigen (einschließlich GL-Oberflächen, GL-Texturen usw.). Gemeinsam genutzter CPU-Speicher.
  • Stack: Der vom nativen Stack und Java-Stack in der Anwendung verwendete Speicher. Dies hängt normalerweise damit zusammen, wie viele Threads Ihre App ausführt.
  • Speicher, der von Anwendungen zum Verarbeiten von Code und Ressourcen wie Dex-Bytecode, optimiertem oder kompiliertem Dex-Code, .so-Bibliotheken und Schriftarten verwendet wird.
  • Die Anwendung verwendet Speicher, dessen Klassifizierung das System nicht sicher ist.
  • Die Anzahl der von der Anwendung zugewiesenen Java/Kotlin-Objekte. Diese Zahl berücksichtigt keine in C oder C++ zugewiesenen Objekte.

Folgende Bedeutungen:

  • Zuteilungen: Bestehen malloc() odernew Die Anzahl der vom Operator zugewiesenen Objekte.
  • Freigaben: via free() oderdelete Die Anzahl der vom Operator freigegebenen Objekte.
  • Zuteilungsgröße: Die Gesamtgröße aller Zuteilungen während des ausgewählten Zeitraums in Bytes.
  • Größe der Freigaben: Die Gesamtgröße des innerhalb des ausgewählten Zeitraums freigegebenen Speichers in Bytes.
  • Gesamtzahl: Zuweisungen minus freigegebene Zuweisungen.
  • Verbleibende Größe: Zuweisungsgröße minus Größe der aufgehobenen Zuweisungen.
  • Flache Größe: Die Gesamtgröße aller Instanzen im Heap in Bytes.

Analyse des obigen Bildes:

Fügen Sie hier eine Bildbeschreibung ein

Die Werte für Zuweisungen und Freigaben in diesem Bereich sind relativ ähnlich und die flache Größe ist relativ groß, was darauf hinweist, dass Objekte häufig erstellt und zerstört werden können.

Fügen Sie hier eine Bildbeschreibung ein

Nach dem Klicken können Sie die Aufrufstapelinformationen anzeigen und in Kombination mit dem Code daraus schließen, dass im Handler ein Speicherjitterproblem vorliegt.

Optimierungstipps
  • Vermeiden Sie es, häufig Objekte zu erstellen und zu zerstören.

Problem mit Speicherverlust

Simulationsproblemcode
public class CallbackManager {
    public static ArrayList<Callback> sCallbacks = new ArrayList<>();

    public static void addCallback(Callback callback) {
        sCallbacks.add(callback);
    }

    public static void removeCallback(Callback callback) {
        sCallbacks.remove(callback);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
public class LeakActivity extends AppCompatActivity implements Callback {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_leak);
        ImageView imageView = findViewById(R.id.imageView);
        Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.splash);
        imageView.setImageBitmap(bitmap);
        CallbackManager.addCallback(this);
    }

    @Override
    public void onOperate() {

    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
Verwenden Sie zur Erkennung das LeakCanary-Tool

Abhängige Bibliotheken hinzufügen:

debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.14'
  • 1

Nachdem ein Speicherverlust aufgetreten ist, generiert LeakCanary relevante Informationen und gibt sie automatisch aus:

Fügen Sie hier eine Bildbeschreibung ein

Aus der obigen Abbildung ist ersichtlich, dass bei LeakActivity ein Speicherverlust vorliegt und die Referenzkettenbeziehung angezeigt wird.

Natürlich können Sie auch eine hprof-Datei generieren und spezifische Informationen über das Profiler-Tool anzeigen:

Fügen Sie hier eine Bildbeschreibung ein

Wie aus dem obigen Bild hervorgeht, sind 10 Leckpunkte aufgetreten, einschließlich LeakActivity. Klicken Sie auf LeakActivity, um das Speicherleckobjekt anzuzeigen und die Referenzkette zu überprüfen.

Optimierungstipps
  • Rechtzeitiges Recycling der Sammelelemente.
  • Vermeiden Sie statische Verweise auf zu viele Instanzen.
  • Verwenden Sie statische innere Klassen.
  • Schließen Sie Ressourcenobjekte umgehend.

Bitmap-Optimierung

Wenn Sie die Bildressourcen nach der Verwendung von Bitmap nicht freigeben, kann dies leicht passierenSpeicherverlust, der zu einem Speicherüberlauf führt

Bitmap-Speichermodell
  • Vor API10 (Android 2.3.3): Bitmap-Objekte werden im Heap-Speicher abgelegt und Pixeldaten werden im lokalen Speicher abgelegt.
  • Nach API10: alles im Heap-Speicher.
  • Nach API26 (Android8.0): Pixeldaten werden im lokalen Speicher abgelegt. Dies ermöglicht eine schnelle Freigabe der Bitmap-Pixeldaten des nativen Layers zusammen mit den Objekten des Java-Layers.

Speicherrecycling:

  • Vor Android 3.0 müssen Sie es manuell aufrufenBitmap.recycle()Führen Sie ein Bitmap-Recycling durch.
  • Ab Android 3.0 bietet das System eine intelligentere Speicherverwaltung, und in den meisten Fällen ist keine manuelle Wiederverwendung von Bitmaps erforderlich.

Bitmap-Pixelkonfiguration:

KonfigurationBelegte Bytegröße (Byte)veranschaulichen
ALPHA_81einzelner transparenter Kanal
RGB_5652Einfache RGB-Tönung
ARGB_8888424-Bit-Echtfarbe
RGBA_F168Android 8.0 Neu (HDR)

Berechnen Sie den von Btimap belegten Speicher:

  • Bitmap#getByteCount()
  • getWidth() * getHeight() * 1 Pixel belegt Speicher
Ressourcendateiverzeichnis

Problem mit der Ressourcendatei:

  • mdpi (mittlere Dichte): ca. 160 dpi, 1x Ressource.
  • HDPI (hohe Dichte): ca. 240 dpi, 1,5-fache Ressourcen.
  • xhdpi (Extra High Density): Ungefähr 320 dpi, 2x Ressourcen.
  • xxhdpi (Extra Ultra High Density): Ungefähr 480 dpi, 3x Ressourcen.
  • xxxhdpi (Extra Ultra High Density): Ungefähr 640 dpi, 4x Ressourcen.

Testcode:

private void printBitmap(Bitmap bitmap, String drawable) {
    String builder = drawable +
            " Bitmap占用内存:" +
            bitmap.getByteCount() +
            " width:" +
            bitmap.getWidth() +
            " height:" +
            bitmap.getHeight() +
            " 1像素占用大小:" +
            getByteBy1px(bitmap.getConfig());
    Log.e("TAG", builder);
}

private int getByteBy1px(Bitmap.Config config) {
    if (Bitmap.Config.ALPHA_8.equals(config)) {
        return 1;
    } else if (Bitmap.Config.RGB_565.equals(config)) {
        return 2;
    } else if (Bitmap.Config.ARGB_8888.equals(config)) {
        return 4;
    }
    return 1;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
// 逻辑密度
float density = metrics.density;
// 物理密度
int densityDpi = metrics.densityDpi;
Log.e("TAG", density + "-" + densityDpi);

// 1倍图
Bitmap bitmap1 = BitmapFactory.decodeResource(getResources(), R.drawable.splash1);
printBitmap(bitmap1, "drawable-mdpi");

// 2倍图
Bitmap bitmap2 = BitmapFactory.decodeResource(getResources(), R.drawable.splash2);
printBitmap(bitmap2, "drawable-xhdpi");

// 3倍图
Bitmap bitmap3 = BitmapFactory.decodeResource(getResources(), R.drawable.splash3);
printBitmap(bitmap3, "drawable-xxhdpi");

// 4倍图
Bitmap bitmap4 = BitmapFactory.decodeResource(getResources(), R.drawable.splash4);
printBitmap(bitmap4, "drawable-xxxhdpi");

// drawable
Bitmap bitmap5 = BitmapFactory.decodeResource(getResources(), R.drawable.splash);
printBitmap(bitmap5, "drawable");
/*
        3.0-480
        drawable-mdpi Bitmap占用内存:37127376 width:2574 height:3606 1像素占用大小:4
        drawable-xhdpi Bitmap占用内存:9281844 width:1287 height:1803 1像素占用大小:4
        drawable-xxhdpi Bitmap占用内存:4125264 width:858 height:1202 1像素占用大小:4
        drawable-xxxhdpi Bitmap占用内存:2323552 width:644 height:902 1像素占用大小:4
        drawable Bitmap占用内存:37127376 width:2574 height:3606 1像素占用大小:4
         */
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33

veranschaulichen:

1dp auf MDPI-Geräten1px, 1dp auf xhdpi-Geräten2px, 1dp==3px auf xxhdpi-Gerät.

Daher ist das aktuelle Gerät xxhdpi, sodass die Breite desselben Bildes unter der xxhdpi-Ressource 858 beträgt, unter der mdpi-Ressource wird es um das Dreifache vergrößert und die Breite beträgt 2574, und unter der xhdpi-Ressource wird es um das 1,5-fache vergrößert Die Breite beträgt 1287.

Optimierungstipps
  • Richten Sie mehrere Sätze von Bildressourcen ein.
  • Wählen Sie die entsprechende Dekodierungsmethode.
  • Bildcache einrichten.

Download des Quellcodes