Condivisione della tecnologia

Ottimizzazione delle prestazioni Android, ottimizzazione della memoria

2024-07-12

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

Ottimizzazione delle prestazioni Android, ottimizzazione della memoria

problema di memoria

  • distruzione della memoria
  • perdita di memoria
  • eccesso di memoria

distruzione della memoria

Il memory thrashing si riferisce alla creazione e distruzione di un gran numero di oggetti in un breve periodo di tempo, con conseguenti frequenti attività di garbage collection (Garbage Collection, GC). Questa frequente attività del GC occupa molte risorse della CPU e può causare ritardi nell'applicazione o un degrado delle prestazioni.

Prestazioni: la curva della memoria è frastagliata.

perdita di memoria

Una perdita di memoria si verifica quando un'applicazione conserva riferimenti a oggetti che non sono più necessari, impedendo a questi oggetti di essere riciclati dal Garbage Collector, occupando così spazio di memoria che avrebbe potuto essere rilasciato. Con il passare del tempo, le perdite di memoria determinano una quantità sempre minore di memoria disponibile, il che può eventualmente portare a arresti anomali dell'applicazione o al degrado delle prestazioni.

eccesso di memoria

L'overflow della memoria si verifica quando un'applicazione tenta di allocare più spazio di memoria, ma il sistema non è in grado di soddisfare la richiesta perché non c'è più spazio di memoria sufficiente da allocare. Questo di solito fa sì che l'applicazione lanci un'eccezione OutOfMemoryError.

Strumento di rilevamento

  • Profilo della memoria
  • Analizzatore di memoria
  • PerditaCanary

Profilo della memoria

  • Memory Profiler è lo strumento di analisi della memoria fornito con Android Studio.
  • Grafico in tempo reale che mostra l'utilizzo della memoria del programma.
  • Identifica perdite di memoria, thrashing e altro ancora.
  • Fornisce la possibilità di acquisire dump dell'heap, forzare GC e tenere traccia delle allocazioni di memoria.

Analizzatore di memoria

  • Potente strumento di analisi Java Heap per individuare perdite di memoria e utilizzo della memoria.
  • Genera report generali, analizza i problemi e altro ancora.

PerditaCanary

  • Rilevamento automatico delle perdite di memoria.
    • LeakCanary rileva automaticamente le perdite in questi oggetti: Activity, Fragment, View, ViewModel, Service.
  • Sito ufficiale: https://github.com/square/leakcanary

Meccanismo di gestione della memoria

Giava

Struttura della memoria Java: heap, stack della macchina virtuale, area dei metodi, contatore dei programmi, stack dei metodi locali.

Algoritmo di riciclo della memoria Java:

  • Algoritmo Mark-and-Sweep:
    1. Contrassegna gli oggetti che devono essere riciclati
    2. Riciclare uniformemente tutti gli oggetti contrassegnati.
  • Algoritmo di copia:
    1. Dividere la memoria in due blocchi di uguali dimensioni.
    2. Una volta esaurito un blocco di memoria, gli oggetti sopravvissuti vengono copiati in un altro blocco.
  • Algoritmo di comparazione dei contrassegni:
    1. Il processo di marcatura è lo stesso dell'algoritmo "mark-and-sweep".
    2. Gli oggetti di sopravvivenza si spostano verso un'estremità.
    3. Cancella la memoria rimanente.
  • Algoritmo di raccolta generazionale:
    • Combina i vantaggi di più algoritmi di raccolta.
    • Il tasso di sopravvivenza degli oggetti di nuova generazione è basso e viene utilizzato un algoritmo di copia.
    • Il tasso di sopravvivenza degli oggetti della vecchia generazione è elevato e viene utilizzato l'algoritmo mark-sort.

Svantaggi dell'algoritmo mark-clear: la marcatura e la cancellazione non sono efficienti e produrranno un gran numero di frammenti di memoria discontinui.

Algoritmo di replica: semplice da implementare ed efficiente da eseguire. Svantaggi: metà dello spazio sprecato.

Algoritmo Mark-compact: evita la frammentazione della memoria causata da Mark-Clean ed evita lo spreco di spazio dell'algoritmo di copia.

Android

