Zarządzanie pamięcią aplikacji

Na tej stronie dowiesz się, jak aktywnie zmniejszać wykorzystanie pamięci przez aplikację. Informacje na temat: jak system operacyjny Android zarządza pamięcią, Omówienie zarządzania pamięcią

Pamięć RAM jest cennym zasobem w każdym środowisku programistycznym – Z kolei rozwiązanie sprawdza się jeszcze lepiej w mobilnym systemie operacyjnym, w którym ilość pamięci fizycznej jest często ograniczona. Mimo że zarówno środowisko wykonawcze Androida (ART), jak i maszyna wirtualna Dalvik wykonują rutynowe usuwanie czyszczenia nie oznacza to, że możesz ignorować, kiedy i gdzie aplikacja przydziela i udostępnia pamięć. Nadal musisz unikać wycieków pamięci, zwykle spowodowanych przez przytrzymanie obiektu odwołań w statycznych zmiennych członkowskich, a następnie Reference obiektów w odpowiedni czas określony przez wywołania zwrotne cyklu życia.

Monitorowanie dostępnej pamięci i wykorzystania pamięci

Zanim rozwiążesz problemy z wykorzystaniem pamięci przez aplikację, musisz je znaleźć. Narzędzie do profilowania pamięci w Android Studio pomaga znaleźć i diagnozuj problemy z pamięcią w ten sposób:

  • Sprawdź, jak aplikacja przydziela pamięć na przestrzeni czasu. Narzędzie do profilowania pamięci pokazuje w czasie rzeczywistym, jak dużo pamięci wykorzystywanej przez aplikację, liczbę przydzielonych obiektów Java oraz czas czyszczenia pamięci ma miejsce.
  • Inicjuj zdarzenia czyszczenia pamięci i rób zrzut sterty Javy podczas używania aplikacji biegi.
  • Rejestruj przydziały pamięci aplikacji, sprawdzaj wszystkie przydzielone obiekty, wyświetl zrzut stosu i przejdź do odpowiedniego kodu w edytorze Android Studio.

Zwalnianie pamięci w odpowiedzi na zdarzenia

Android może odzyskać pamięć z aplikacji lub całkowicie zatrzymać aplikację, aby zwolnić pamięć. w przypadku zadań o znaczeniu krytycznym, jak wyjaśniono w Omówienie zarządzania pamięcią Aby uzyskać dalszą pomoc zrównoważyć pamięć systemową i uniknąć zatrzymania procesu aplikacji przez system, można zaimplementować ComponentCallbacks2 w zajęciach Activity. Podana wartość onTrimMemory() metoda wywołania zwrotnego pozwala aplikacji nasłuchiwać zdarzeń związanych z pamięcią, gdy aplikacja znajduje się w zarówno na pierwszym planie, jak i w tle. Następnie umożliwia aplikacji publikowanie obiektów w odpowiedzi na cykl życia aplikacji lub zdarzenia systemowe wskazujące, że system musi odzyskać pamięć.

Możesz zaimplementować wywołanie zwrotne onTrimMemory(), aby odpowiedzieć na różne żądania związane z pamięcią zdarzeń, jak widać w tym przykładzie:

Kotlin

import android.content.ComponentCallbacks2
// Other import statements.

class MainActivity : AppCompatActivity(), ComponentCallbacks2 {

    // Other activity code.

