Na tej stronie znajdziesz informacje o tym, jak aktywnie zmniejszać wykorzystanie pamięci w aplikacji. Więcej informacji o tym, jak system operacyjny Android zarządza pamięcią, znajdziesz w omówieniu zarządzania pamięcią.
Pamięć RAM jest cennym zasobem w każdym środowisku programistycznym, a jeszcze cenniejsza jest w przypadku mobilnego systemu operacyjnego, w którym pamięć fizyczna jest często ograniczona. Chociaż środowisko wykonawcze Androida (ART) i maszyna wirtualna Dalvik przeprowadzają rutynowe odśmiecanie pamięci, nie oznacza to, że możesz ignorować, kiedy i gdzie aplikacja przydziela i zwalnia pamięć. Nadal musisz unikać wycieków pamięci, które zwykle są spowodowane przechowywaniem odwołań do obiektów w statycznych zmiennych składowych, i zwalniać obiekty Reference w odpowiednim czasie, zgodnie z wywołaniami zwrotnymi cyklu życia.
Zmniejszanie rozmiaru kodu i zasobów aplikacji
Niektóre zasoby i biblioteki w kodzie mogą zużywać pamięć bez Twojej wiedzy. Ogólny rozmiar aplikacji, w tym bibliotek innych firm i zasobów osadzonych, może wpływać na ilość pamięci zużywanej przez aplikację. Możesz zmniejszyć zużycie pamięci przez aplikację, usuwając z kodu zbędne, niepotrzebne lub rozbudowane komponenty, zasoby i biblioteki.
Zmniejszanie ogólnego rozmiaru aplikacji przez włączenie R8
Skompilowany kod aplikacji jest aktywną częścią pamięci środowiska wykonawczego. Każda klasa, metoda, zależność biblioteki i stała tekstowa musi być załadowana do pamięci RAM podczas działania. Im większa jest skompilowana baza kodu, tym więcej pamięci RAM wymaga aplikacja.
Możesz użyć R8, aby zmniejszyć wykorzystanie pamięci przez aplikację. R8 jest tradycyjnie znany z zmniejszania rozmiaru pliku APK, ale ma też bezpośredni, pozytywny wpływ na pamięć środowiska wykonawczego (RAM). R8 analizuje kod bajtowy aplikacji, aby usuwać martwy kod, scalać nadmiarowe klasy, wstawiać metody i minimalizować identyfikatory. Dzięki wczytywaniu do pamięci RAM mniejszej ilości skompilowanego kodu bajtowego z pliku APK zmniejsza ogólne podstawowe wykorzystanie pamięci przez aplikację. Dodatkowo minimalizowanie nazw klas, metod i pól do krótszych identyfikatorów bezpośrednio zmniejsza narzut pamięci RAM. Optymalizacje takie jak łączenie klas i rozbudowane wstawianie metod zastępują też kosztowne wyszukiwania w czasie działania i wzorce alokacji, co skutkuje zoptymalizowaną pamięcią sterty i stosu.
Omówienie reguł przechowywania
Reguły zachowywania to instrukcje konfiguracji, które informują R8, które części kodu należy zachować podczas optymalizacji. Zapobiegają one usuwaniu lub minimalizowaniu kodu, na którym opiera się aplikacja. Więcej informacji znajdziesz w artykule Omówienie reguł przechowywania.
Źle napisane reguły keep uniemożliwiają R8 optymalizację dużych części bazy kodu. Unikaj zbyt ogólnych reguł przechowywania i postępuj zgodnie z tymi sprawdzonymi metodami:
- Globalne reguły, których należy unikać:
-dontoptimize: całkowicie wyłącza optymalizację całej aplikacji, co powoduje, że pliki wykonywalne są większe i działają wolniej.-dontshrink: zapobiega usuwaniu nieużywanego kodu i zasobów.-dontobfuscate: zapobiega minifikacji nazw, co uniemożliwia uzyskanie znacznych oszczędności pamięci (zwłaszcza w przypadku dużych aplikacji).
Unikaj symboli wieloznacznych obejmujących cały pakiet: ogólne reguły, takie jak
-keep class com.example.package.** { *; }, wymuszają zachowanie przez R8 każdej klasy, pola i metody w tym pakiecie. Całkowicie uniemożliwia to R8 usuwanie, optymalizowanie i minimalizowanie kodu w tym pakiecie.Użyj domyślnego pliku konfiguracji R8: zawsze używaj
proguard-android-optimize.txt.
Więcej informacji o tworzeniu reguł przechowywania znajdziesz w artykule Omówienie reguł przechowywania. Konkretne wzorce, których należy używać i których należy unikać, znajdziesz w artykule Sprawdzone metody dotyczące reguł Keep.
Analizator konfiguracji R8 dostarcza informacji o konfiguracji R8 i o tym, jak każda reguła zachowywania wpływa na aplikację. Więcej informacji o tym, jak identyfikować reguły blokujące optymalizację, znajdziesz w sekcji Analizator konfiguracji R8.
Uważaj na korzystanie z bibliotek zewnętrznych
Kod biblioteki zewnętrznej często nie jest pisany z myślą o środowiskach mobilnych i może być nieefektywny w pracy na urządzeniu mobilnym. Jeśli używasz biblioteki zewnętrznej, może być konieczne zoptymalizowanie jej pod kątem urządzeń mobilnych. Zaplanuj te działania z wyprzedzeniem i przeanalizuj bibliotekę pod kątem rozmiaru kodu i zajętości pamięci RAM przed jej użyciem.
Nawet niektóre biblioteki zoptymalizowane pod kątem urządzeń mobilnych mogą powodować problemy z powodu różnych implementacji. Na przykład jedna biblioteka może używać uproszczonych buforów protokołu, a inna – mikrobuforów protokołu, co spowoduje, że w aplikacji będą 2 różne implementacje buforów protokołu. Może się to zdarzyć w przypadku różnych implementacji rejestrowania, analiz, platform wczytywania obrazów, buforowania i wielu innych rzeczy, których się nie spodziewasz.
Optymalizacja aplikacji za pomocą R8 może usuwać nieużywany kod z zależności, ale jej skuteczność jest często ograniczona przez wewnętrzną konfigurację biblioteki. Na przykład ogólne reguły zachowywania lub użycie odbicia w bibliotece mogą uniemożliwić R8 zmniejszenie kodu, co prowadzi do większego zużycia pamięci. Strategie wyboru wydajnych bibliotek znajdziesz w artykule Rozważny wybór bibliotek.
Unikaj używania biblioteki wspólnej tylko w przypadku 1–2 funkcji z kilkudziesięciu. Nie pobieraj dużej ilości kodu i dodatkowych elementów, których nie używasz. Zastanawiając się, czy użyć biblioteki, poszukaj implementacji, która w dużym stopniu odpowiada Twoim potrzebom. W przeciwnym razie możesz zdecydować się na utworzenie własnej implementacji.
Używanie Hilt lub Dagger 2 do wstrzykiwania zależności
Frameworki wstrzykiwania zależności mogą uprościć pisany przez Ciebie kod i zapewnić adaptacyjne środowisko przydatne do testowania i innych zmian konfiguracji.
Jeśli zamierzasz używać w aplikacji platformy wstrzykiwania zależności, rozważ użycie Hilt lub Daggera. Hilt to biblioteka wstrzykiwania zależności na Androida, która działa na platformie Dagger. Dagger nie używa odbicia do skanowania kodu aplikacji. Możesz używać statycznej implementacji Daggera w czasie kompilacji w aplikacjach na Androida bez niepotrzebnych kosztów w czasie działania i wykorzystania pamięci.
Inne platformy wstrzykiwania zależności, które korzystają z odbicia, inicjują procesy, skanując kod pod kątem adnotacji. Ten proces może wymagać znacznie większej liczby cykli procesora i pamięci RAM oraz powodować zauważalne opóźnienie podczas uruchamiania aplikacji.
Podczas korzystania z wstrzykiwania zależności zachowaj ostrożność, aby uniknąć wycieków pamięci. W tym celu dopilnuj, aby obiekty miały odpowiedni zakres. Przechowywanie obiektów dłużej niż jest to konieczne przez powiązanie ich z nieprawidłowym cyklem życia może prowadzić do wycieków pamięci.
Świadomie wczytuj obrazy
Bitmapy graficzne są zwykle największymi obiektami w pamięci aplikacji. Nawet jeśli pracujesz ze skompresowanymi plikami, takimi jak JPEG, plik musi zostać rozpakowany do nieskompresowanej mapy bitowej, aby można było go wyświetlić na ekranie. Mały skompresowany plik obrazu może zostać przekształcony w bardzo dużą bitmapę.
Na przykład większość bitmap używa konfiguracji ARGB_8888, co oznacza, że każdy piksel wymaga 4 bajtów pamięci – po 1 bajcie na kolor czerwony, zielony, niebieski i kanał alfa (przezroczystość). Jeśli masz plik JPEG o rozmiarze 100 KB i wyświetlasz go w widoku o rozmiarze 1000 × 1000 pikseli, bitmapa będzie wymagać 4 bajtów na każdy z tych 1 000 000 pikseli, co daje łącznie 4 MB pamięci.
Aby zoptymalizować wykorzystanie obrazów, możesz zrobić kilka rzeczy. Na przykład biblioteki wczytywania obrazów mogą pomóc w zwalnianiu pamięci, gdy nie jest ona potrzebna. Więcej informacji o wydajnym przetwarzaniu obrazów znajdziesz w artykule Optymalizacja obrazów bitmapowych.
Monitorowanie dostępnej pamięci i wykorzystania pamięci
Zanim rozwiążesz problemy z wykorzystaniem pamięci przez aplikację, musisz je znaleźć. Profiler pamięci w Android Studio pomaga znajdować i diagnozować problemy z pamięcią na te sposoby:
- Sprawdzanie, jak aplikacja przydziela pamięć w czasie. Memory Profiler pokazuje w czasie rzeczywistym wykres ilości pamięci używanej przez aplikację, liczbę przydzielonych obiektów Java i momentów, w których następuje odśmiecanie pamięci.
- Inicjuj zdarzenia odśmiecania pamięci i rób zrzuty sterty Javy podczas działania aplikacji.
- Rejestruj przydziały pamięci w aplikacji, sprawdzaj wszystkie przydzielone obiekty, wyświetlaj zrzut stosu dla każdego przydziału i przechodź do odpowiedniego kodu w edytorze Android Studio.
Profiler pamięci jest też zintegrowany z biblioteką LeakCanary do wykrywania wycieków pamięci. Dzięki LeakCanary możesz przenieść analizę wycieków pamięci z urządzenia testowego na komputer używany do programowania, co może znacznie przyspieszyć przepływ pracy. Więcej informacji znajdziesz w informacjach o wersji Androida Studio.
Istnieją inne narzędzia, których możesz użyć do diagnozowania problemów z pamięcią na podstawie danych od użytkowników korzystających z aplikacji w wersji produkcyjnej:
- Używaj Android Vitals do śledzenia zdarzeń związanych z zamykaniem aplikacji z braku pamięci (LMK).
- Użyj Menedżera profilowania, aby śledzić błędy braku pamięci, a także nieprawidłowe działanie aplikacji, które może być spowodowane wyciekami pamięci.
Zwalnianie pamięci w odpowiedzi na zdarzenia
W razie potrzeby Android może odzyskać pamięć z aplikacji lub całkowicie ją zatrzymać, aby zwolnić pamięć na potrzeby krytycznych zadań, co zostało wyjaśnione w omówieniu zarządzania pamięcią. Aby jeszcze bardziej zrównoważyć pamięć systemową i uniknąć konieczności zatrzymania procesu aplikacji przez system, możesz zaimplementować interfejs ComponentCallbacks2 w klasach Activity. Podana metoda wywołania zwrotnego onTrimMemory()
powiadamia aplikację o zdarzeniach związanych z cyklem życia lub pamięcią, które
stanowią dobrą okazję do dobrowolnego zmniejszenia wykorzystania pamięci przez aplikację.
Zwolnienie pamięci może zmniejszyć częstotliwość zamykania aplikacji przez proces zamykania aplikacji z powodu braku pamięci.
Wdrożenie onTrimMemory() powinno koncentrować się wyłącznie na zdarzeniach TRIM_MEMORY_UI_HIDDEN i TRIM_MEMORY_BACKGROUND. (Od Androida 14 system nie dostarcza już powiadomień dotyczących innych, starszych stałych). Te stałe zostały formalnie wycofane w Androidzie 15).
TRIM_MEMORY_UI_HIDDEN: ten sygnał wskazuje, że interfejs aplikacji zniknął z widoku użytkownika. Ta zmiana daje możliwość zwolnienia znacznych ilości pamięci ściśle powiązanych z interfejsem, takich jak mapy bitowe, bufory odtwarzania filmów czy złożone zasoby animacji.TRIM_MEMORY_BACKGROUND: ten sygnał oznacza, że proces działa w tle i może zostać zakończony, aby zaspokoić globalne potrzeby systemu w zakresie pamięci. Aby wydłużyć czas, przez jaki proces pozostaje w stanie buforowanym, i zmniejszyć liczbę zimnych startów aplikacji, należy agresywnie zwalniać wszystkie zasoby, które można łatwo odtworzyć po wznowieniu sesji przez użytkownika.
Ten przykładowy kod pokazuje, jak wdrożyć wywołanie zwrotne onTrimMemory(), aby reagować na różne zdarzenia związane z pamięcią:
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, ile pamięci potrzebujesz
Aby umożliwić działanie wielu procesów, Android ustawia sztywny limit rozmiaru sterty
przydzielonej każdej aplikacji. Dokładny limit rozmiaru sterty różni się w zależności od urządzenia i zależy od
całkowitej ilości dostępnej pamięci RAM. Jeśli aplikacja osiągnie pojemność sterty 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 na stercie na bieżącym urządzeniu. Możesz wysłać do systemu zapytanie o tę wartość, wywołując funkcję getMemoryInfo(). Zwraca obiekt ActivityManager.MemoryInfo, który zawiera informacje o bieżącym stanie pamięci urządzenia, w tym o dostępnej pamięci, całkowitej pamięci i progu pamięci – poziomie pamięci, przy którym system zaczyna zatrzymywać procesy. Obiekt ActivityManager.MemoryInfo udostępnia też lowMemory, czyli prostą wartość logiczną, która informuje, czy na urządzeniu jest mało pamięci.
Ten przykładowy fragment kodu pokazuje, jak używać metody getMemoryInfo() w 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;
}
Monitorowanie zamykania aplikacji z powodu małej ilości pamięci
Błędy LMK (low memory kill) widoczne dla użytkowników występują, gdy pamięć systemowa jest krytycznie mała. Gdy pamięć jest prawie pełna, lmkd (demon LMK) zamyka procesy na podstawie ich oom_adj_score. Aplikacje, które są buforowane lub uruchamiają usługę bez powiązanego interfejsu (np. zadanie), mają najwyższe wyniki i są zamykane w pierwszej kolejności. Jeśli ilość pamięci nadal jest krytycznie niska, demon jest zmuszony do odzyskania pamięci z procesów o wartości oom_adj_score 0.
Ten wynik jest zarezerwowany dla widocznych aplikacji, więc ich zakończenie powoduje natychmiastowe, nieprawidłowe wyjście z procesu. Dla użytkownika końcowego wygląda to tak, jakby aplikacja uległa awarii, często z pominięciem standardowych mechanizmów zapisywania stanu cyklu życia i utratą postępów użytkownika.
Zakończenia procesów na pierwszym planie są głównym elementem w Android Vitals, ponieważ stanowią dokładny wskaźnik nieprawidłowego zarządzania pamięcią. Wskaźnik LMK powyżej 1% wskazuje na pilną potrzebę podjęcia natychmiastowych działań, ale niski wskaźnik nie musi oznaczać, że wszystko jest w porządku. Niska częstotliwość błędów LMK widocznych dla użytkowników może oznaczać, że demon LMK często zamyka procesy działające w tle, co pogarsza wydajność uruchamiania częściowo z pamięci i płynność wielozadaniowości. Dlatego zalecamy stosowanie sprawdzonych metod zarządzania pamięcią niezależnie od aktualnego wyniku LMK, aby zapewnić długoterminową stabilność i dobry stan urządzenia.
Śledzenie problemów z pamięcią za pomocą aplikacji ProfilingManager
Platforma Android udostępnia interfejs
ProfilingManager, zaawansowany interfejs API do obserwacji, który umożliwia rejestrowanie danych użytkowników w środowisku produkcyjnym na podstawie ustawionych przez Ciebie wyzwalaczy. Może to pomóc w wykryciu trudnych do odtworzenia problemów z pamięcią.
Dwa nowe wyzwalacze wprowadzone w Androidzie 17 są szczególnie przydatne do wykrywania problemów z pamięcią:
TRIGGER_TYPE_OOMoznacza, że aplikacja zgłosiłaOutOfMemoryError. Zostanie on wywołany przy następnym uruchomieniu aplikacji po awarii, gdy zarejestruje ona wyzwalacze profilowania.TRIGGER_TYPE_ANOMALYjest wywoływane, gdy system wykryje nietypowe działanie aplikacji. Może to być spowodowane m.in. nadmiernym wykorzystaniem pamięci. Jest wywoływane, gdy aplikacja wykazuje nadmierne wykorzystanie pamięci, ale zanim system podejmie jakiekolwiek działania w celu zatrzymania procesu powodującego problem. Jeśli na przykład aplikacja przekracza limity pamięci wprowadzone w Androidzie 17,TRIGGER_TYPE_ANOMALYwywołuje się przed zamknięciem aplikacji przez system.
Więcej informacji o używaniu ProfilingManager do programowego rejestrowania i pobierania wyzwalaczy znajdziesz w dokumentacji profilowania opartego na wyzwalaczach. Firebase Crashlytics obsługuje też wyzwalacze TRIGGER_TYPE_OOM i TRIGGER_TYPE_ANOMALY, korelując je z innymi metadanymi diagnostycznymi.
Możesz też używać profilowania opartego na aplikacji, aby ręcznie definiować punkty początkowe i końcowe śledzenia. Zalecamy to rozwiązanie, jeśli chcesz ręcznie przechwytywać zrzuty sterty lub profile sterty w obszarach, w których podejrzewasz wycieki pamięci lub nadmierne wykorzystanie pamięci.
Śledzenie stanu procesu i pamięci
Oprócz wywołań zwrotnych i zdarzeń związanych z pamięcią możesz też jawnie wysyłać zapytania o stan i priorytet procesów aplikacji. Jest to szczególnie przydatne, aby zrozumieć, jak system operacyjny klasyfikuje bieżący stan aplikacji (np. czy jest ona na pierwszym planie, czy jest widoczna dla użytkownika lub czy jest w pamięci podręcznej) i czy istnieje ryzyko, że zostanie zamknięta przez proces zabijania aplikacji z powodu małej ilości pamięci.
Z panelem w Pythonie do ciągłego monitorowania
Podczas działania aplikacji użyj skryptu w języku Python Android Memory Monitor, aby uzyskać widok ciągłego monitorowania procesów aplikacji i wykorzystania pamięci. Jest to narzędzie wiersza poleceń, które za pomocą adb poleceń wyświetla terminal i panele internetowe, aby pomóc Ci zrozumieć stan i wykorzystanie pamięci przez procesy aplikacji. Aby uruchomić panele, uruchom skrypt z nazwą pakietu aplikacji, np.:
python3 android_mem_monitor.py your.package.name
Szczegółowe instrukcje dotyczące funkcji panelu i sposobu ich używania znajdziesz w pliku README lub w komentarzach w skrypcie.
W czasie działania z ActivityManager
Aby uzyskać informacje o procesach działających w czasie wykonywania, możesz wysłać zapytanie do ActivityManager.getRunningAppProcesses(). Zwraca listę obiektów zawierających podstawowe informacje o każdym procesie aplikacji. Pole importance wskazuje względne znaczenie, jakie system przypisuje procesowi, i odpowiada stałym wartościom, takim jak:
IMPORTANCE_FOREGROUND: proces aktywnie uruchamia interfejs użytkownika na pierwszym planie.IMPORTANCE_FOREGROUND_SERVICE: proces działa jako usługa na pierwszym planie (np. aktywne odtwarzanie muzyki w tle lub pobieranie dużych plików), co jest przykładem usługi postrzeganej przez użytkownika.IMPORTANCE_CACHED: ten proces zawiera kod w pamięci podręcznej. Proces jest wstrzymywany, a niewykorzystana pamięć jest odzyskiwana przez Androida. W tym stanie system operacyjnyMemoryLimiternie zakończy działania aplikacji z powodu przekroczenia progu pamięci przypisanego do aplikacji. Jeśli jednak w systemie zabraknie pamięci, aplikacja będzie pierwszym kandydatem do zamknięcia przez proces zabijania aplikacji przy niskim poziomie pamięci.
Do debugowania za pomocą adb
Podczas debugowania zachowania pamięci i cykli życia procesów możesz też używać poleceń
adb, aby w czasie rzeczywistym sprawdzać wyniki OOM aplikacji i jej ślad pamięci. Aby monitorować procesy i stany pamięci aplikacji, możesz użyć tych poleceń:
- Znajdź identyfikatory aktywnych procesów (PID): najpierw znajdź wszystkie procesy, które są obecnie uruchomione w Twojej aplikacji, używając polecenia
psfiltrowanego według nazwy pakietu.
shell adb shell ps -A | grep your.package.name - Sprawdź bieżący wynik OOM: użyj identyfikatora PID procesu, aby odczytać jego wynik OOM z systemu plików procesu. Wynik 0–199 zwykle oznacza aplikację na pierwszym planie, 200–249 – widoczną usługę, 250–899 – usługę w tle, a wynik powyżej 900 – proces w pamięci podręcznej, który prawdopodobnie zostanie zakończony.
shell adb shell cat /proc/pid/oom_score_adj - Ciągłe monitorowanie wyników OOM: aby ciągle monitorować zmiany wyniku OOM aplikacji (który będzie najniższym wynikiem OOM spośród wszystkich jej procesów), możesz użyć polecenia
watch-uids.
shell adb shell am watch-uids - Pomiar śladu pamięci: aby szybko odczytać podstawowy ślad pamięci procesu, np. anonimowe wykorzystanie pamięci RSS i pamięci wymiany, odczytaj jego stan bezpośrednio z systemu plików procesu w systemie Linux.
shell adb shell cat /proc/pid/status | grep -E "VmRSS|RssAnon|VmSwap"
Aby uzyskać bardziej szczegółowe i kompleksowe zestawienie przydziałów pamięci, użyj poleceniadumpsys meminfo:
shell adb shell dumpsys meminfo pid - Sprawdź aktywne komponenty: jeśli proces utrzymuje nieoczekiwanie wysoki priorytet (np. pozostaje na poziomie wyniku OOM
200zamiast spaść do poziomu buforowania), możesz sprawdzić, co dokładnie jest uruchomione w ramach tego procesu. Poleceniedumpsys activity processesujawnia konkretne aktywności, usługi, dostawców i odbiorców, którzy utrzymują proces przy życiu.
shell adb shell dumpsys activity processes your.package.name
Używaj bardziej efektywnych pod względem pamięci konstrukcji kodu
Niektóre funkcje Androida, klasy Java i konstrukcje kodu zużywają więcej pamięci niż inne. Możesz zminimalizować ilość pamięci używanej przez aplikację, wybierając w kodzie bardziej wydajne alternatywy.
Korzystaj z usług oszczędnie
Zdecydowanie odradzamy pozostawianie usług włączonych, gdy nie jest to konieczne. Uruchamianie niepotrzebnych usług to jeden z najgorszych błędów w zarządzaniu pamięcią, jakie może popełnić aplikacja na Androida. Jeśli aplikacja potrzebuje usługi do działania w tle, nie pozostawiaj jej uruchomionej, chyba że musi wykonać zadanie. Zatrzymaj usługę po zakończeniu zadania. W przeciwnym razie może dojść do wycieku pamięci.
Gdy uruchomisz usługę, system będzie starać się utrzymać proces tej usługi. Takie zachowanie sprawia, że procesy usług są bardzo kosztowne, ponieważ pamięć RAM używana przez usługę pozostaje niedostępna dla innych procesów. Zmniejsza to liczbę procesów w pamięci podręcznej, które system może przechowywać w pamięci podręcznej LRU, co obniża wydajność przełączania aplikacji. Może to nawet prowadzić do nadmiernego obciążenia systemu, gdy pamięci jest za mało i nie można utrzymać wystarczającej liczby procesów do obsługi wszystkich aktualnie działających usług.
Zasadniczo unikaj korzystania z usług trwałych ze względu na ciągłe zapotrzebowanie na dostępną pamięć. Zamiast tego zalecamy używanie alternatywnej implementacji, np. WorkManager.
Więcej informacji o tym, jak używać WorkManager do planowania procesów w tle, znajdziesz w artykule Praca ciągła.
Używanie zoptymalizowanych kontenerów danych
Niektóre klasy udostępniane przez język programowania nie są zoptymalizowane pod kątem
używania na urządzeniach mobilnych. Na przykład ogólna implementacja HashMap może być nieefektywna pod względem pamięci, ponieważ wymaga oddzielnego obiektu wpisu dla każdego mapowania.
Platforma Androida zawiera kilka zoptymalizowanych kontenerów danych, w tym SparseArray, SparseBooleanArray i LongSparseArray. Na przykład klasy SparseArray są bardziej wydajne, ponieważ nie wymagają automatycznego opakowywania klucza, a czasami także wartości, co powoduje utworzenie kolejnych obiektów dla każdego wpisu.
W razie potrzeby możesz zawsze przełączyć się na surowe tablice, aby uzyskać prostą strukturę danych.
Uważaj na abstrakcje kodu
Programiści często używają abstrakcji jako dobrej praktyki programowania, ponieważ mogą one zwiększyć elastyczność kodu i ułatwić jego konserwację. Abstrakcje zwykle wymagają jednak wykonania większej ilości kodu. Jak opisano w artykule Zmniejszanie rozmiaru kodu i zasobów aplikacji, większa skompilowana baza kodu bezpośrednio zwiększa ilość pamięci RAM, której wymaga aplikacja. Jeśli abstrakcje nie przynoszą znaczących korzyści, unikaj ich.
Używanie uproszczonych buforów protokołu do serializacji danych
Bufory protokołu to niezależny od języka i platformy rozszerzalny mechanizm zaprojektowany przez Google do serializowania danych strukturalnych – podobny do XML, ale mniejszy, szybszy i prostszy. Jeśli do przechowywania danych używasz protokołów buforów, w kodzie po stronie klienta zawsze używaj protokołów buforów w wersji lite. Zwykłe bufory protokołu generują bardzo obszerny kod, co zwiększa rozmiar kodu aplikacji w pamięci RAM (patrz Zmniejszanie rozmiaru kodu i zasobów aplikacji) i przyczynia się do zwiększenia rozmiaru pliku APK.
Więcej informacji znajdziesz w pliku readme protobuf.
Uważaj na wycieki pamięci
Nieprawidłowe zarządzanie odwołaniami może prowadzić do wycieków pamięci, w których obiekty żyją dłużej niż powinny, co uniemożliwia odzyskanie pamięci przez moduł odśmiecania. Aby uniknąć wycieków pamięci, zastosuj projektowanie z uwzględnieniem cyklu życia.
Unikaj fragmentacji pamięci
Zdarzenia odśmiecania pamięci nie mają wpływu na wydajność aplikacji. Jednak wiele zdarzeń odśmiecania, które występują w krótkim czasie, może szybko wyczerpać baterię, a także nieznacznie wydłużyć czas konfiguracji klatek ze względu na niezbędne interakcje między modułem odśmiecania a wątkami aplikacji. Im więcej czasu system poświęca na odśmiecanie pamięci, tym szybciej wyczerpuje się bateria.
Często fragmentacja pamięci może powodować dużą liczbę zdarzeń odśmiecania pamięci. W praktyce churn pamięci opisuje liczbę przydzielonych obiektów tymczasowych, które występują w danym okresie.
Możesz na przykład przydzielić wiele obiektów tymczasowych w pętli for.
Możesz też utworzyć nowe obiekty Paint lub Bitmap w funkcji onDraw() widoku. W obu przypadkach aplikacja szybko tworzy dużą liczbę obiektów. Mogą one szybko zużyć całą dostępną pamięć w młodszej generacji, co wymusza uruchomienie odśmiecania pamięci.
Użyj profilera pamięci, aby znaleźć w kodzie miejsca, w których występuje duża rotacja pamięci, zanim je naprawisz.
Po zidentyfikowaniu problematycznych obszarów w kodzie spróbuj zmniejszyć liczbę alokacji w obszarach o krytycznym znaczeniu dla wydajności. Rozważ przeniesienie elementów poza pętle wewnętrzne lub do struktury alokacji opartej na fabryce.
Możesz też ocenić, czy pule obiektów są korzystne w danym przypadku użycia. W przypadku puli obiektów zamiast upuszczać instancję obiektu na podłogę, zwalniasz ją do puli, gdy nie jest już potrzebna. Gdy następnym razem będzie potrzebna instancja obiektu tego typu, możesz ją pobrać z puli zamiast przydzielać.
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ść. Chociaż pule unikają alokacji, wprowadzają inne obciążenia. Na przykład utrzymywanie puli zwykle wiąże się z synchronizacją, która generuje znaczne obciążenie. Wyczyszczenie instancji obiektu z puli, aby uniknąć wycieków pamięci podczas zwalniania, a następnie zainicjowanie jej podczas pozyskiwania może wiązać się z pewnym narzutem.
Przechowywanie w puli większej liczby instancji obiektów niż jest to potrzebne również obciąża mechanizm odśmiecania pamięci. Pule obiektów zmniejszają liczbę wywołań odzyskiwania pamięci, ale zwiększają ilość pracy potrzebnej do każdego wywołania, ponieważ jest ona proporcjonalna do liczby aktywnych (osiągalnych) bajtów.