Κοινή χρήση τεχνολογίας

Βελτιστοποίηση απόδοσης Android Βελτιστοποίηση μνήμης

2024-07-12

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

Βελτιστοποίηση απόδοσης Android Βελτιστοποίηση μνήμης

πρόβλημα μνήμης

  • θρυμματισμός μνήμης
  • έλλειψη μνήμης
  • υπερχείλιση μνήμης

θρυμματισμός μνήμης

Το Memory thrashing αναφέρεται στη δημιουργία και καταστροφή μεγάλου αριθμού αντικειμένων σε σύντομο χρονικό διάστημα, με αποτέλεσμα συχνές δραστηριότητες συλλογής σκουπιδιών (Garbage Collection, GC). Αυτή η συχνή δραστηριότητα GC καταλαμβάνει πολλούς πόρους της CPU και μπορεί να προκαλέσει καθυστερήσεις στην εφαρμογή ή υποβάθμιση της απόδοσης.

Απόδοση: Η καμπύλη μνήμης είναι οδοντωτή.

έλλειψη μνήμης

Μια διαρροή μνήμης εμφανίζεται όταν μια εφαρμογή κρατά αναφορές σε αντικείμενα που δεν χρειάζονται πλέον, με αποτέλεσμα να αποτύχει η ανάκτηση αυτών των αντικειμένων από τον συλλέκτη σκουπιδιών, καταλαμβάνοντας έτσι χώρο στη μνήμη που θα μπορούσε να είχε ελευθερωθεί. Με την πάροδο του χρόνου, οι διαρροές μνήμης έχουν ως αποτέλεσμα ολοένα και λιγότερη διαθέσιμη μνήμη, η οποία μπορεί τελικά να οδηγήσει σε σφάλματα εφαρμογής ή υποβάθμιση της απόδοσης.

υπερχείλιση μνήμης

Η υπερχείλιση μνήμης συμβαίνει όταν μια εφαρμογή επιχειρεί να εκχωρήσει περισσότερο χώρο στη μνήμη, αλλά το σύστημα δεν μπορεί να ικανοποιήσει το αίτημα επειδή δεν υπάρχει πλέον αρκετός χώρος μνήμης για εκχώρηση. Αυτό συνήθως προκαλεί την εφαρμογή να δημιουργήσει μια εξαίρεση OutOfMemoryError.

Εργαλείο ανίχνευσης

  • Προφίλ μνήμης
  • Αναλυτής μνήμης
  • LeakCanary

Προφίλ μνήμης

  • Το Memory Profiler είναι το εργαλείο ανάλυσης μνήμης που συνοδεύει το Android Studio.
  • Γράφημα σε πραγματικό χρόνο που δείχνει τη χρήση της μνήμης του προγράμματος.
  • Προσδιορίστε τις διαρροές μνήμης, το thrashing και άλλα.
  • Παρέχει τη δυνατότητα καταγραφής χωματερών, εξαναγκασμού GC και παρακολούθησης εκχωρήσεων μνήμης.

Αναλυτής μνήμης

  • Ισχυρό εργαλείο ανάλυσης Java Heap για εύρεση διαρροών μνήμης και χρήσης μνήμης.
  • Δημιουργήστε συνολικές αναφορές, αναλύστε ζητήματα και πολλά άλλα.

LeakCanary

  • Αυτόματος εντοπισμός διαρροής μνήμης.
    • Το LeakCanary εντοπίζει αυτόματα διαρροές σε αυτά τα αντικείμενα: Δραστηριότητα, Τμήμα, Προβολή, ViewModel, Υπηρεσία.
  • Επίσημος ιστότοπος: https://github.com/square/leakcanary

Μηχανισμός διαχείρισης μνήμης

Ιάβα

Δομή μνήμης Java: σωρό, στοίβα εικονικής μηχανής, περιοχή μεθόδων, μετρητής προγράμματος, στοίβα τοπικής μεθόδου.

Αλγόριθμος ανακύκλωσης μνήμης Java:

  • Αλγόριθμος Mark-and-sweep:
    1. Σημειώστε αντικείμενα που πρέπει να ανακυκλωθούν
    2. Ανακυκλώστε όλα τα επισημασμένα αντικείμενα ομοιόμορφα.
  • Αλγόριθμος αντιγραφής:
    1. Χωρίστε τη μνήμη σε δύο κομμάτια ίσου μεγέθους.
    2. Αφού εξαντληθεί ένα μπλοκ μνήμης, τα σωζόμενα αντικείμενα αντιγράφονται σε άλλο μπλοκ.
  • Αλγόριθμος σήμανσης-συλλογής:
    1. Η διαδικασία σήμανσης είναι η ίδια με τον αλγόριθμο "mark-and-sweep".
    2. Τα αντικείμενα επιβίωσης κινούνται προς το ένα άκρο.
    3. Διαγράψτε την υπολειπόμενη μνήμη.
  • Αλγόριθμος συλλογής γενεών:
    • Συνδυάστε τα πλεονεκτήματα των πολλαπλών αλγορίθμων συλλογής.
    • Το ποσοστό επιβίωσης των αντικειμένων νέας γενιάς είναι χαμηλό και χρησιμοποιείται αλγόριθμος αντιγραφής.
    • Το ποσοστό επιβίωσης των αντικειμένων στην παλιά γενιά είναι υψηλό και χρησιμοποιείται ο αλγόριθμος ταξινόμησης σήμανσης.

