Sprawdzone metody zarządzania pamięcią

W tym dokumencie zakładamy, że postępujesz zgodnie ze sprawdzonymi metodami dotyczącymi aplikacji na Androida w zakresie zarządzania pamięcią, takimi jak 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ó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 jej awarii. W aplikacjach na Androida może dochodzić do wycieków pamięci z powodu wielu nieprawidłowych praktyk, takich jak nieprawidłowe zwalnianie zasobów czy niezarejestrowywanie odbiorników, gdy nie są już potrzebne.

W tym dokumencie znajdziesz sprawdzone metody, które pomogą Ci zapobiegać przeciekom pamięci w kodzie, wykrywać je i rozwiązywać związane z nimi problemy. Jeśli po wypróbowaniu metod opisanych w tym dokumencie podejrzewasz wyciek pamięci w naszych pakietach SDK, zapoznaj się z artykułem Jak zgłaszać problemy z pakietami SDK Google.

Zanim skontaktujesz się z zespołem pomocy

Zanim zgłosisz wyciek pamięci zespołowi pomocy Google, postępuj zgodnie z podanymi w tym dokumencie sprawdzonymi metodami i krokami debugowania, aby upewnić się, że błąd nie występuje w Twoim kodzie. Te czynności mogą rozwiązać problem, a jeśli nie, wygenerują informacje, których potrzebuje zespół pomocy Google, aby Ci pomóc.

Zapobieganie wyciekom pamięci

Aby uniknąć najczęstszych przyczyn wycieków pamięci w kodzie korzystającym z pakietów SDK Google, postępuj zgodnie z tymi sprawdzonymi metodami.

Sprawdzone metody dotyczące aplikacji na Androida

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

  1. Zwolnij nieużywane zasoby.
  2. Wyłączaj odbiorniki, gdy nie są już potrzebne.
  3. Anuluj zadania, gdy nie są już potrzebne.
  4. Przekazywanie metod cyklu życia w celu zwalniania zasobów
  5. Używaj najnowszych wersji pakietów SDK

Szczegółowe informacje o każdej z tych praktyk znajdziesz w sekcjach poniżej.

Zwalnianie nieużywanych zasobów

Gdy aplikacja na Androida korzysta z 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 artykule Cykl życia aktywności w dokumentacji Androida.

Zwalnianie 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ą komponentu NavigationView lub MapView. Obiekt GoogleMap jest powiązany w relacji 1:1 z obiektem NavigationView lub MapView, z którego został pobrany. Musisz zadbać o to, aby obiekt GoogleMap nie był przechowywany w pamięci podręcznej lub aby odwołanie do niego zostało zwolnione po wywołaniu metody NavigationView#onDestroy lub MapView#onDestroy. Jeśli używasz fragmentu NavigationSupportFragment, MapSupportFragment lub własnego fragmentu, który zawiera te widoki, odwołanie musi zostać zwolnione w metodzie 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
  }
}

Wyłączanie odbiorników, gdy nie są już potrzebne

Gdy aplikacja na Androida rejestruje odbiornik zdarzenia, takiego jak kliknięcie przycisku lub zmiana stanu widoku, pamiętaj, aby wyrejestrować odbiornik, gdy aplikacja nie musi już monitorować zdarzenia. Jeśli tego nie zrobisz, odbiorniki 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, aby nasłuchiwać zdarzeń przybycia:addArrivalListener. Jeśli aplikacja wywołuje metodę removeArrivalListener, aby nasłuchiwać zdarzeń przybycia, 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()
}

Anulowanie zadań, gdy nie są już potrzebne

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

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

Przekazywanie metod cyklu życia w celu zwalniania zasobów

Jeśli Twoja aplikacja korzysta z pakietu Navigation SDK lub Maps SDK, zwolnij zasoby, przekazując metody cyklu życia (wytłuszczone) do navView. Możesz to zrobić za pomocą NavigationView w pakiecie Navigation SDK lub MapView w pakiecie Maps SDK lub Navigation SDK. Zamiast bezpośrednio używać NavigationViewMapView, możesz też użyć SupportNavigationFragment lub SupportMapFragment. 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, aby zawierały 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 odpowiednich sugestii podanych wcześniej w tym dokumencie nadal występują wycieki pamięci, wykonaj te czynności, aby rozwiązać problem.

