Partage de technologie

Optimisation des performances Android Optimisation de la mémoire

2024-07-12

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

Optimisation des performances Android Optimisation de la mémoire

problème de mémoire

  • battage de mémoire
  • fuite de mémoire
  • débordement de mémoire

battage de mémoire

Le thrashing de la mémoire fait référence à la création et à la destruction d'un grand nombre d'objets sur une courte période de temps, entraînant des activités fréquentes de garbage collection (Garbage Collection, GC). Cette activité fréquente du GC consomme beaucoup de ressources CPU et peut entraîner des retards d'application ou une dégradation des performances.

Performances : la courbe de mémoire est irrégulière.

fuite de mémoire

Une fuite de mémoire se produit lorsqu'une application contient des références à des objets qui ne sont plus nécessaires, ce qui empêche le recyclage de ces objets par le ramasse-miettes, occupant ainsi de l'espace mémoire qui aurait pu être libéré. Au fil du temps, les fuites de mémoire entraînent de moins en moins de mémoire disponible, ce qui peut éventuellement entraîner des pannes d'applications ou une dégradation des performances.

débordement de mémoire

Un dépassement de mémoire se produit lorsqu'une application tente d'allouer plus d'espace mémoire, mais que le système ne parvient pas à satisfaire la demande car il n'y a plus assez d'espace mémoire à allouer. Cela amène généralement l’application à lever une exception OutOfMemoryError.

Outil de détection

  • Profileur de mémoire
  • Analyseur de mémoire
  • FuiteCanary

Profileur de mémoire

  • Memory Profiler est l'outil d'analyse de la mémoire fourni avec Android Studio.
  • Graphique en temps réel montrant l'utilisation de la mémoire du programme.
  • Identifiez les fuites de mémoire, les problèmes et bien plus encore.
  • Offre la possibilité de capturer des vidages de tas, de forcer le GC et de suivre les allocations de mémoire.

Analyseur de mémoire

  • Puissant outil d’analyse Java Heap pour détecter les fuites de mémoire et l’utilisation de la mémoire.
  • Générez des rapports globaux, analysez les problèmes, et bien plus encore.

FuiteCanary

  • Détection automatique des fuites de mémoire.
    • LeakCanary détecte automatiquement les fuites dans ces objets : Activité, Fragment, Vue, ViewModel, Service.
  • Site officiel : https://github.com/square/leakcanary

Mécanisme de gestion de la mémoire

Java

Structure de la mémoire Java : tas, pile de machines virtuelles, zone de méthodes, compteur de programme, pile de méthodes locale.

Algorithme de recyclage de mémoire Java :

  • Algorithme de marquage et de balayage :
    1. Marquer les objets qui doivent être recyclés
    2. Recyclez uniformément tous les objets marqués.
  • Algorithme de copie :
    1. Divisez la mémoire en deux morceaux de taille égale.
    2. Une fois qu'un bloc de mémoire est épuisé, les objets survivants sont copiés dans un autre bloc.
  • Algorithme de marquage-collage :
    1. Le processus de marquage est le même que l'algorithme « mark-and-sweep ».
    2. Les objets de survie se déplacent vers une extrémité.
    3. Effacez la mémoire restante.
  • Algorithme de collecte générationnelle :
    • Combinez les avantages de plusieurs algorithmes de collecte.
    • Le taux de survie des objets de nouvelle génération est faible et un algorithme de copie est utilisé.
    • Le taux de survie des objets de l'ancienne génération est élevé et l'algorithme de tri par marquage est utilisé.

Inconvénients de l'algorithme de marquage-effacement : Le marquage et l'effacement ne sont pas efficaces et produiront un grand nombre de fragments de mémoire discontinus.

Algorithme de réplication : simple à mettre en œuvre et efficace à exécuter. Inconvénients : Gaspillage de la moitié de l’espace.

Algorithme Mark-compact : évitez la fragmentation de la mémoire causée par le nettoyage et évitez de perdre de l'espace avec l'algorithme de copie.

Android