    /**
     * Release memory when the UI becomes hidden or when system resources become low.
     * @param level the memory-related event that is raised.
     */
    override fun onTrimMemory(level: Int) {

        // Determine which lifecycle or system event is raised.
        when (level) {

            ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN -> {
                /*
                   Release any UI objects that currently hold memory.

                   The user interface moves to the background.
                */
            }

            ComponentCallbacks2.TRIM_MEMORY_RUNNING_MODERATE,
            ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW,
            ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL -> {
                /*
                   Release any memory your app doesn't need to run.

                   The device is running low on memory while the app is running.
                   The event raised indicates the severity of the memory-related event.
                   If the event is TRIM_MEMORY_RUNNING_CRITICAL, then the system
                   begins stopping background processes.
                */
            }

            ComponentCallbacks2.TRIM_MEMORY_BACKGROUND,
            ComponentCallbacks2.TRIM_MEMORY_MODERATE,
            ComponentCallbacks2.TRIM_MEMORY_COMPLETE -> {
                /*
                   Release as much memory as the process can.

                   The app is on the LRU list and the system is running low on memory.
                   The event raised indicates where the app sits within the LRU list.
                   If the event is TRIM_MEMORY_COMPLETE, the process is one of the
                   first to be terminated.
                */
            }

            else -> {
                /*
                  Release any non-critical data structures.

                  The app receives an unrecognized memory level value
                  from the system. Treat this as a generic low-memory message.
                */
            }
        }
    }
}

Java

import android.content.ComponentCallbacks2;
// Other import statements.

public class MainActivity extends AppCompatActivity
    implements ComponentCallbacks2 {

    // Other activity code.

    /**
     * Release memory when the UI becomes hidden or when system resources become low.
     * @param level the memory-related event that is raised.
     */
    public void onTrimMemory(int level) {

        // Determine which lifecycle or system event is raised.
        switch (level) {

            case ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN:

                /*
                   Release any UI objects that currently hold memory.

                   The user interface moves to the background.
                */

                break;

            case ComponentCallbacks2.TRIM_MEMORY_RUNNING_MODERATE:
            case ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW:
            case ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL:

                /*
                   Release any memory your app doesn't need to run.

                   The device is running low on memory while the app is running.
                   The event raised indicates the severity of the memory-related event.
                   If the event is TRIM_MEMORY_RUNNING_CRITICAL, then the system
                   begins stopping background processes.
                */

                break;

            case ComponentCallbacks2.TRIM_MEMORY_BACKGROUND:
            case ComponentCallbacks2.TRIM_MEMORY_MODERATE:
            case ComponentCallbacks2.TRIM_MEMORY_COMPLETE:

                /*
                   Release as much memory as the process can.

                   The app is on the LRU list and the system is running low on memory.
                   The event raised indicates where the app sits within the LRU list.
                   If the event is TRIM_MEMORY_COMPLETE, the process is one of the
                   first to be terminated.
                */

                break;

            default:
                /*
                  Release any non-critical data structures.

                  The app receives an unrecognized memory level value
                  from the system. Treat this as a generic low-memory message.
                */
                break;
        }
    }
}

Sprawdzanie ilości pamięci, której potrzebujesz

Aby umożliwić korzystanie z wielu uruchomionych procesów, Android ustawia stały limit rozmiaru sterty przypisanym do każdego z nich . Dokładny limit rozmiaru stosu różni się w zależności od urządzenia w zależności od ilości dostępnej pamięci RAM i ogólne. Jeśli aplikacja osiągnie pojemność stosu i spróbuje przydzielić więcej pamięci, system zgłosi OutOfMemoryError.

Aby uniknąć wyczerpania pamięci, możesz wysłać do systemu zapytanie o ilość dostępnego miejsca dostępne na danym urządzeniu. Możesz przesłać do systemu zapytanie dotyczące tego rysunku, wywołując getMemoryInfo() Powoduje to zwrócenie ActivityManager.MemoryInfo obiekt dostarczający informacje o bieżącym stanie pamięci urządzenia, w tym ilość pamięci, łączną ilość pamięci oraz próg pamięci, czyli poziom pamięci, od którego system zaczyna Zatrzymać procesy. Obiekt ActivityManager.MemoryInfo udostępnia też lowMemory, czyli prostej wartości logicznej, która określa, czy na urządzeniu kończy się pamięć.

Poniższy przykładowy fragment kodu pokazuje, jak używać metody getMemoryInfo() w do aplikacji.

Kotlin

fun doSomethingMemoryIntensive() {

    // Before doing something that requires a lot of memory,
    // check whether the device is in a low memory state.
    if (!getAvailableMemory().lowMemory) {
        // Do memory intensive work.
    }
}

