Sprawdzone metody zarządzania pamięcią

W tym dokumencie zakładamy, że stosujesz sprawdzone metody dotyczące aplikacji na Androida objętych zarządzaniem pamięcią, np. Zarządzanie pamięcią aplikacji.

Wstęp

Wyciek pamięci to typ wycieku zasobów, który występuje, gdy program komputerowy nie zwalnia przydzielonej pamięci, która nie jest już potrzebna. Wyciek może spowodować, że aplikacja zażąda od systemu operacyjnego więcej pamięci, niż jest dostępna, co doprowadzi do awarii aplikacji. Wyciek pamięci w aplikacjach na Androida może być spowodowany wieloma nieprawidłowymi praktykami, takimi jak nieprawidłowe pozbycie się zasobów lub wyrejestrowanie detektorów, gdy nie są już potrzebne.

Ten dokument zawiera sprawdzone metody zapobiegania wyciekom pamięci w kodzie, wykrywania ich i rozwiązywania problemów. Jeśli za pomocą metod opisanych w tym dokumencie podejrzewasz wyciek pamięci w naszych pakietach SDK, przeczytaj artykuł Zgłaszanie problemów z pakietami SDK Google.

Zanim skontaktujesz się z zespołem pomocy

Zanim zgłosisz wyciek pamięci zespołowi pomocy Google, postępuj zgodnie ze sprawdzonymi metodami i instrukcjami debugowania przedstawionymi w tym dokumencie, aby upewnić się, że błąd nie znajduje się w kodzie. Te czynności mogą pomóc w rozwiązaniu problemu. Jeśli tak nie, zostaną wygenerowane informacje potrzebne zespołowi pomocy Google.

Zapobieganie wyciekom pamięci

Postępuj zgodnie z tymi sprawdzonymi metodami, aby uniknąć niektórych najczęstszych przyczyn wycieków pamięci w kodzie, który korzysta z pakietów SDK Google.

Sprawdzone metody dotyczące aplikacji na Androida

Sprawdź, czy w aplikacji na Androida zostały wykonane wszystkie te czynności:

  1. Zwalnianie nieużywanych zasobów
  2. Wyrejestruj detektory, które nie są już potrzebne.
  3. Anuluj niepotrzebne zadania.
  4. Przekieruj metody cyklu życia do zwolnienia zasobów.
  5. Korzystanie z najnowszych wersji pakietów SDK

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

Zwolnij nieużywane zasoby

Gdy aplikacja na Androida korzysta z zasobu, pamiętaj, by go zwolnić, gdy nie będzie już potrzebny. Jeśli tego nie zrobisz, zasób będzie nadal zajmować pamięć nawet po zakończeniu działania aplikacji. Więcej informacji znajdziesz w sekcji Cykl życia aktywności w dokumentacji Androida.

Opublikuj nieaktualne odwołania do Map Google w pakietach GeoSDK

Typowy błąd polega na tym, że mapa Google może spowodować wyciek pamięci, jeśli jest przechowywana w pamięci podręcznej za pomocą funkcji NavigationView lub MapView. Obiekt GoogleMap ma relację 1 do 1 z obiektem NavigationView lub MapView, z którego jest pobierana. Musisz się upewnić, że mapa Google nie jest przechowywana w pamięci podręcznej lub aby odwołanie zostało zwolnione po wywołaniu elementu NavigationView#onDestroy lub MapView#onDestroy. Jeśli używasz interfejsu NavigationSupportFragment, MapSupportFragment lub własnego fragmentu zawierającego te widoki, musisz opublikować odwołanie w elemencie 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
  }
}

Wyrejestruj detektory, które nie są już potrzebne

Gdy aplikacja na Androida zarejestruje detektor zdarzenia, np. kliknięcie przycisku lub zmianę stanu widoku, pamiętaj, aby wyrejestrować detektor, gdy aplikacja przestanie monitorować zdarzenie. Jeśli tego nie zrobisz, detektory będą nadal zajmować pamięć nawet po zakończeniu aplikacji.