L'allocazione elastica della memoria Android, il valore di allocazione e il valore massimo sono influenzati da dispositivi specifici.

L'algoritmo di riciclaggio Dalvik e l'algoritmo di riciclaggio ART sono entrambi meccanismi di garbage collection utilizzati per la gestione della memoria nel sistema operativo Android.

Algoritmo di riciclaggio di Dalvik:

  • Algoritmo Mark-and-Sweep.
  • Il vantaggio è che è semplice da implementare. Lo svantaggio è che l'esecuzione dell'applicazione verrà sospesa durante le fasi di marcatura e cancellazione, causando il blocco temporaneo dell'applicazione e influenzando l'esperienza dell'utente.

Algoritmo di riciclaggio dell'arte:

  • Algoritmo di compattazione della raccolta dei rifiuti. Sono stati apportati miglioramenti in base all'algoritmo mark-sweep per ridurre i tempi di pausa durante la garbage collection.
  • Marcatura Concorrente: ART introduce una fase di marcatura concorrente, ovvero può avvenire in concomitanza con l'esecuzione dell'applicazione. Ciò riduce i tempi di pausa dovuti alla garbage collection.
  • Pulizia e compattazione: durante la fase di pulizia, ART non solo cancella gli oggetti non contrassegnati, ma compatta anche la memoria, il che significa che sposta insieme gli oggetti sopravvissuti per ridurre la frammentazione della memoria. Ciò rende la gestione della memoria più efficiente e riduce la possibilità di errori di allocazione della memoria.
  • Raccolta adattiva: ART introduce anche il concetto di raccolta adattiva, il che significa che regola automaticamente la frequenza e la modalità della garbage collection in base al comportamento dell'applicazione e ai modelli di utilizzo della memoria. Ciò consente ad ART di adattarsi meglio alle diverse esigenze applicative.

Meccanismo LMK:

  • Meccanismo Low Memory Killer.
  • La sua funzione principale è terminare alcuni processi in background secondo una determinata politica di priorità quando la memoria del sistema non è sufficiente per liberare memoria e garantire stabilità e reattività del sistema.

risolvere

Problema di esaurimento della memoria

Codice del problema di simulazione
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
Utilizzare lo strumento Memory Profiler per rilevare

Memory Profiler può visualizzare le allocazioni di memoria, fare clic su "Registra allocazioni Java/Kotlin".

Inserisci qui la descrizione dell'immagine

Significato di quanto sopra:

  • Java: memoria allocata dal codice Java o Kotlin.
  • Nativo: memoria allocata dal codice C o C++.
  • Grafica: la memoria utilizzata dalla coda del buffer grafico per visualizzare i pixel sullo schermo (incluse superfici GL, trame GL, ecc.). Memoria condivisa della CPU.
  • Stack: la memoria utilizzata dallo stack nativo e dallo stack Java nell'applicazione. Questo di solito ha a che fare con il numero di thread in esecuzione sulla tua app.
  • Memoria utilizzata dalle applicazioni per elaborare codice e risorse come bytecode dex, codice dex ottimizzato o compilato, librerie .so e caratteri.
  • L'applicazione utilizza memoria che il sistema non è sicuro di come classificare.
  • Il numero di oggetti Java/Kotlin allocati dall'applicazione. Questo numero non tiene conto degli oggetti allocati in C o C++.

I seguenti significati:

  • Assegnazioni: Superato malloc() Onew Il numero di oggetti assegnati dall'operatore.
  • Deallocazioni: via free() Odelete Il numero di oggetti deallocati dall'operatore.
  • Dimensioni allocazioni: la dimensione totale di tutte le allocazioni durante il periodo di tempo selezionato, in byte.
  • Dimensioni deallocazioni: la dimensione totale della memoria rilasciata nel periodo di tempo selezionato, in byte.
  • Conteggio totale: allocazioni meno deallocazioni.
  • Dimensione rimanente: dimensione delle allocazioni meno dimensione delle deallocazioni.
  • Dimensione minima: la dimensione totale di tutte le istanze nell'heap, in byte.

Analisi dell'immagine sopra:

Inserisci qui la descrizione dell'immagine

I valori di allocazioni e deallocazioni in quest'area sono relativamente simili e la dimensione superficiale è relativamente grande, indicando che gli oggetti possono essere creati e distrutti frequentemente.