Μειονεκτήματα του αλγορίθμου σαφής ένδειξης: Η σήμανση και η διαγραφή δεν είναι αποτελεσματικές και θα δημιουργήσουν μεγάλο αριθμό ασυνεχών θραυσμάτων μνήμης.

Αλγόριθμος αναπαραγωγής: απλός στην εφαρμογή και αποτελεσματικός στην εκτέλεση. Μειονεκτήματα: Χάθηκε ο μισός χώρος.

Αλγόριθμος Mark-compact: αποφύγετε τον κατακερματισμό της μνήμης που προκαλείται από το σημάδι καθαρισμού και αποφύγετε τη σπατάλη χώρου στον αλγόριθμο αντιγραφής.

Android

Η ελαστική κατανομή μνήμης Android, η τιμή εκχώρησης και η μέγιστη τιμή επηρεάζονται από συγκεκριμένες συσκευές.

Ο αλγόριθμος ανακύκλωσης Dalvik και ο αλγόριθμος ανακύκλωσης ART είναι μηχανισμοί συλλογής σκουπιδιών που χρησιμοποιούνται για τη διαχείριση μνήμης στο λειτουργικό σύστημα Android.

Αλγόριθμος ανακύκλωσης Dalvik:

  • Αλγόριθμος Mark-and-sweep.
  • Το πλεονέκτημα είναι ότι είναι απλό στην εφαρμογή του. Το μειονέκτημα είναι ότι η εκτέλεση της εφαρμογής θα τεθεί σε παύση κατά τη διάρκεια των φάσεων σήμανσης και εκκαθάρισης, γεγονός που θα προκαλέσει προσωρινό πάγωμα της εφαρμογής και θα επηρεάσει την εμπειρία χρήστη.

Αλγόριθμος ανακύκλωσης τέχνης:

  • Αλγόριθμος Συμπίεσης Συλλογής Σκουπιδιών. Έχουν γίνει βελτιώσεις με βάση τον αλγόριθμο σάρωσης για τη μείωση του χρόνου παύσης κατά τη συλλογή σκουπιδιών.
  • Ταυτόχρονη σήμανση: Το ART εισάγει μια φάση ταυτόχρονης σήμανσης, που σημαίνει ότι μπορεί να συμβεί ταυτόχρονα με την εκτέλεση της εφαρμογής. Αυτό μειώνει τους χρόνους παύσης λόγω συλλογής σκουπιδιών.
  • Καθαρισμός και συμπύκνωση: Κατά τη φάση καθαρισμού, το ART όχι μόνο καθαρίζει αντικείμενα που δεν έχουν επισημανθεί, αλλά συμπυκνώνει επίσης τη μνήμη, πράγμα που σημαίνει ότι μετακινεί τα επιζώντα αντικείμενα μαζί για να μειώσει τον κατακερματισμό της μνήμης. Αυτό κάνει τη διαχείριση της μνήμης πιο αποτελεσματική και μειώνει την πιθανότητα αποτυχιών στην εκχώρηση μνήμης.
  • Προσαρμοστική συλλογή: Το ART εισάγει επίσης την έννοια της προσαρμοστικής συλλογής, που σημαίνει ότι προσαρμόζει αυτόματα τη συχνότητα και τον τρόπο συλλογής σκουπιδιών με βάση τη συμπεριφορά της εφαρμογής και τα μοτίβα χρήσης μνήμης. Αυτό επιτρέπει στο ART να προσαρμόζεται καλύτερα σε διαφορετικές απαιτήσεις εφαρμογής.

Μηχανισμός LMK:

  • Μηχανισμός Killer χαμηλής μνήμης.
  • Η κύρια λειτουργία του είναι να τερματίζει ορισμένες διεργασίες παρασκηνίου σύμφωνα με μια συγκεκριμένη πολιτική προτεραιότητας όταν η μνήμη του συστήματος δεν επαρκεί για την απελευθέρωση της μνήμης και τη διασφάλιση της σταθερότητας και της απόκρισης του συστήματος.