Załóżmy np., że aplikacja korzysta z pakietu SDK nawigacji i wywołuje następujący detektor w celu nasłuchiwania zdarzeń przychodzących: addArrivalListener w celu nasłuchiwania zdarzeń przychodzących, powinna też wywoływać metodę removeArrivalListener, gdy nie trzeba już monitorować tych zdarzeń.

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()
}

Anuluj zadania, gdy nie są potrzebne

Gdy aplikacja na Androida rozpoczyna zadanie asynchroniczne, np. pobieranie lub żądanie sieciowe, pamiętaj, aby je anulować po jego zakończeniu. Jeśli zadanie nie zostanie anulowane, będzie ono działać w tle nawet po zakończeniu ich wykonywania przez aplikację.

Więcej informacji o sprawdzonych metodach znajdziesz w sekcji Zarządzanie pamięcią aplikacji w dokumentacji Androida.

Przekieruj metody cyklu życia do zwolnienia zasobów

Jeśli Twoja aplikacja korzysta z pakietu SDK do nawigacji lub Map, udostępnij je, przekazując do systemu navView metody cyklu życia (wyróżnione pogrubieniem). Możesz to zrobić, korzystając z NavigationView w pakiecie SDK nawigacji, albo MapView w pakiecie SDK Map lub Nawigacji. Możesz też użyć właściwości SupportNavigationFragment lub SupportMapFragment zamiast bezpośrednio używać NavigationView i MapView. Fragmenty pomocy 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()
  */
}

Korzystaj z najnowszych wersji pakietów SDK

Pakiety SDK Google są na bieżąco aktualizowane o nowe funkcje, poprawki błędów i ulepszenia wydajności. Aktualizuj pakiety SDK w aplikacji, aby uniknąć tych poprawek.

Debugowanie wycieków pamięci

Jeśli po zastosowaniu wszystkich odpowiednich sugestii podanych wcześniej w tym dokumencie nadal widzisz wycieki pamięci, wykonaj te czynności, aby debugować.

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

Aby debugować wycieki pamięci, wykonaj te czynności:

  1. Odtwórz problem. Ten krok jest niezbędny do jego debugowania.
  2. Sprawdź, czy użycie pamięci jest zgodne z oczekiwaniami. Sprawdź, czy zwiększone wykorzystanie, które wygląda na wyciek, nie jest w rzeczywistości ilością pamięci niezbędnej do uruchomienia aplikacji.
  3. Debugowanie ogólne. Jest kilka narzędzi, których możesz użyć do debugowania. Trzy różne zestawy standardowych narzędzi pomagają w debugowaniu problemów z pamięcią na Androidzie: Android Studio, Perfetto i Android Debug Bridge (adb) w wierszu poleceń.
  4. Sprawdź wykorzystanie pamięci przez aplikację Pobierz zrzut stosu i śledzenie alokacji, a następnie przeanalizuj je.
  5. Rozwiązywanie problemów z wyciekami pamięci

W sekcjach poniżej znajdziesz szczegółowe informacje na ten temat.

Krok 1. Odtwórz problem

Jeśli nie udało Ci się odtworzyć problemu, najpierw rozważ scenariusze, które mogą doprowadzić do wycieku pamięci. Zajrzyj od razu do zrzutu stosu, jeśli wiesz, że problem został odtworzony. Jeśli jednak przy uruchomieniu aplikacji lub w innym przypadkowym punkcie w czasie pojawi się zrzut stosu, może to oznaczać, że warunki nie zostały aktywowane przez wyciek. Próbując odtworzyć problem, zastanów się nad różnymi scenariuszami:

  • Jaki zestaw funkcji jest aktywowany?

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

    • Czy próbowałeś/próbowałaś kilka razy aktywować tę sekwencję?
  • Przez jaki stan cyklu życia zmieniła się aplikacja?

    • Czy wypróbowałeś(-aś) wiele iteracji w różnych stanach cyklu życia?

Upewnij się, że możesz odtworzyć problem w najnowszych wersjach pakietów SDK. Być może problem z poprzedniej wersji został już rozwiązany.