// Get a MemoryInfo object for the device's current memory status.
private fun getAvailableMemory(): ActivityManager.MemoryInfo {
    val activityManager = getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
    return ActivityManager.MemoryInfo().also { memoryInfo ->
        activityManager.getMemoryInfo(memoryInfo)
    }
}

Java

public void doSomethingMemoryIntensive() {

    // Before doing something that requires a lot of memory,
    // check whether the device is in a low memory state.
    ActivityManager.MemoryInfo memoryInfo = getAvailableMemory();

    if (!memoryInfo.lowMemory) {
        // Do memory intensive work.
    }
}

// Get a MemoryInfo object for the device's current memory status.
private ActivityManager.MemoryInfo getAvailableMemory() {
    ActivityManager activityManager = (ActivityManager) this.getSystemService(ACTIVITY_SERVICE);
    ActivityManager.MemoryInfo memoryInfo = new ActivityManager.MemoryInfo();
    activityManager.getMemoryInfo(memoryInfo);
    return memoryInfo;
}

Używaj mniej pamięciowych konstrukcji kodu

Niektóre funkcje Androida, klasy Java i konstrukcje kodu wykorzystują więcej pamięci niż inne. Dostępne opcje aby zminimalizować ilość pamięci wykorzystywanej przez aplikację, wybierając w kodzie bardziej wydajne alternatywy.

Korzystaj z usług z umiarem

Zdecydowanie odradzamy pozostawianie uruchomionych usług, gdy nie są one niepotrzebne. Zostaw niepotrzebne uruchomione usługi to jeden z najgorszych błędów w zarządzaniu pamięcią, jakie może popełniać aplikacja na Androida. Jeśli Twoja aplikacja potrzebuje usługi do działania w tle, nie opuszczaj musi być uruchomiony, chyba że musi uruchomić jakieś zadanie. Zatrzymaj usługę po zakończeniu zadania. W przeciwnym razie może to doprowadzić do wycieku pamięci.

Gdy uruchamiasz usługę, system preferuje, aby proces jej działania nie został uruchomiony. Ten sprawia, że procesy usługi są bardzo kosztowne, ponieważ ilość pamięci RAM wykorzystywanej przez usługę pozostaje bez zmian niedostępne w przypadku innych procesów. Zmniejsza to liczbę procesów przechowywanych w pamięci podręcznej, które system może w pamięci podręcznej LRU, co zmniejsza efektywność przełączania aplikacji. Może nawet doprowadzić do wrogów w systemie, gdy pamięć jest niewystarczająca i system nie jest w stanie utrzymać wystarczającej liczby procesów do obsługi wszystkich usług uruchomione.

Zasadniczo unikaj stosowania trwałych usług z powodu stałego zapotrzebowania na dostępne usługi pamięci. Zamiast tego zalecamy użycie innej implementacji, takiej jak WorkManager Więcej informacji na temat konfiguracji o tym, jak za pomocą WorkManager planować procesy w tle, zobacz Trwała praca.

Użyj zoptymalizowanych kontenerów danych

Niektóre zajęcia dostępne w języku programowania nie są zoptymalizowane pod kątem urządzeń mobilnych urządzenia. Na przykład ogólny Implementacja HashMap może być pamięcią nieefektywne, ponieważ wymaga osobnego obiektu wpisu dla każdego mapowania.

Platforma Androida obejmuje kilka zoptymalizowanych kontenerów danych, w tym: SparseArray, SparseBooleanArray, i LongSparseArray. Na przykład klasy SparseArray są skuteczniejsze, ponieważ omijają Trzeba autobox klucz, a czasem wartość, co powoduje utworzenie kolejnego lub dwóch obiektów na wpis.

W razie potrzeby zawsze możesz przełączyć się na nieprzetworzone tablice, aby uzyskać przejrzystą strukturę danych.

Uważaj na abstrakcje kodu