Inserisci qui la descrizione dell'immagine

Dopo aver fatto clic, è possibile visualizzare le informazioni sullo stack di chiamate e, combinate con il codice, è possibile dedurre che esiste un problema di jitter della memoria nel gestore.

Suggerimenti per l'ottimizzazione
  • Evitare di creare e distruggere frequentemente oggetti.

Problema di perdita di memoria

Codice del problema di simulazione
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
Utilizza lo strumento LeakCanary per rilevare

Aggiungi librerie dipendenti:

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

Dopo che si verifica una perdita di memoria, LeakCanary genererà informazioni rilevanti e le scaricherà automaticamente:

Inserisci qui la descrizione dell'immagine

Come si può vedere dalla figura sopra: LeakActivity ha una perdita di memoria e viene visualizzata la relazione della catena di riferimenti.

Naturalmente, puoi anche generare un file hprof e visualizzare informazioni specifiche tramite lo strumento Profiler:

Inserisci qui la descrizione dell'immagine

Come si può vedere dall'immagine sopra: si sono verificati 10 punti di perdita, incluso LeakActivity. Fare clic su LeakActivity per visualizzare l'oggetto di perdita di memoria e controllare la catena di riferimento. Si può vedere che è mantenuto da ArrayList.

Suggerimenti per l'ottimizzazione
  • Riciclaggio tempestivo degli elementi della raccolta.
  • Evitare riferimenti statici a troppe istanze.
  • Utilizzare classi interne statiche.
  • Chiudere tempestivamente gli oggetti risorsa.

Ottimizzazione bitmap

Se non rilasci le risorse dell'immagine dopo aver utilizzato Bitmap, è facile causarloPerdita di memoria, con conseguente overflow della memoria

Modello di memoria bitmap
  • Prima di api10 (Android 2.3.3): gli oggetti bitmap vengono inseriti nella memoria heap e i dati pixel vengono inseriti nella memoria locale.
  • Dopo api10: tutto nella memoria heap.
  • Dopo api26 (Android8.0): i dati dei pixel vengono inseriti nella memoria locale. Ciò consente di rilasciare rapidamente i dati dei pixel bitmap del livello nativo insieme agli oggetti del livello Java.

Riciclo della memoria:

  • Prima di Android 3.0, devi chiamarlo manualmenteBitmap.recycle()Esegui il riciclo delle bitmap.
  • A partire da Android 3.0, il sistema fornisce una gestione della memoria più intelligente e nella maggior parte dei casi non è necessario riciclare manualmente Bitmap.

Configurazione pixel bitmap:

ConfigurazioneDimensione byte occupato (byte)illustrare
ALPHA_81unico canale trasparente
RGB_5652Tinta RGB semplice
ARGB_88884Colore reale a 24 bit
RGBA_F168Android 8.0 Nuovo (HDR)

Calcola la memoria occupata da Btimap:

  • Bitmap#getByteCount()
  • getWidth() * getHeight() * 1 pixel occupa memoria
Directory dei file di risorse

Problema relativo al file di risorse:

  • mdpi (densità media): circa 160 dpi, 1x risorsa.
  • hdpi (alta densità): circa 240 dpi, risorse 1,5x.
  • xhdpi (densità extra alta): circa 320 dpi, 2x risorse.
  • xxhdpi (Extra Ultra High Density): circa 480 dpi, 3x risorse.
  • xxxhdpi (Extra Ultra High Density): circa 640 dpi, 4x risorse.

Codice di prova:

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

illustrare:

1dp sui dispositivi MDPI1px, 1dp su dispositivi xhdpi2px, 1dp==3px sul dispositivo xxhdpi.

Pertanto, il dispositivo corrente è xxhdpi, quindi la larghezza della stessa immagine sotto la risorsa xxhdpi è 858, sotto la risorsa mdpi verrà ingrandita 3 volte e la larghezza è 2574, e sotto la risorsa xhdpi verrà ingrandita 1,5 volte e la larghezza è 1287.

Suggerimenti per l'ottimizzazione
  • Configura più set di risorse immagine.
  • Scegli il metodo di decodifica appropriato.
  • Configura la cache delle immagini.

Scaricamento del codice sorgente