Krok 2. Sprawdź, czy aplikacja wykorzystuje pamięć używaną przez aplikację

Każda funkcja wymaga dodatkowej pamięci. Podczas debugowania różnych scenariuszy zastanów się, czy jest to oczekiwane użycie, czy też faktycznie wynika to z wycieku pamięci. Na przykład w przypadku różnych funkcji lub zadań użytkownika weź pod uwagę te możliwości:

  • Prawdopodobnie wyciek: aktywacja scenariusza za pomocą wielu iteracji skutkuje z czasem wzrostu wykorzystania pamięci.

  • Prawdopodobnie oczekiwane wykorzystanie pamięci: pamięć jest odzyskiwana po zatrzymaniu scenariusza.

  • Możliwe, że oczekiwane wykorzystanie pamięci: wykorzystanie pamięci przez pewien czas rośnie, a potem maleje. Może to być spowodowane ograniczoną pamięcią podręczną lub innym oczekiwanym wykorzystaniem.

Jeśli działanie aplikacji wskazuje prawdopodobnie na wykorzystanie pamięci, problem można rozwiązać, zarządzając jej pamięcią. Więcej informacji znajdziesz w artykule Zarządzanie pamięcią aplikacji.

Krok 3. Przeprowadź debugowanie na poziomie ogólnym

Debugowanie wycieku pamięci zacznij od wysokiego poziomu, a potem przejdź do bardziej szczegółowego widoku, gdy wykluczysz jakieś możliwości. Skorzystaj z jednego z tych zaawansowanych narzędzi do debugowania, aby najpierw przeanalizować, czy z czasem doszło do wycieku:

Program profilujący pamięci Android Studio

To narzędzie generuje histogram przedstawiający konsumpcję pamięci. W tym samym interfejsie można również uruchamiać zrzuty sterty i śledzenie alokacji. To narzędzie jest domyślną rekomendacją. Więcej informacji znajdziesz w opisie narzędzia do profilowania pamięci w Android Studio.

Liczniki pamięci Perfetto

Perfetto zapewnia precyzyjną kontrolę nad śledzeniem kilku wskaźników i przedstawia je na jednym histogramie. Więcej informacji znajdziesz w artykule o licznikach pamięci Perfetto.

Interfejs Perfetto

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

Wiele danych, które można śledzić w Perfetto, jest również dostępnych jako narzędzie wiersza poleceń adb, do którego można bezpośrednio wysyłać zapytania. Oto kilka ważnych przykładów:

  • Meminfo pozwala wyświetlić szczegółowe informacje o pamięci z określonych miejsc.

  • Narzędzie Procstats zawiera ważne statystyki zbiorcze na przestrzeni czasu.

Kluczowym wskaźnikiem, na który należy zwrócić uwagę, jest maksymalny rozmiar pamięci fizycznej (maxRSS), którego aplikacja potrzebuje z upływem czasu. Wartość MaxPSS może nie być tak dokładna. Informacje o tym, jak zwiększyć dokładność, znajdziesz w flagi adb shell dumpsys procstats --help –start-testing.

Śledzenie alokacji

Śledzenie alokacji identyfikuje zrzut stosu, do którego przydzielono pamięć i czy nie została ona uwolniona. Ten krok jest szczególnie przydatny przy śledzeniu wycieków kodu natywnego. To narzędzie wykrywa zrzut stosu, więc może być świetnym sposobem na szybkie zdebugowanie głównej przyczyny lub znalezienie sposobu na odtworzenie problemu. Instrukcje korzystania ze ś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 wykrywania wycieku pamięci jest wykonanie zrzutu stosu aplikacji i sprawdzenie jej pod kątem wycieków. Zrzut sterty to zrzut wszystkich obiektów w pamięci aplikacji. Może służyć do diagnozowania wycieków pamięci i innych problemów związanych z pamięcią.

