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:

  • Zobacz, jak Twoja aplikacja przydziela pamięć w ciągu czasu. Narzędzie do profilowania pamięci wyświetla w czasie rzeczywistym wykres pokazujący, ile pamięci używa aplikacja, liczbę przydzielonych obiektów Java oraz kiedy następuje zbieranie elementów.
  • Inicjuj zdarzenia czyszczenia pamięci i rób zrzut sterty Javy podczas używania aplikacji biegi.
  • Możesz rejestrować przydzielanie pamięci przez aplikację, sprawdzać wszystkie przydzielone obiekty, wyświetlać zrzut stosu dla każdej alokacji i przechodzić 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 metoda wywołania zwrotnego onTrimMemory()powiadamia aplikację o zdarzeniach związanych z cyklem życia lub pamięcią, które dają jej możliwość dobrowolnego zmniejszenia wykorzystania pamięci. Zwolnienie pamięci może zmniejszyć prawdopodobieństwo zamknięcia aplikacji przez zapewnianie małej ilości pamięci.

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) {

        if (level >= ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN) {
            // Release memory related to UI elements, such as bitmap caches.
        }

        if (level >= ComponentCallbacks2.TRIM_MEMORY_BACKGROUND) {
            // Release memory related to background processing, such as by
            // closing a database connection.
        }
    }
}

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) {

        if (level >= ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN) {
            // Release memory related to UI elements, such as bitmap caches.
        }

        if (level >= ComponentCallbacks2.TRIM_MEMORY_BACKGROUND) {
            // Release memory related to background processing, such as by
            // closing a database connection.
        }
    }
}

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

Aby umożliwić działanie wielu procesów, Android ustawia stały limit rozmiaru stosu dla każdej aplikacji. Dokładny limit rozmiaru stosu różni się na różnych urządzeniach w zależności od dostępnej pamięci RAM. 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.

Oszczędnie korzystaj z usług

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.

Korzystanie z optymalnych 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. Jednak abstrakcje są znacznie droższe, ponieważ zazwyczaj wymagają większej ilości kodu, który musi zostać wykonany, co wymaga więcej czasu i pamięci RAM na mapowanie kodu w 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 znajdziesz w README protobuf.

Unikanie zużywania pamięci

Zdarzenia związane z zbieraniem pamięci nie wpływają 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 usuwanie śmieci, tym szybciej bateria kanalizacji.

Często rezygnacje z pamięci mogą powodować dużą liczbę zdarzeń czyszczenia pamięci. W praktyce rotacja pamięci to liczba przypisanych tymczasowych obiektów, które występują w danym przedziale 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ę przydziału.

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, może zużywaną przez aplikację pamięć. Możesz ograniczyć 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. Te narzędzia obsługują nowoczesne metody kompresji 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 Trener. Hilt to wstrzykiwanie zależności na Androida, która działa na platformie Dagger. Dagger nie używa odbicia lustrzanego do skanowania kodu Twojej aplikacji. 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ęcej cykli procesora i pamięci RAM oraz może spowodować zauważalne opóźnienie podczas uruchamiania aplikacji.

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

Kod zewnętrznej biblioteki często nie jest napisany z myślą o środowiskach mobilnych i może być nieefektywny w przypadku klienta mobilnego. 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órych oczekujesz w tych bibliotekach, mogą wymagać zależności niskiego poziomu. Jest to szczególnie problematyczne, gdy używasz podklasy Activity z biblioteki, która może mieć wiele zależności, gdy biblioteki używają odbicia lustrzanego, co jest powszechne i wymaga ręcznego dostosowania ProGuarda.

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. Jeśli rozważasz użycie biblioteki, poszukaj implementacji, która najlepiej odpowiada Twoim potrzebom. W przeciwnym razie możesz utworzyć na własną implementację.