Sprawdzone metody zarządzania pamięcią

W tym dokumencie zakładamy, że stosujesz się do sprawdzonych metod dotyczących aplikacji na Androida objętych zarządzaniem pamięcią, takich jak Zarządzanie pamięcią aplikacji.

Wprowadzenie

Nieszczelność pamięci to rodzaj nieszczelności zasobów, który występuje, gdy program komputerowy nie zwalnia już niepotrzebnej pamięci. 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.

Ten dokument zawiera kilka sprawdzonych metod, które pomagają zapobiegać wyciekom pamięci w kodzie, wykrywać je i rozwiązywać. 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 instrukcjami debugowania podanymi w tym dokumencie, aby upewnić się, że błąd nie występuje w Twoim kodzie. Te czynności mogą rozwiązać problem, a jeśli to nie zadziałają, wygenerują informacje potrzebne zespołowi pomocy Google.

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 wykonałeś/wykonałaś wszystkie te czynności:

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

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

Zwalnianie nieużywanych zasobów

Jeśli Twoja aplikacja na Androida korzysta z zasobu, pamiętaj, by 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 artykule Cykl życia aktywności w dokumentacji Androida.

Publikowanie nieaktualnych odwołań do GoogleMap w pakietach GeoSDK

Częstym błędem jest to, że obiekt GoogleMap może powodować wyciek pamięci, jeśli jest buforowany za pomocą interfejsu NavigationView lub MapView. Mapa GoogleMap ma relację 1:1 z obiektem NavigationView lub MapView, z którego jest pobierana. 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 do zawijania tych widoków, odniesienie musi być udostępnione we fragmencie#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
  }
}

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, detektory będą nadal zajmować pamięć nawet po zakończeniu działania aplikacji.

Załóżmy na przykład, że Twoja aplikacja korzysta z pakietu Navigation SDK i wywołuje ten detektor w celu nasłuchiwania zdarzeń przyjazdu: addArrivalListener w celu nasłuchiwania zdarzeń przyjazdu, powinna także wywoływać metodę removeArrivalListener, gdy nie musi już monitorować zdarzeń przyjazdu.

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 używa pakietu SDK usługi Navigation lub Maps, zwolnij zasoby, przekazując metody cyklu życia (pogrubione) do usługi navView. Możesz to zrobić, używając funkcji NavigationView w Navigation SDK lub MapView w Mapach lub Navigation SDK. 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()
  */
}

Używaj najnowszych wersji pakietów SDK

Pakiety SDK Google są stale aktualizowane o nowe funkcje, poprawki błędów i ulepszenia wydajności. Pamiętaj o aktualizowaniu pakietów SDK w aplikacji, aby korzystać z tych poprawek.

Debugowanie wycieków pamięci

Jeśli po wdrożeniu wszystkich sugestii podanych wcześniej w tym dokumencie nadal występują wycieki pamięci, wykonaj te czynności, aby debugować.

Zanim zaczniesz, musisz wiedzieć, 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:

  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. Debuguj na poziomie ogólnym. Do debugowania możesz użyć kilku narzędzi. Trzy różne standardowe zestawy narzędzi pomagają debugować problemy z pamięcią w Androidzie: narzędzia wiersza poleceń Android Studio, Perfetto i 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, przejdź od razu do zrzutu stosu. 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. Aby odtworzyć problem, przetestuj różne scenariusze:

  • 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 wypróbowałeś/wypróbowałaś kilka iteracji przez różne stany cyklu życia?

Sprawdź, czy możesz odtworzyć problem w najnowszej wersji pakietów SDK. Być może w poprzedniej wersji został 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 zgodne z oczekiwaniami, czy też jest to wyciek pamięci. Na przykład w przypadku różnych funkcji lub zadań użytkownika weź pod uwagę te możliwości:

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

  • Prawdopodobny oczekiwany poziom wykorzystania 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 działanie aplikacji jest najprawdopodobniej oczekiwane przy wykorzystaniu pamięci, problem można rozwiązać, zarządzając pamięcią aplikacji. Jeśli potrzebujesz pomocy, zobacz Zarządzanie pamięcią aplikacji.

Krok 3. Debugowanie na wysokim poziomie