λύσει

Πρόβλημα εξομάλυνσης της μνήμης

Κωδικός προβλήματος προσομοίωσης
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
Χρησιμοποιήστε το εργαλείο Memory Profiler για ανίχνευση

Το Memory Profiler μπορεί να δει τις εκχωρήσεις μνήμης, κάντε κλικ στην επιλογή "Εγγραφή εκχωρήσεων Java/Kotlin".

Εισαγάγετε την περιγραφή της εικόνας εδώ

Έννοια των παραπάνω:

  • Java: Μνήμη που εκχωρείται με κώδικα Java ή Kotlin.
  • Εγγενής: Μνήμη που εκχωρείται με κώδικα C ή C++.
  • Γραφικά: Η μνήμη που χρησιμοποιείται από την ουρά προσωρινής αποθήκευσης γραφικών για την εμφάνιση εικονοστοιχείων στην οθόνη (συμπεριλαμβανομένων των επιφανειών GL, των υφών GL, κ.λπ.). Κοινόχρηστη μνήμη CPU.
  • Στοίβα: Η μνήμη που χρησιμοποιείται από την εγγενή στοίβα και τη στοίβα Java στην εφαρμογή. Αυτό συνήθως έχει να κάνει με το πόσα νήματα εκτελείται η εφαρμογή σας.
  • Μνήμη που χρησιμοποιείται από εφαρμογές για την επεξεργασία κώδικα και πόρων όπως dex bytecode, βελτιστοποιημένος ή μεταγλωττισμένος κώδικας dex, βιβλιοθήκες .so και γραμματοσειρές.
  • Η εφαρμογή χρησιμοποιεί μνήμη που το σύστημα δεν είναι βέβαιο για τον τρόπο ταξινόμησης.
  • Ο αριθμός των αντικειμένων Java/Kotlin που έχουν εκχωρηθεί από την εφαρμογή. Αυτός ο αριθμός δεν λαμβάνει υπόψη αντικείμενα που έχουν εκχωρηθεί σε C ή C++.

Οι ακόλουθες έννοιες:

  • Κατανομές: Πάσο malloc() ήnew Ο αριθμός των αντικειμένων που εκχωρήθηκαν από τον χειριστή.
  • Κατανομές: μέσω free() ήdelete Ο αριθμός των αντικειμένων που εκχωρήθηκαν από τον χειριστή.
  • Μέγεθος κατανομών: Το συνολικό μέγεθος όλων των εκχωρήσεων κατά την επιλεγμένη χρονική περίοδο, σε byte.
  • Μέγεθος Deallocations: Το συνολικό μέγεθος της μνήμης που απελευθερώθηκε κατά την επιλεγμένη χρονική περίοδο, σε byte.
  • Συνολική καταμέτρηση: Κατανομές μείον Κατανομές.
  • Μέγεθος που απομένει: Μέγεθος Κατανομών μείον Μέγεθος Διανομών.
  • Μικρό μέγεθος: Το συνολικό μέγεθος όλων των παρουσιών στο σωρό, σε byte.

Ανάλυση της παραπάνω εικόνας:

Εισαγάγετε την περιγραφή της εικόνας εδώ

Οι τιμές των Κατανομών και των Διανομών σε αυτήν την περιοχή είναι σχετικά παρόμοιες και το Ρηχό Μέγεθος είναι σχετικά μεγάλο, υποδεικνύοντας ότι τα αντικείμενα ενδέχεται να δημιουργούνται και να καταστρέφονται συχνά.

Εισαγάγετε την περιγραφή της εικόνας εδώ

Αφού κάνετε κλικ, μπορείτε να προβάλετε τις πληροφορίες της στοίβας κλήσεων και σε συνδυασμό με τον κωδικό, μπορείτε να συμπεράνετε ότι υπάρχει πρόβλημα τρεμούλιασης μνήμης στο Handler.

Συμβουλές βελτιστοποίησης
  • Αποφύγετε να δημιουργείτε και να καταστρέφετε συχνά αντικείμενα.

Πρόβλημα διαρροής μνήμης

Κωδικός προβλήματος προσομοίωσης
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
Χρησιμοποιήστε το εργαλείο LeakCanary για ανίχνευση

Προσθήκη εξαρτημένων βιβλιοθηκών:

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

Μετά την εμφάνιση διαρροής μνήμης, το LeakCanary θα δημιουργήσει σχετικές πληροφορίες και θα τις απορρίψει αυτόματα:

Εισαγάγετε την περιγραφή της εικόνας εδώ