Zanim zaczniesz, zapoznaj się z informacjami o tym, jak Android zarządza pamięcią. Więcej informacji znajdziesz w artykule Omówienie zarządzania pamięcią na stronie Androida.

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

  1. Odtwórz problem. Ten krok jest niezbędny do debugowania.
  2. Sprawdź, czy wykorzystanie pamięci jest zgodne z oczekiwaniami. Sprawdź, czy zwiększone zużycie, które wygląda na wyciek, nie jest w rzeczywistości pamięcią wymaganą do uruchomienia aplikacji.
  3. Debugowanie na wysokim poziomie Do debugowania możesz użyć kilku narzędzi. Do debugowania problemów z pamięcią na Androidzie służą 3 różne standardowe zestawy narzędzi: Android Studio, Perfetto i narzędzia wiersza poleceń Android Debug Bridge (adb).
  4. Sprawdź wykorzystanie pamięci przez aplikację Pobierz zrzut sterty i śledzenie alokacji, a następnie go przeanalizuj.
  5. Naprawianie wycieków pamięci

W kolejnych sekcjach szczegółowo opisujemy te czynności.

Krok 1. Wykonaj czynności prowadzące do wystąpienia problemu

Jeśli nie udało Ci się odtworzyć problemu, najpierw rozważ scenariusze, które mogą prowadzić do wycieku pamięci. Bezpośrednie sprawdzenie zrzutu pamięci może się sprawdzić, jeśli wiesz, że problem został odtworzony. Jeśli jednak zrzut sterty zostanie utworzony podczas uruchamiania aplikacji lub w innym losowym momencie, warunki wywołujące wyciek mogły nie zostać aktywowane. Podczas próby odtworzenia problemu rozważ różne scenariusze:

  • Jakie funkcje są aktywowane?

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

    • Czy próbowano wielokrotnie aktywować tę sekwencję?
  • Jakie stany cyklu życia przeszła aplikacja?

    • Czy przeprowadzono wiele iteracji w różnych stanach cyklu życia?

Sprawdź, czy problem występuje w najnowszej wersji pakietów SDK. Problem z poprzedniej wersji mógł już zostać 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 może to być oczekiwane użycie, czy też jest to wyciek pamięci. Na przykład w przypadku różnych funkcji lub zadań użytkownika rozważ następujące możliwości:

  • Prawdopodobnie wyciek: aktywowanie scenariusza w wielu iteracjach powoduje wzrost wykorzystania pamięci z upływem czasu.

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

  • Prawdopodobnie oczekiwane wykorzystanie pamięci: wykorzystanie pamięci rośnie przez pewien czas, a potem maleje. Może to być spowodowane ograniczonym rozmiarem pamięci podręcznej lub innym oczekiwanym wykorzystaniem pamięci.

Jeśli zachowanie aplikacji jest zgodne z oczekiwanym zuż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 wysokiego poziomu, a potem przejdź do szczegółów, gdy zawęzisz możliwości. Użyj jednego z tych narzędzi do debugowania wysokiego poziomu, aby najpierw sprawdzić, czy z czasem występuje wyciek pamięci:

Android Studio Memory Profiler

To narzędzie wyświetla histogram zużycia pamięci. Zrzuty sterty i śledzenie alokacji można też wywoływać z tego samego interfejsu. To narzędzie jest zalecane domyślnie. Więcej informacji znajdziesz w artykule Android Studio Memory Profiler.

Liczniki pamięci Perfetto

Perfetto umożliwia precyzyjne śledzenie kilku rodzajów danych i wyświetlanie ich w postaci jednego histogramu. Więcej informacji znajdziesz w artykule o licznikach pamięci Perfetto.

Interfejs Perfetto

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

Większość informacji, które możesz śledzić za pomocą Perfetto, jest też dostępna jako adbnarzędzie wiersza poleceń, o które możesz bezpośrednio wysyłać zapytania. Oto kilka ważnych przykładów:

  • Meminfo umożliwia wyświetlanie szczegółowych informacji o pamięci w danym momencie.

  • Procstats udostępnia ważne zagregowane statystyki z upływem czasu.

Kluczową statystyką, na którą należy zwrócić uwagę, jest maksymalny rozmiar pamięci fizycznej (maxRSS), jakiego aplikacja wymaga z biegiem czasu. Wartość MaxPSS może być mniej dokładna. Aby zwiększyć dokładność, użyj flagi adb shell dumpsys procstats --help –start-testing.

