Sprawdzone metody zarządzania pamięcią

W tym dokumencie zakładamy, że zastosowano sprawdzone metody dotyczące zarządzania pamięcią w aplikacjach na Androida, np. Zarządzanie pamięcią aplikacji.

Wprowadzenie

Wyciek pamięci to rodzaj wycieku zasobów, który występuje, gdy program komputerowy nie zwalnia przydzielonej pamięci, której już nie potrzebuje. Wyciek pamięci może spowodować, że aplikacja żąda od systemu więcej pamięci, niż ma dostępnej, i w konsekwencji się zawiesza. W aplikacjach na Androida wiele niewłaściwych praktyk może powodować wycieki pamięci, np. nieprawidłowe usuwanie zasobów lub nierejestrowanie odsłuchiwania, gdy nie jest już potrzebne.

W tym dokumencie znajdziesz sprawdzone metody zapobiegania, wykrywania i rozwiązywania przecieków pamięci w kodzie. Jeśli wypróbowałeś metody opisane w tym dokumencie i podejrzewasz, że w pakietach SDK występuje wyciek pamięci, zapoznaj się z artykułem Jak zgłaszać problemy z pakietami Google SDK.

Zanim skontaktujesz się z zespołem pomocy

Zanim zgłosisz wyciek pamięci do zespołu pomocy Google, postępuj zgodnie ze wskazówkami dotyczącymi najlepszych praktyk i opisanymi w tym dokumencie czynnościami debugowania, aby upewnić się, że błąd nie występuje w Twoim kodzie. Te czynności mogą rozwiązać problem. Jeśli nie, wygenerują informacje, których potrzebuje zespół pomocy Google, aby Ci pomóc.

Zapobieganie wyciekom pamięci

Stosuj te sprawdzone metody, aby uniknąć niektórych najczęstszych przyczyn wycieków pamięci w kodzie, który korzysta z pakietów Google SDK.

Sprawdzone metody dotyczące aplikacji na Androida

Sprawdź, czy w aplikacji na Androida wykonasz wszystkie te czynności:

  1. Zwolnij nieużywane zasoby.
  2. Wyrejestruj słuchaczy, gdy nie są już potrzebni.
  3. Anuluj zadania, gdy nie są potrzebne.
  4. Wyślij metody cyklu życia, aby zwolnić zasoby
  5. Używaj najnowszych wersji pakietów SDK

Szczegółowe informacje o każdej z tych metod znajdziesz w następnych sekcjach.

Zwalnianie nieużywanych zasobów

Gdy aplikacja na Androida używa zasobu, pamiętaj, aby go zwolnić, gdy nie jest już potrzebny. Jeśli tego nie zrobisz, zasób będzie nadal zajmować pamięć nawet po zakończeniu korzystania z niego przez aplikację. Więcej informacji znajdziesz w dokumentacji Androida na temat cyklu życia aktywności.

Zwolnij nieaktualne odwołania do GoogleMap w GeoSDK

Typowym błędem jest to, że GoogleMap może spowodować wyciek pamięci, jeśli jest przechowywany w pamięci podręcznej za pomocą NavigationView lub MapView. Obiekt GoogleMap ma relację jeden do jednego z obiektem NavigationView lub MapView, z którego jest pobierany. Musisz zadbać o to, aby mapa Google nie była przechowywana w pamięci podręcznej, lub aby odwołanie zostało zwolnione, gdy wywołano metodę NavigationView#onDestroy lub MapView#onDestroy. Jeśli używasz fragmentu NavigationSupportFragment, MapSupportFragment lub własnego fragmentu otaczającego te widoki, musisz zwolnić odwołanie w Fragment#onDestroyView.

class NavFragment : SupportNavigationFragment() {

  var googleMap: GoogleMap?

  override fun onCreateView(
    inflater: LayoutInflater,
    parent: ViewGroup?,
    savedInstanceState: Bundle?,
  ): View  {
    super.onCreateView(inflater,parent,savedInstanceState)
    getMapAsync{map -> googleMap = map}
  }