Όπως φαίνεται από το παραπάνω σχήμα: Το LeakActivity έχει διαρροή μνήμης και εμφανίζεται η σχέση της αλυσίδας αναφοράς.

Φυσικά, μπορείτε επίσης να δημιουργήσετε ένα αρχείο hprof και να προβάλετε συγκεκριμένες πληροφορίες μέσω του εργαλείου Profiler:

Εισαγάγετε την περιγραφή της εικόνας εδώ

Όπως φαίνεται από την παραπάνω εικόνα: 10 σημεία διαρροής, συμπεριλαμβανομένου του LeakActivity, κάντε κλικ στο LeakActivity για να δείτε το αντικείμενο που έχει διαρρεύσει και να ελέγξετε την αλυσίδα αναφοράς.

Συμβουλές βελτιστοποίησης
  • Έγκαιρη ανακύκλωση στοιχείων συλλογής.
  • Αποφύγετε τις στατικές αναφορές σε πάρα πολλές περιπτώσεις.
  • Χρησιμοποιήστε στατικές εσωτερικές τάξεις.
  • Κλείστε τα αντικείμενα πόρων αμέσως.

Βελτιστοποίηση bitmap

Εάν δεν απελευθερώσετε τους πόρους εικόνας μετά τη χρήση του Bitmap, είναι εύκολο να προκληθείΔιαρροή μνήμης, με αποτέλεσμα υπερχείλιση μνήμης

Μοντέλο μνήμης bitmap
  • Πριν από το api10 (Android 2.3.3): Τα αντικείμενα bitmap τοποθετούνται στη μνήμη σωρού και τα δεδομένα pixel τοποθετούνται στην τοπική μνήμη.
  • Μετά το api10: όλα στη μνήμη σωρού.
  • Μετά το api26 (Android8.0): τα δεδομένα pixel τοποθετούνται στην τοπική μνήμη. Αυτό επιτρέπει στα δεδομένα εικονοστοιχείων Bitmap του εγγενούς επιπέδου να απελευθερωθούν γρήγορα μαζί με τα αντικείμενα του επιπέδου Java.

Ανακύκλωση μνήμης:

  • Πριν από το Android 3.0, πρέπει να το καλέσετε χειροκίνηταBitmap.recycle()Εκτελέστε ανακύκλωση Bitmap.
  • Ξεκινώντας από το Android 3.0, το σύστημα παρέχει πιο έξυπνη διαχείριση μνήμης και στις περισσότερες περιπτώσεις δεν χρειάζεται να ανακυκλώσετε το Bitmap με μη αυτόματο τρόπο.

Διαμόρφωση pixel bitmap:

ΔιαμόρφωσηΜέγεθος κατειλημμένου byte (byte)εικονογραφώ
ALPHA_81ενιαίο διαφανές κανάλι
RGB_5652Απλή απόχρωση RGB
ARGB_8888424-bit αληθινό χρώμα
RGBA_F168Android 8.0 Νέο (HDR)

Υπολογίστε τη μνήμη που καταλαμβάνει το Btimap:

  • Bitmap#getByteCount()
  • getWidth() * getHeight() * 1 pixel καταλαμβάνει μνήμη
Κατάλογος αρχείων πόρων

Πρόβλημα αρχείου πόρων:

  • mdpi (μεσαία πυκνότητα): περίπου 160 dpi, πόρος 1x.
  • hdpi (υψηλή πυκνότητα): περίπου 240 dpi, πόροι 1,5x.
  • xhdpi (Εξτρα υψηλή πυκνότητα): Περίπου 320 dpi, πόροι 2x.
  • xxhdpi (Extra Ultra High Density): Περίπου 480dpi, πόροι 3x.
  • xxxhdpi (Extra Ultra High Density): Περίπου 640dpi, 4x πόροι.

Κωδικός δοκιμής:

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

εικονογραφώ:

1dp σε συσκευές mdpi1px, 1dp σε συσκευές xhdpi2px, 1dp==3px σε συσκευή xxhdpi.

Επομένως, η τρέχουσα συσκευή είναι xxhdpi, επομένως το πλάτος της ίδιας εικόνας κάτω από τον πόρο xxhdpi είναι 858, κάτω από τον πόρο mdpi θα μεγεθύνεται 3 φορές και το πλάτος είναι 2574, και κάτω από τον πόρο xhdpi θα μεγεθύνεται 1,5 φορές και το πλάτος είναι 1287.

Συμβουλές βελτιστοποίησης
  • Ρυθμίστε πολλαπλά σύνολα πόρων εικόνας.
  • Επιλέξτε την κατάλληλη μέθοδο αποκωδικοποίησης.
  • Ρύθμιση προσωρινής μνήμης εικόνων.

Λήψη πηγαίου κώδικα