Deweloperzy często stosują abstrakcje jako dobrą metodę programowania, ponieważ pozwalają ulepszyć kod. i utrzymanie. Abstrakcje są jednak znacznie kosztowne, ponieważ zwykle wymagają więcej kodu do wykonania, co wymaga więcej czasu i pamięci RAM do mapowania do pamięci. Jeśli Twoje wyobrażenia nie przynoszą zbyt wiele korzyści, unikaj ich.

Używaj buforów protokołów Lite w przypadku danych seryjnych

Protokół bufory (protobufy) to neutralny dla języka i platformy mechanizm z elastycznością. Został zaprojektowany przez Google do serializowania uporządkowanych danych – podobnych do formatu XML, ale w mniejszej, szybszej i prostszej wersji. Jeśli dla danych używasz buforów protokołów, zawsze używaj uproszczonych protokołów w kodzie po stronie klienta. Zwykła Generują bardzo szczegółowy kod, który może powodować wiele problemów w aplikacji, np. większe wykorzystanie pamięci RAM, znaczny wzrost rozmiaru pliku APK i wolniejsze wykonywanie działania.

Więcej informacji: protobuf Readme.

Unikanie zużywania pamięci

Zdarzenia czyszczenia pamięci nie mają wpływu na wydajność aplikacji. Jednak wiele funkcji czyszczenia pamięci krótkotrwałe wydarzenia mogą szybko wyczerpywać baterię, wydłużają czas konfigurowania ramek z powodu niezbędnych interakcji między modułem odśmiecania wątki w aplikacjach. Im więcej czasu system poświęca na czyszczenie pamięci, tym szybciej bateria kanalizacji.

Często rezygnacje z pamięci mogą powodować dużą liczbę zdarzeń czyszczenia pamięci. W wspomniana funkcja oznacza liczbę przydzielonych obiektów tymczasowych, które występują w danym od czasu do czasu.

Możesz na przykład przydzielić wiele obiektów tymczasowych w pętli for. Lub: możesz utworzyć nowe Paint lub Bitmap obiektów wewnątrz onDraw() funkcji widoku. W obu przypadkach aplikacja szybko tworzy wiele obiektów przy dużych ilościach. Te mogą szybko wykorzystać całą dostępną pamięć w młodym pokoleniu, wymuszając czyszczenie pamięci zdarzenia konwersji.

Użyj narzędzia do profilowania pamięci, aby znaleźć miejsca w w kodzie, w którym zużywa się dużo pamięci.

Po zidentyfikowaniu problematycznych obszarów w kodzie spróbuj zmniejszyć liczbę przydziałów w w obszarach o krytycznym znaczeniu dla wydajności. Rozważ usunięcie elementów z wewnętrznej pętli lub przeniesienie ich fabrycznie strukturę alokacji.

Możesz też ocenić, czy pule obiektów są korzystne dla danego przypadku użycia. Za pomocą puli obiektów zamiast upuszczając instancję obiektu na podłodze, wypuścisz ją do puli, gdy nie będzie już potrzebna. Gdy następnym razem instancja obiektu tego typu będzie potrzebna, możesz ją pobrać z puli, niż ich przydzielanie.

Dokładnie oceń wydajność, aby określić, czy pula obiektów jest odpowiednia w danej sytuacji. W niektórych przypadkach pule obiektów mogą pogorszyć wydajność. Mimo że pule omijają nakładają na siebie inne zadania. Na przykład konserwacja puli zwykle obejmuje synchronizacji, które mają zasadnicze znaczenie. Wyczyść też instancję puli obiektów unikanie wycieków pamięci podczas publikowania, a jego inicjowanie w trakcie pozyskiwania może mieć wartość inną niż zero nadmiarowe.

Wstrzymanie większej liczby instancji obiektów w puli niż jest to konieczne powoduje również obciążenie śmieci kolekcji. Pule obiektów zmniejszają liczbę wywołań czyszczenia, ale ostatecznie na potrzeby każdego wywołania, ponieważ jest on proporcjonalny do liczby aktywnych (osiągalnych) bajtów.

Usuń zasoby i biblioteki wymagające dużej ilości pamięci