  override fun onDestroyView() {
    googleMap = null
  }
}

rejestrowanie i odrejestrowanie słuchaczy, gdy nie są już potrzebne;

Gdy aplikacja na Androida zarejestruje słuchacza zdarzenia, np. kliknięcie przycisku lub zmianę stanu widoku, pamiętaj, aby zarejestrować słuchacza, gdy aplikacja nie będzie już musiała monitorować zdarzenia. Jeśli tego nie zrobisz, odbiorcy będą nadal zajmować pamięć nawet po zakończeniu działania aplikacji.

Załóżmy na przykład, że aplikacja korzysta z pakietu Navigation SDK i wywołuje ten dekoder, aby nasłuchiwać zdarzeń przybycia: addArrivalListener Metoda powinna też wywoływać metodę removeArrivalListener, gdy nie musi już monitorować zdarzeń przybycia.

var arrivalListener: Navigator.ArrivalListener? = null

fun registerNavigationListeners() {
  arrivalListener =
    Navigator.ArrivalListener {
      ...
    }
  navigator.addArrivalListener(arrivalListener)
}

override fun onDestroy() {
  navView.onDestroy()
  if (arrivalListener != null) {
    navigator.removeArrivalListener(arrivalListener)
  }

  ...
  super.onDestroy()
}

anulować zadania, gdy nie są już potrzebne.

Gdy aplikacja na Androida rozpocznie zadanie asynchroniczne, takie jak pobieranie lub żądanie sieciowe, pamiętaj, aby je anulować po jego zakończeniu. Jeśli zadanie nie zostanie anulowane, będzie nadal działać w tle, nawet gdy aplikacja zakończyła już z nim pracę.

Więcej informacji o sprawdzonych metodach znajdziesz w dokumentacji Androida dotyczących zarządzania pamięcią aplikacji.

Przesyłanie metod cyklu życia w celu zwalniania zasobów

Jeśli Twoja aplikacja korzysta z pakietu SDK Nawigacji lub Map, pamiętaj, aby zwolnić zasoby, przekazując metody cyklu życia (wyróżnione pogrubioną czcionką) do navView. Możesz to zrobić, używając funkcji NavigationView w pakiecie Navigation SDK lub funkcji MapView w pakiecie Maps SDK lub Navigation SDK. Zamiast operatorów NavigationViewMapView możesz też użyć odpowiednio operatorów SupportNavigationFragment lub SupportMapFragment. Fragmenty obsługujące obsługują przekazywanie metod cyklu życia.

class NavViewActivity : AppCompatActivity() {

  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    ...
    navView = ...
    navView.onCreate(savedInstanceState)
    ...
  }

  override fun onSaveInstanceState(savedInstanceState: Bundle) {
    super.onSaveInstanceState(savedInstanceState)
    navView.onSaveInstanceState(savedInstanceState)
  }

  override fun onTrimMemory(level: Int) {
    super.onTrimMemory(level)
    navView.onTrimMemory(level)
  }

  /* Same with
    override fun onStart()
    override fun onResume()
    override fun onPause()
    override fun onConfigurationChanged(...)
    override fun onStop()
    override fun onDestroy()
  */
}

Używanie najnowszych wersji pakietów SDK

Pakiety SDK Google są stale aktualizowane o nowe funkcje, poprawki błędów i ulepszenia wydajności. Aby otrzymywać te poprawki, aktualizuj pakiety SDK w swojej aplikacji.

Debugowanie wycieków pamięci

Jeśli po zastosowaniu wszystkich sugestii z początku tego dokumentu nadal występuje wyciek pamięci, wykonaj te czynności, aby przeprowadzić debugowanie.

Zanim zaczniesz, dowiedz się, jak Android zarządza pamięcią. Więcej informacji znajdziesz w artykule Omówienie zarządzania pamięcią na urządzeniach z Androidem.