Debugowanie wycieku pamięci zaczyna się od poziomu ogólnego, a potem przejdź do bardziej szczegółowego widoku, gdy sprecyzujesz możliwości. Użyj jednego z tych ogólnych narzędzi do debugowania, aby najpierw przeanalizować, czy z czasem nie wyciek danych:

Program profilujący pamięci Android Studio

Jest to graficzny histogram wykorzystywanej pamięci. W tym samym interfejsie można też wywoływać 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 rodzajów danych i prezentuje je w jednym histogramie. Więcej informacji znajdziesz w artykule o licznikach pamięci Perfetto.

Interfejs Perfetto

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

Wiele z tych informacji możesz śledzić za pomocą narzędzia adb wiersza poleceń, 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.

  • Raport Procstats dostarcza na przestrzeni czasu ważne zbiorcze statystyki.

Istotną statystyką, którą należy wziąć pod uwagę, jest maksymalny rozmiar pamięci fizycznej (maxRSS), którego aplikacja wymaga w miarę upływu czasu. Wartość MaxPSS może nie być tak 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 czy nie została ona zwolniona. Ten krok jest szczególnie przydatny przy śledzeniu wycieków w kodzie natywnym. 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 korzystania ze śledzenia alokacji znajdziesz w artykule Debugowanie pamięci w kodzie natywnym ze śledzeniem 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 stosu 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 wykrywać wycieki pamięci, których nie można naprawić przez 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. Zarejestruj zrzut stosu.
  2. Przeanalizuj zrzut stosu, aby znaleźć wycieki pamięci.
  3. Napraw wycieki pamięci

Szczegółowe informacje znajdziesz w kolejnych sekcjach.

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.

Używanie narzędzia adb do przechwytywania 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 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.

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 zrobieniu zrzutu stosu możesz go przeanalizować za pomocą Profilera pamięci w Android Studio. W tym celu wykonaj następujące czynności:

  1. Otwórz projekt na Androida w Android Studio.

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

  3. Otwórz kartę Android Profiler.

  4. Wybierz Pamięć.

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

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

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

    • Identyfikuj obiekty, które zużywają dużo pamięci.

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

  7. Na podstawie tych informacji możesz zawęzić zakres lub znaleźć źródło wycieku pamięci i go rozwiązać.

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 różnią się w zależności od sytuacji. Pomocne mogą być jednak te sugestie:

Inne narzędzia do debugowania

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

Debuguj pamięć w kodzie natywnym ze śledzeniem alokacji

Nawet jeśli nie korzystasz bezpośrednio z kodu natywnego, możesz użyć kilku popularnych bibliotek Androida, w tym pakietów SDK Google. Jeśli uważasz, że wyciek pamięci jest spowodowany kodem natywnym, możesz skorzystać z kilku narzędzi, aby rozwiązać ten problem. Śledzenie alokacji za pomocą Android Studio lub heapprofd (również zgodnego z Perfetto) to świetny sposób na identyfikowanie potencjalnych przyczyn wycieku pamięci i często najszybszy sposób debugowania.

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

Identyfikuj wycieki za pomocą aplikacji LeakCanary

LeakCanary to zaawansowane narzędzie do wykrywania wycieków pamięci w aplikacjach na Androida. Aby dowiedzieć się więcej o tym, jak używać LeakCanary w swojej aplikacji, wejdź na stronę LeakCanary.

Jak zgłaszać problemy z pakietami SDK Google

Jeśli po wypróbowaniu metod opisanych w tym dokumencie 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:

  • Etapy odtwarzania wycieku pamięci. Jeśli te czynności wymagają złożonego kodowania, pomocne może być skopiowanie kodu, który odtwarza problem, do naszej przykładowej aplikacji i dostarczenie w interfejsie dodatkowych czynności, które trzeba wykonać w interfejsie, aby aktywować wyciek.

  • zrzuty stosu z aplikacji z odtworzonym problemem. Zapisz zrzuty stosu w 2 różnych punktach w czasie, które pokazują, że znacznie zwiększyło się wykorzystanie pamięci.

  • Jeśli spodziewany jest wyciek pamięci natywnej, udostępnij dane wyjściowe śledzenia alokacji z usługi heapprofd.

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

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

    Ważna uwaga: zrzuty stosu zwykle nie są wystarczające do debugowania problemu z pamięcią, więc pamiętaj, by podać też jedną z pozostałych form informacji.