Android Studio może wykryć wycieki pamięci, których nie można naprawić przez GC. Po przechwyceniu zrzutu stosu Android Studio sprawdza, czy istnieje działanie lub fragment, które są wciąż osiągalne, ale zostały już zniszczone.

  1. Zrób zrzut stosu.
  2. Przeanalizuj zrzut stosu, aby znaleźć wycieki pamięci.
  3. Rozwiązywanie problemów z wyciekami pamięci

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

Zrób zrzut stosu

Aby przechwycić zrzut stosu, możesz użyć Android Debug Bridge (adb) lub Android Studio Memory Profiler.

Używanie narzędzia adb do przechwytywania zrzutu stosu

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

  1. Podłącz urządzenie z Androidem do komputera.
  2. W wierszu polecenia przejdź do katalogu, w którym znajdują się narzędzia adb.
  3. Aby przechwycić 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.

Użyj Android Studio, aby zapisać zrzut stosu

Aby przechwycić zrzut stosu za pomocą narzędzia do profilowania pamięci w Android Studio, wykonaj czynności opisane w sekcji Zapisuj zrzut stosu na Androidzie.

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

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

  1. Otwórz projekt w Android Studio.

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

  3. Otwórz kartę Program profilujący Androida.

  4. Kliknij Pamięć.

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

  6. Użyj wykresu, aby przeanalizować zrzut stosu:

    • Identyfikowanie obiektów, które nie są już używane.

    • Zidentyfikuj obiekty, które wykorzystują dużą ilość pamięci.

    • Sprawdź, ile pamięci wykorzystują poszczególne obiekty.

  7. Użyj tych informacji, aby zidentyfikować źródło wycieku pamięci i go rozwiązać.

Krok 5. Rozwiąż problem z wyciekami pamięci

Po zidentyfikowaniu źródła wycieku pamięci możesz go naprawić. Naprawienie wycieków pamięci w aplikacjach na Androida pomaga poprawić ich wydajność i stabilność. Szczegóły mogą się różnić w zależności od sytuacji. Oto kilka wskazówek, które mogą okazać się pomocne:

Inne narzędzia do debugowania

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

Debuguj pamięć w kodzie natywnym za pomocą śledzenia alokacji

Nawet jeśli nie używasz bezpośrednio kodu natywnego, możesz użyć kilku typowych bibliotek Androida, m.in. pakietów Google SDK. Jeśli uważasz, że wyciek pamięci znajduje się w kodzie natywnym, do jego debugowania możesz użyć kilku narzędzi. Śledzenie alokacji za pomocą Android Studio lub heapprofd (zgodnego też z Perfetto) to świetny sposób na identyfikowanie potencjalnych przyczyn wycieku pamięci i często jest najszybszym sposobem na debugowanie.

Inną zaletą śledzenia alokacji jest to, że można udostępniać wyniki bez uwzględniania informacji poufnych, które można znaleźć w stercie.

Identyfikowanie wycieków za pomocą LeakCanary

LeakCanary to zaawansowane narzędzie do identyfikowania wycieków pamięci w aplikacjach na Androida. Więcej informacji o korzystaniu z LeakCanary w aplikacji znajdziesz na stronie LeakCanary.

Zgłaszanie problemów z pakietami SDK Google

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

  • Kroki umożliwiające odtworzenie wycieku pamięci. Jeśli kroki wymagają złożonego kodowania, warto skopiować do przykładowej aplikacji kod, który replikuje problem, i podać dodatkowe czynności, które trzeba wykonać w interfejsie, żeby aktywować wyciek danych.

  • Zrzuty sterty zarejestrowane w aplikacji, w których odtworzony problem. Możesz rejestrować zrzuty stosu w 2 różnych momentach w czasie, które pokazują, że wykorzystanie pamięci znacznie wzrosło.

  • Jeśli spodziewany jest wyciek pamięci natywnej, udostępnij wynik śledzenia alokacji z heapprofd.

  • Raport o błędzie wygenerowany po odtworzeniu stanu wycieku.

  • Zrzuty stosu wszelkich awarii związanych z pamięcią.

    Ważna uwaga: zrzuty stosu same w sobie zwykle nie wystarczają do debugowania problemu z pamięcią, dlatego pamiętaj, by podać też inne rodzaje informacji.