Aby debugować wycieki pamięci:

  1. Odtwórz problem. Ten krok jest niezbędny do debugowania.
  2. Sprawdź, czy wykorzystanie pamięci jest oczekiwane. Sprawdź, czy zwiększone wykorzystanie, które wydaje się być wyciekiem, nie jest w istocie pamięcią wymaganą do działania aplikacji.
  3. Debugowanie na najwyższym poziomie. Do debugowania możesz użyć kilku narzędzi. Do debugowania problemów z pamięcią w Androidzie służą 3 standardowe zestawy narzędzi: Android Studio, Perfeto i narzędzia wiersza poleceń Android Debug Bridge (adb).
  4. Sprawdź wykorzystanie pamięci przez aplikację. Pobierz zrzut stosu i śledzenie alokacji, a następnie przeanalizuj te dane.
  5. Naprawienie wycieków pamięci.

W następnych sekcjach omawiamy te czynności szczegółowo.

Krok 1. Odtwórz problem

Jeśli nie udało Ci się odtworzyć problemu, najpierw zastanów się, jakie scenariusze mogą prowadzić do wycieku pamięci. Jeśli wiesz, że problem został odtworzony, możesz przejść od razu do przeglądania zrzutu pamięci. Jeśli jednak zrzut stosu zostanie utworzony tylko przy uruchamianiu aplikacji lub w innym losowym momencie, może to oznaczać, że nie zostały spełnione warunki powodujące wyciek pamięci. Podczas próby odtworzenia problemu rozważ przetestowanie różnych scenariuszy:

  • Jaki zestaw funkcji jest aktywowany?

  • Jaka konkretna sekwencja działań użytkownika powoduje wyciek?

    • Czy próbowałeś/próbowałaś aktywować tę sekwencję kilka razy?
  • Przez które stany cyklu życia przeszła aplikacja?

    • Czy przetestowałeś/przetestowałaś już kilka iteracji w różnych stanach cyklu życia?

Sprawdź, czy możesz odtworzyć problem w najnowszej wersji pakietów SDK. Problem z poprzedniej wersji może być już rozwiązany.

Krok 2. Sprawdź, czy zużycie pamięci przez aplikację jest prawidłowe

Każda funkcja wymaga dodatkowej pamięci. Podczas debugowania różnych scenariuszy zastanów się, czy jest to oczekiwane wykorzystanie aplikacji, czy też wyciek pamięci. W przypadku różnych funkcji lub zadań użytkownika rozważ te możliwości:

  • Prawdopodobna nieszczelność: aktywowanie scenariusza przez wiele iteracji powoduje z czasem wzrost wykorzystania pamięci.

  • Prawdopodobna oczekiwana ilość wykorzystanej pamięci: pamięć jest odzyskiwana po zatrzymaniu scenariusza.

  • Możliwe oczekiwane wykorzystanie pamięci: wykorzystanie pamięci wzrasta przez pewien czas, a potem maleje. Może to być spowodowane ograniczonym buforem lub innym oczekiwanym wykorzystaniem pamięci.

Jeśli zachowanie aplikacji jest zgodne z oczekiwanym użyciem pamięci, problem można rozwiązać, zarządzając pamięcią aplikacji. Więcej informacji znajdziesz w artykule Zarządzanie pamięcią aplikacji.

Krok 3. Debugowanie na wysokim poziomie

Podczas debugowania wycieku pamięci zacznij od ogólnego poziomu, a potem przejdź do bardziej szczegółowego poziomu, gdy już zawęzisz zakres możliwości. Aby sprawdzić, czy występuje wyciek danych, użyj jednego z tych ogólnych narzędzi debugowania:

Narzędzie Memory Profiler w Android Studio

To narzędzie wyświetla histogram zużytej pamięci. W tym samym interfejsie możesz też wywołać zrzuty stosu i śledzenie alokacji. To narzędzie jest zalecane domyślnie. Więcej informacji znajdziesz w profilu pamięci w Android Studio.