L'allocation élastique de la mémoire Android, la valeur d'allocation et la valeur maximale sont affectées par des appareils spécifiques.

L'algorithme de recyclage Dalvik et l'algorithme de recyclage ART sont tous deux des mécanismes de récupération de place utilisés pour la gestion de la mémoire dans le système d'exploitation Android.

Algorithme de recyclage Dalvik :

  • Algorithme de marquage et de balayage.
  • L’avantage est qu’il est simple à mettre en œuvre. L'inconvénient est que l'exécution de l'application sera suspendue pendant les phases de marquage et d'effacement, ce qui entraînera un gel temporaire de l'application et affectera l'expérience utilisateur.

Algorithme de recyclage d’art :

  • Algorithme de compactage de la collecte des déchets. Des améliorations ont été apportées sur la base de l'algorithme de balayage de marquage pour réduire les temps de pause lors du garbage collection.
  • Marquage simultané : ART introduit une phase de marquage simultané, ce qui signifie qu'elle peut avoir lieu simultanément à l'exécution de l'application. Cela réduit les temps de pause dus au garbage collection.
  • Nettoyage et compactage : pendant la phase de nettoyage, ART efface non seulement les objets non marqués, mais compacte également la mémoire, ce qui signifie qu'il rassemble les objets survivants pour réduire la fragmentation de la mémoire. Cela rend la gestion de la mémoire plus efficace et réduit les risques d'échec d'allocation de mémoire.
  • Collecte adaptative : ART introduit également le concept de collecte adaptative, ce qui signifie qu'il ajuste automatiquement la fréquence et le mode de récupération de place en fonction du comportement de l'application et des modèles d'utilisation de la mémoire. Cela permet à ART de mieux s’adapter aux différentes exigences des applications.

Mécanisme LMK :

  • Mécanisme Low Memory Killer.
  • Sa fonction principale est de mettre fin à certains processus en arrière-plan selon une certaine politique de priorité lorsque la mémoire système est insuffisante pour libérer de la mémoire et assurer la stabilité et la réactivité du système.

résoudre

Problème de destruction de mémoire

Code du problème de simulation
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
Utilisez l'outil Memory Profiler pour détecter

Memory Profiler peut afficher les allocations de mémoire, cliquez sur « Enregistrer les allocations Java/Kotlin ».

Insérer la description de l'image ici

Signification de ce qui précède :

  • Java : Mémoire allouée par le code Java ou Kotlin.
  • Natif : Mémoire allouée par du code C ou C++.
  • Graphiques : la mémoire utilisée par la file d'attente du tampon graphique pour afficher les pixels à l'écran (y compris les surfaces GL, les textures GL, etc.). Mémoire partagée du processeur.
  • Pile : mémoire utilisée par la pile native et la pile Java dans l'application. Cela est généralement lié au nombre de threads exécutés par votre application.
  • Mémoire utilisée par les applications pour traiter le code et les ressources telles que le bytecode dex, le code dex optimisé ou compilé, les bibliothèques .so et les polices.
  • L'application utilise de la mémoire que le système ne sait pas comment classer.
  • Le nombre d'objets Java/Kotlin alloués par l'application. Ce nombre ne prend pas en compte les objets alloués en C ou C++.

Les significations suivantes :

  • Allocations : Pass malloc() ounew Le nombre d'objets alloués par l'opérateur.
  • Désallocations : via free() oudelete Le nombre d'objets désalloués par l'opérateur.
  • Taille des allocations : taille totale de toutes les allocations pendant la période sélectionnée, en octets.
  • Taille des désallocations : taille totale de la mémoire libérée au cours de la période sélectionnée, en octets.
  • Nombre total : allocations moins désallocations.
  • Taille restante : taille des allocations moins taille des désallocations.
  • Taille peu profonde : taille totale de toutes les instances du tas, en octets.

Analyse de l'image ci-dessus :

Insérer la description de l'image ici

Les valeurs des allocations et des désallocations dans cette zone sont relativement similaires et la taille peu profonde est relativement grande, ce qui indique que les objets peuvent être fréquemment créés et détruits.

