Zarządzanie pamięcią aplikacji

Na tej stronie znajdziesz informacje o tym, jak zapobiegawczo zmniejszyć użycie pamięci w aplikacji. Informacje o tym, jak system operacyjny Android zarządza pamięcią, znajdziesz w artykule Omówienie zarządzania pamięcią.

Pamięć RAM to cenny zasób dla każdego środowiska programistycznego. 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 je rozwiążesz, musisz znaleźć problemy z wykorzystaniem pamięci przez aplikację. Profilator pamięci w Android Studio pomaga znajdować i diagnozować problemy z pamięcią w następujący 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.
  • Inicjowanie zdarzeń zbierania pamięci podręcznej i robienie zrzutu pamięci podręcznej Java podczas działania aplikacji.
  • Rejestruj przydzielanie pamięci w aplikacji, sprawdzaj wszystkie przydzielone obiekty, wyświetlaj zrzuty stosu dla poszczególnych przydziałów i przechodź do odpowiedniego kodu w edytorze Android Studio.

Zwalnianie pamięci w odpowiedzi na zdarzenia

Android może odzyskać pamięć z aplikacji lub całkowicie ją zatrzymać, jeśli to konieczne, aby zwolnić pamięć dla ważnych zadań. Więcej informacji znajdziesz w artykule 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() powiadamia aplikację o zdarzeniach cyklu życia lub zdarzeniach związanych z pamięcią, które przedstawiają wartość możliwość dobrowolnego ograniczenia wykorzystania pamięci przez aplikację. 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 błąd OutOfMemoryError.

Aby uniknąć wyczerpania pamięci, możesz wysłać do systemu zapytanie o ilość dostępnego miejsca dostępne na danym urządzeniu. Aby uzyskać tę wartość, możesz wysłać do systemu zapytanie, dzwoniąc pod numer 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 zatrzymania procesów. 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 konstrukcji kodu bardziej efektywnie wykorzystujących pamięć.

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

Stanowczo zalecamy, aby nie pozostawiać uruchomionych usług, gdy nie jest to konieczne. Pozostawienie uruchomionych niepotrzebnych usług to jeden z najgorszych błędów zarządzania pamięcią, jakie może popełnić aplikacja na Androida. Jeśli aplikacja potrzebuje usługi do działania w tle, nie zostawiaj jej uruchomionej, chyba że musi wykonać zadanie. Zatrzymaj usługę po wykonaniu 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 to nawet doprowadzić do przeciążenia systemu, gdy brakuje pamięci i system nie może utrzymać wystarczającej liczby procesów, aby hostować wszystkie aktualnie działające usługi.

Unikaj korzystania z trwałych usług, ponieważ wymagają one ciągłego dostępu do pamięci. Zamiast tego zalecamy użycie innej implementacji, takiej jak WorkManager Więcej informacji o używaniu funkcji WorkManager do planowania procesów w tle znajdziesz w artykule Trwałe zadania.

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 używają abstrakcji, ponieważ pomagają one zwiększyć elastyczność i łatwość konserwacji kodu. 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żywanie skompresowanych protokołów protobuf do serializacji danych

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łe protokoły generują bardzo obszerny kod, który może powodować wiele problemów w aplikacji, takich jak zwiększone wykorzystanie pamięci RAM, znaczne zwiększenie rozmiaru pliku APK i wolniejsze działanie.

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 zbieranie śmieci, tym szybciej bateria się wyczerpuje.

Często przepełnienie pamięci może powodować dużą liczbę zdarzeń związanych z usuwaniem śmieci. W praktyce rotacja pamięci określa liczbę 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. Możesz też utworzyć nowe obiekty Paint lub Bitmap w ramach funkcji onDraw() widoku. W obu przypadkach aplikacja szybko tworzy wiele obiektów przy dużych ilościach. Mogą one szybko zużyć całą dostępną pamięć w młodym pokoleniu, co spowoduje wywołanie procesu zbierania elementów.

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 będzie potrzebna instancja obiektu tego typu, możesz ją pobrać z puli zamiast przydzielić.

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 utrzymanie puli zwykle wymaga synchronizacji, która wiąże się z niebagatelnym obciążeniem. 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.

Zatrzymywanie w pulach większej liczby instancji obiektów niż jest to potrzebne obciąża też proces zbierania elementów do usunięcia. Chociaż obiekty w pulach zmniejszają liczbę wywołań mechanizmu usuwania elementów, ostatecznie zwiększają one ilość pracy wymaganej do każdego wywołania, ponieważ jest ona proporcjonalna do liczby aktywnych (dostępnych) bajtów.

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

Niektóre zasoby i biblioteki w Twoim kodzie mogą zużywać pamięć bez Twojej wiedzy. ogólny rozmiar aplikacji, w tym biblioteki innych firm lub umieszczone zasoby, mogą zużywaną przez aplikację pamięć. Możesz zmniejszyć zużycie pamięci przez aplikację, usuwając z kodu niepotrzebne komponenty, zasoby i biblioteki lub komponenty, które zajmują zbyt dużo miejsca.

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 Zmniejsz rozmiar aplikacji.

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

Ramy wstrzykiwania zależności mogą uprościć kod, który piszesz, i zapewnić elastyczne środowisko, które jest przydatne do testowania i innych zmian konfiguracji.

Jeśli chcesz użyć w aplikacji mechanizmu iniekcji zależności, rozważ użycie Hilt lub Dagger. Hilt to biblioteka do wstrzykiwania zależności na Androida, która działa na podstawie Daggera. 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 mechanizmy wstrzykiwania zależności, które używają mechanizmu odbicia wstecznego do inicjowania procesów przez skanowanie kodu pod kątem adnotacji; Ten proces może wymagać znacznie większej liczby cykli pracy procesora i pamięci RAM zauważalnego opóźnienia przy uruchamianiu aplikacji.

Uważaj na używanie 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órych oczekujesz w tych bibliotekach, mogą wymagać zależności niskiego 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 używania komponentów wspólnych tylko do obsługi 1 lub 2 funkcji spośród kilkudziesięciu. 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ć własną implementację.