Śledzenie alokacji

Śledzenie przydzielania identyfikuje ślad stosu, w którym przydzielono pamięć, i sprawdza, czy została ona zwolniona. Ten krok jest szczególnie przydatny podczas śledzenia wycieków w kodzie natywnym. Narzędzie to identyfikuje ślad stosu, więc może być świetnym sposobem na szybkie debugowanie głównej przyczyny problemu lub ustalenie, jak go odtworzyć. 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 sterty

Jednym ze sposobów wykrywania wycieków pamięci jest uzyskanie zrzutu sterty aplikacji, a następnie sprawdzenie go pod kątem wycieków. Zrzut sterty to migawka wszystkich obiektów w pamięci aplikacji. Może służyć do diagnozowania wycieków pamięci i innych problemów z pamięcią.

Android Studio może wykrywać wycieki pamięci, których nie można naprawić za pomocą GC. Gdy przechwycisz zrzut sterty, Android Studio sprawdzi, czy jest aktywność lub fragment, który jest nadal osiągalny, ale został już zniszczony.

  1. Zrób zrzut stosu.
  2. Analizowanie zrzutu sterty w celu wykrycia wycieków pamięci
  3. Naprawianie wycieków pamięci

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

Przechwytywanie zrzutu sterty

Aby zapisać zrzut stosu, możesz użyć Android Debug Bridge (adb) lub narzędzia Memory Profiler w Android Studio.

Używanie adb do przechwytywania zrzutu sterty

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

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

    adb shell am dumpheap my.app.name $PHONE_FILE_OUT

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

    adb pull $PHONE_FILE_OUT $LOCAL_FILE.

Zapisywanie zrzutu stosu za pomocą Androida Studio

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

Analizowanie zrzutu sterty w celu wykrycia wycieków pamięci

Po zapisaniu 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 Androida w Android Studio.

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

  3. Otwórz kartę Android Profiler.

  4. Kliknij Pamięć.

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

  6. Aby przeanalizować zrzut sterty za pomocą wykresu:

    • identyfikować obiekty, które nie są już używane;

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

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

  7. Skorzystaj z tych informacji, aby zawęzić lub znaleźć źródło wycieku pamięci i go naprawić.

Krok 5. Napraw wycieki pamięci

Gdy zidentyfikujesz źródło wycieku pamięci, możesz je naprawić. Naprawienie wycieków pamięci w aplikacjach na Androida pomaga poprawić ich wydajność i stabilność. Szczegóły różnią się w zależności 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 naprawić wycieku pamięci, wypróbuj te narzędzia:

Debugowanie pamięci w kodzie natywnym za pomocą śledzenia alokacji

Nawet jeśli nie używasz bezpośrednio kodu natywnego, robi to wiele popularnych bibliotek Androida, w tym pakiety SDK Google. 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 i często najszybszy sposób na debugowanie.

Śledzenie alokacji ma też tę zaletę, że pozwala udostępniać wyniki bez uwzględniania informacji poufnych, które można znaleźć w stogu.

Wykrywanie wycieków za pomocą LeakCanary

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

Jak zgłaszać problemy z pakietami SDK Google

Jeśli po wypróbowaniu metod opisanych w tym dokumencie podejrzewasz wyciek pamięci w naszych pakietach SDK, skontaktuj się z obsługą klienta i podaj jak najwięcej z tych informacji:

  • Czynności umożliwiające odtworzenie wycieku pamięci Jeśli czynności wymagają skomplikowanego kodowania, możesz skopiować kod, który odtwarza problem, do naszej aplikacji przykładowej i podać dodatkowe czynności, które należy wykonać w interfejsie, aby wywołać wyciek.

  • Zrzuty sterty z aplikacji, w której odtworzono problem. Zrób 2 zrzuty sterty w różnych momentach, które pokazują, że wykorzystanie pamięci znacznie wzrosło.

  • Jeśli spodziewasz się wycieku pamięci natywnej, udostępnij dane śledzenia przydziału z narzędzia heapprofd.

  • Raport o błędzie utworzony po odtworzeniu warunków wycieku.

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

    Ważna uwaga: zrzuty stosu zwykle nie wystarczają do debugowania problemu z pamięcią, więc podaj też informacje w innej formie.