Niektóre zasoby i biblioteki w kodzie mogą bez Twojej wiedzy wykorzystywać pamięć. ogólny rozmiar aplikacji, w tym biblioteki innych firm lub umieszczone zasoby, mogą ilość pamięci wykorzystywanej przez aplikację. Możesz poprawić wykorzystanie pamięci przez aplikację, usuwając zbędne, komponentów, zasobów i bibliotek z kodu, które są zbędne lub rozroszczone.

Zmniejsz ogólny rozmiar pliku APK

Możesz znacznie zmniejszyć wykorzystanie pamięci przez aplikację, zmniejszając jej ogólny rozmiar. Na rozmiar obrazu mogą wpływać rozmiar mapy bitowej, zasoby, ramki animacji i biblioteki innych firm. Twojej aplikacji. Android Studio i pakiet Android SDK zapewniają wiele narzędzi, które pomagają zmniejszyć między zasobami i zewnętrznymi zależnościami. Narzędzia te obsługują nowoczesne metody zmniejszania kodu, takie jak: Kompilacja R8.

Więcej informacji o zmniejszaniu ogólnego rozmiaru aplikacji znajdziesz w artykule Zmniejszanie rozmiaru aplikacji.

Wstrzykiwanie zależności za pomocą Hilta lub Daggera 2

Platformy wstrzykiwania zależności mogą uprościć pisany kod i zapewnić przydatne do testowania i wprowadzania innych zmian w konfiguracji.

Jeśli zamierzasz stosować w swojej aplikacji platformę wstrzykiwania zależności, rozważ zastosowanie Hilt lub Dagger. Hilt to wstrzykiwanie zależności na Androida, która działa na platformie Dagger. Sztylet nie używa odbicia do skanowania w kodzie. W aplikacjach na Androida możesz używać statycznej implementacji czasu kompilacji Daggera na kosztach środowiska wykonawczego lub o wykorzystaniu pamięci.

Inne platformy wstrzykiwania zależności, które używają procesów odbicia, inicjują te procesy przez skanowanie na potrzeby adnotacji. Ten proces może wymagać znacznie większej liczby cykli pracy procesora i pamięci RAM zauważalnego opóźnienia przy uruchamianiu aplikacji.

Zachowaj ostrożność podczas korzystania z bibliotek zewnętrznych

Zewnętrzny kod biblioteki często nie jest napisany pod kątem środowisk mobilnych i może być nieefektywny nad klientem mobilnym. Jeśli korzystasz z biblioteki zewnętrznej, być może trzeba będzie ją zoptymalizować, na urządzenia mobilne. Zaplanuj pracę z wyprzedzeniem i przeanalizuj bibliotekę pod kątem rozmiar kodu i ilość pamięci RAM.

Nawet niektóre biblioteki zoptymalizowane pod kątem urządzeń mobilnych mogą powodować problemy ze względu na różne implementacje. Dla: Jedna biblioteka może na przykład korzystać z protokołów Lite, a inna mikroprotobufów, co daje dwa różne implementacje buforów protokołu w swojej aplikacji. Dzieje się tak przy różnych implementacjach funkcji takich jak logowanie, analityka, platformy wczytywania obrazów, buforowanie i wiele innych rzeczy, których się nie spodziewamy.

Chociaż ProGuard może pomagać w usuwaniu interfejsów API i zasobów z: odpowiednie flagi, nie usunie dużych wewnętrznych zależności biblioteki. Funkcje, które Cię interesują te biblioteki mogą wymagać zależności niższego poziomu. Przydaje się to szczególnie wtedy, gdy użyj podklasy Activity z która może mieć szeroki zakres zależności, gdy biblioteki używają funkcji refleksji, jest powszechny i wymaga ręcznego dostosowania ProGuard, aby działał.

Unikaj korzystania z zasobów wspólnych do jednej lub dwóch z dziesiątek funkcji. Nie wciągaj dużych dużo kodu i nakłady pracy, których nie używasz. Rozważając skorzystanie z biblioteki, implementacji, która silnie odpowiada Twoim potrzebom. W przeciwnym razie możesz utworzyć na własną implementację.