Liczniki pamięci w Perfetto

Perfetto zapewnia dokładną kontrolę nad śledzeniem wielu danych i prezentuje je w jednym histogramie. Więcej informacji znajdziesz w artykule Perfetto Memory Counters.

Interfejs użytkownika Perfetto

Narzędzia wiersza poleceń Android Debug Bridge (adb)

Wiele z tych informacji, które możesz śledzić za pomocą Perfetta, jest też dostępne w narzędziu wiersza poleceń adb, do którego możesz bezpośrednio kierować zapytania. Oto kilka ważnych przykładów:

  • Meminfo pozwala wyświetlić szczegółowe informacje o pamięci w określonym momencie.

  • Procstats zawiera ważne zsumowane statystyki na przestrzeni czasu.

Kluczową statystyką, na którą należy tu zwrócić uwagę, jest maksymalny rozmiar pamięci fizycznej (maxRSS), którego aplikacja wymaga z upływem czasu. Maksymalna wartość mocy sygnału (MaxPS) może być mniej dokładna. Sposób zwiększenia dokładności znajdziesz w sekcji dotyczącej flagi adb shell dumpsys procstats --help –start-testing.

Śledzenie alokacji

Śledzenie przydziału identyfikuje ślad stosu, w którym przydzielona została pamięć, oraz informuje, czy nie została ona zwolniona. Ten krok jest szczególnie przydatny podczas śledzenia wycieków w natywnym kodzie. Ponieważ to narzędzie identyfikuje ślad stosu, może być ono przydatne do szybkiego debugowania głównej przyczyny problemu lub dowiedzieć się, jak go odtworzyć. Instrukcje dotyczące korzystania z śledzenia alokacji znajdziesz w artykule Debugowanie pamięci w kodzie natywnym za pomocą śledzenia alokacji.

Krok 4. Sprawdź wykorzystanie pamięci przez aplikację za pomocą zrzutu stosu

Jednym ze sposobów wykrycia wycieku pamięci jest pobranie zrzutu stosu aplikacji i sprawdzenie, czy nie ma w niej wycieku pamięci. Zrzut stosu to zrzut wszystkich obiektów w pamięci aplikacji. Można go używać do diagnozowania wycieków pamięci i innych problemów związanych z pamięcią.

Android Studio może wykrywać wycieki pamięci, których nie można naprawić za pomocą GC. Gdy tworzysz zrzut stosu, Android Studio sprawdza, czy istnieje aktywność lub fragment, do których można jeszcze dotrzeć, ale które zostały już usunięte.

  1. Zapisz zrzut stosu.
  2. Aby znaleźć wycieki pamięci, przeanalizuj zrzut stosu.
  3. Naprawianie wycieków pamięci

Szczegółowe informacje znajdziesz w sekcjach poniżej.

Wykonywanie zrzutu stosu

Aby wykonać zrzut stosu, możesz użyć narzędzia Android Debug Bridge (adb) lub narzędzia do profilowania pamięci w Android Studio.

Wykorzystanie adb do przechwycenia zrzutu stosu

Aby wykonać zrzut stosu za pomocą adb, wykonaj te czynności:

  1. Podłącz urządzenie z Androidem do komputera.
  2. Otwórz wiersz polecenia i otwórz katalog, w którym znajdują się narzędzia adb.
  3. Aby uzyskać zrzut stosu, uruchom to polecenie :

    adb shell am dumpheap my.app.name $PHONE_FILE_OUT

  4. Aby pobrać zrzut stosu, uruchom to polecenie:

    adb pull $PHONE_FILE_OUT $LOCAL_FILE.

Zrzut stosu za pomocą Android Studio

Aby wykonać zrzut stosu za pomocą narzędzia Android Studio Memory Profiler, wykonaj czynności opisane w sekcji Zapisywanie zrzutu stosu.

Analizowanie zrzutu stosu w celu wykrywania wycieków pamięci