Insérer la description de l'image ici

Après avoir cliqué, vous pouvez afficher les informations de la pile d'appels et, en combinaison avec le code, vous pouvez en déduire qu'il existe un problème de gigue de mémoire dans le gestionnaire.

Conseils d'optimisation
  • Évitez de créer et de détruire des objets fréquemment.

Problème de fuite de mémoire

Code du problème de simulation
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
Utilisez l'outil LeakCanary pour détecter

Ajoutez des bibliothèques dépendantes :

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

Après une fuite de mémoire, LeakCanary générera des informations pertinentes et les videra automatiquement :

Insérer la description de l'image ici

Comme le montre la figure ci-dessus : LeakActivity a une fuite de mémoire et la relation de chaîne de référence est affichée.

Bien entendu, vous pouvez également générer un fichier hprof et afficher des informations spécifiques via l'outil Profiler :

Insérer la description de l'image ici

Comme le montre l'image ci-dessus : 10 points de fuite se sont produits, y compris LeakActivity. Cliquez sur LeakActivity pour afficher l'objet ayant subi une fuite de mémoire et vérifier la chaîne de référence. On peut voir qu'il est détenu par ArrayList.

Conseils d'optimisation
  • Recyclage en temps opportun des éléments de collecte.
  • Évitez les références statiques à trop d’instances.
  • Utilisez des classes internes statiques.
  • Fermez rapidement les objets ressources.

Optimisation des bitmaps

Si vous ne libérez pas les ressources image après avoir utilisé Bitmap, il est facile de provoquerFuite de mémoire, entraînant un débordement de mémoire

Modèle de mémoire bitmap
  • Avant api10 (Android 2.3.3) : les objets bitmap sont placés dans la mémoire tas et les données de pixels sont placées dans la mémoire locale.
  • Après api10 : tout en mémoire tas.
  • Après api26 (Android8.0) : les données de pixels sont placées dans la mémoire locale. Cela permet aux données de pixels Bitmap de la couche native d'être rapidement publiées avec les objets de la couche Java.

Recyclage de la mémoire :

  • Avant Android 3.0, vous devez l'appeler manuellementBitmap.recycle()Effectuez le recyclage Bitmap.
  • À partir d'Android 3.0, le système offre une gestion de la mémoire plus intelligente et, dans la plupart des cas, il n'est pas nécessaire de recycler manuellement Bitmap.

Configuration des pixels bitmap :

ConfigurationTaille de l'octet occupé (octet)illustrer
ALPHA_81canal transparent unique
RVB_5652Teinte RVB simple
ARGB_88884Couleurs vraies 24 bits
RGBA_F168Android 8.0 Nouveau (HDR)

Calculez la mémoire occupée par Btimap :

  • Bitmap#getByteCount()
  • getWidth() * getHeight() * 1 pixel occupe de la mémoire
Répertoire des fichiers de ressources

Problème de fichier de ressources :

  • mdpi (densité moyenne) : environ 160 dpi, 1x ressource.
  • hdpi (haute densité) : environ 240 dpi, 1,5x ressources.
  • xhdpi (extra haute densité) : environ 320 dpi, 2x ressources.
  • xxhdpi (Extra Ultra High Density) : environ 480 dpi, 3x ressources.
  • xxxhdpi (Extra Ultra Haute Densité) : Environ 640 dpi, 4x ressources.

Code d'essai :

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

illustrer:

1dp sur les appareils mdpi1px, 1dp sur les appareils xhdpi2px, 1dp==3px sur un appareil xxhdpi.

Par conséquent, le périphérique actuel est xxhdpi, donc la largeur de la même image sous la ressource xxhdpi est de 858, sous la ressource mdpi, elle sera agrandie 3 fois et la largeur est de 2574, et sous la ressource xhdpi, elle sera agrandie 1,5 fois et la largeur est de 1287.

Conseils d'optimisation
  • Configurez plusieurs ensembles de ressources d’images.
  • Choisissez la méthode de décodage appropriée.
  • Configurez le cache d’images.

Téléchargement du code source