Po przechwyceniu zrzutu stosu możesz go przeanalizować za pomocą narzędzia do profilowania pamięci w Android Studio. W tym celu wykonaj następujące czynności:

  1. Otwórz projekt Androida w Android Studio.

  2. Kliknij Uruchom, a potem wybierz konfigurację Debugowanie.

  3. Otwórz kartę Profilator Androida.

  4. Wybierz Pamięć.

  5. Kliknij Otwórz zrzut stosu i wybierz wygenerowany plik zrzutu stosu. Profilator pamięci wyświetla wykres wykorzystania pamięci przez aplikację.

  6. Aby przeanalizować zrzut pamięci, użyj wykresu:

    • identyfikować obiekty, których już nie używasz;

    • Identyfikowanie obiektów, które wykorzystują dużo pamięci.

    • Zobacz, ile pamięci używa każdy obiekt.

  7. Użyj tych informacji, aby zawęzić lub znaleźć źródło wycieku pamięci i je naprawić.

Krok 5. Napraw wycieki pamięci

Gdy już zidentyfikujesz źródło wycieku pamięci, możesz je naprawić. Naprawy wycieków pamięci w aplikacjach na Androida pomagają poprawić wydajność i stabilność aplikacji. Szczegóły zależą od scenariusza. Pomocne mogą być jednak te sugestie:

Inne narzędzia do debugowania

Jeśli po wykonaniu tych czynności nadal nie udało Ci się znaleźć i usunąć wycieku pamięci, wypróbuj te narzędzia:

Debugowanie pamięci w kodzie natywnym z wykorzystaniem śledzenia alokacji

Nawet jeśli nie używasz bezpośrednio kodu natywnego, wiele popularnych bibliotek na Androida, w tym pakiety SDK Google, korzysta z niego. Jeśli uważasz, że wyciek pamięci występuje w kodzie natywnym, możesz użyć kilku narzędzi do debugowania. Śledzenie alokacji za pomocą Android Studio lub heapprofd (również zgodnego z Perfetto) to świetny sposób na zidentyfikowanie potencjalnych przyczyn wycieku pamięci. Jest to też często najszybsza metoda debugowania.

Śledzenie alokacji ma też tę zaletę, że umożliwia udostępnianie wyników bez uwzględniania informacji poufnych, które można znaleźć w stosie.

Wykrywanie wycieków za pomocą LeakCanary

LeakCanary to zaawansowane narzędzie do wykrywania wycieków pamięci w aplikacjach na Androida. Aby dowiedzieć się więcej o korzystaniu z LeakCanary w aplikacji, odwiedź stronę LeakCanary.

Zgłaszanie problemów z pakietami SDK Google

Jeśli wypróbowałeś metody opisane w tym dokumencie i podejrzewasz, że w naszych pakietach SDK występuje wyciek pamięci, skontaktuj się z obsługą klienta i podaj jak najwięcej z tych informacji:

  • Kroki do odtworzenia wycieku pamięci Jeśli czynności wymagają skomplikowanego kodowania, skopiuj kod, który odtwarza problem w naszej przykładowej aplikacji, i podaj dodatkowe czynności, które należy wykonać w interfejsie, aby wywołać wyciek.

  • zrzuty pamięci z aplikacji z odtworzonym problemem. Utwórz zrzuty stosu w 2 różnych momentach, które pokazują, że wykorzystanie pamięci znacznie wzrosło.

  • Jeśli spodziewasz się wycieku pamięci natywnych funkcji, udostępnij dane wyjściowe śledzenia alokacji z heapprofd.

  • Raport o błędzie, który został utworzony po odtworzeniu warunków wycieku.

  • zrzuty stosu dotyczące awarii związanych z pamięcią.

    Ważna uwaga: zrzuty stosu zwykle nie wystarczą do zdebugowania problemu z pamięcią, dlatego pamiętaj o przesłaniu też jednej z innych form informacji.