Sprawdzone metody zarządzania pamięcią

W tym dokumencie zakładamy, że znasz sprawdzone metody dotyczące aplikacji na Androida w zakresie zarządzania pamięcią, takie 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 spowoduje awarię aplikacji. Wycieki pamięci w aplikacjach na Androida mogą być spowodowane różnymi nieprawidłowymi praktykami, takimi jak nieprawidłowe zwalnianie zasobów lub niezarejestrowywanie odbiorników, gdy nie są już potrzebne.

W tym dokumencie znajdziesz sprawdzone metody, które pomogą Ci zapobiegać wyciekom pamięci w kodzie, wykrywać je i rozwiązywać. Jeśli po wypróbowaniu metod opisanych w tym dokumencie podejrzewasz wyciek pamięci w naszych pakietach SDK, przeczytaj artykuł 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 ze sprawdzonymi metodami i wykonaj kroki debugowania opisane 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 tak się nie stanie, wygenerują informacje, których zespół pomocy Google potrzebuje, aby Ci pomóc.

Zapobieganie wyciekom pamięci

Aby uniknąć najczęstszych przyczyn wycieków pamięci w kodzie, który korzysta 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 wszystkie te czynności:

  1. Zwolnij nieużywane zasoby.
  2. Zarejestruj odbiorniki, gdy nie są już potrzebne.
  3. Anuluj zadania, gdy nie są już potrzebne.
  4. Przekaż metody cyklu życia, aby zwolnić zasoby.
  5. Używaj najnowszych wersji pakietów SDK.
  6. Podczas inicjowania unikaj blokowania wątku głównego, aby zapobiec błędom ANR.

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

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 w artykule Cykl życia aktywności.

Zwalnianie nieaktualnych odwołań do GoogleMap w pakietach GeoSDK

Częstym błędem jest to, że GoogleMap może powodować wyciek pamięci, jeśli jest buforowany za pomocą NavigationView lub MapView. GoogleMap ma relację 1:1 z NavigationView lub MapView, z którego jest pobierany. Musisz się upewnić, że GoogleMap nie jest buforowany lub że odwołanie jest zwalniane, gdy wywoływana jest funkcja NavigationView#onDestroy lub MapView#onDestroy. Jeśli używasz NavigationSupportFragment, MapSupportFragment lub własnego fragmentu, który zawiera te widoki, odwołanie musi zostać zwolnione 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
  }
}

Zarejestrowywanie odbiorników, gdy nie są już potrzebne

Gdy aplikacja na Androida zarejestruje odbiornik zdarzenia, takiego jak kliknięcie przycisku lub zmiana stanu widoku, pamiętaj, aby go wyrejestrować, gdy aplikacja nie będzie już musiała monitorować tego zdarzenia. Jeśli tego nie zrobisz, odbiorniki będą nadal zajmować pamięć nawet po zakończeniu korzystania z nich przez aplikację.

Jeśli na przykład aplikacja korzysta z pakietu Navigation SDK i wywołuje ten odbiornik, aby nasłuchiwać zdarzeń przybycia: addArrivalListener metodę, aby nasłuchiwać zdarzeń przybycia, powinna też wywołać removeArrivalListener , gdy nie będzie już musiała 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()
}

Anulowanie zadań, gdy nie są już potrzebne

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

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

Przekazywanie metod cyklu życia, aby zwolnić zasoby

Jeśli Twoja aplikacja korzysta z pakietu Navigation SDK lub Maps SDK, pamiętaj, aby zwolnić 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ć NavigationView i MapView, możesz też użyć odpowiednio 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ż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 aplikacji.

Podczas inicjowania unikaj blokowania wątku głównego, aby zapobiec błędom ANR

Gdy aplikacja blokuje wątek główny zbyt długo, może to spowodować błąd „Aplikacja nie odpowiada” (ANR). Aby zapobiec błędom ANR, metody cyklu życia, takie jak onCreate(), powinny być jak najkrótsze. Możesz to osiągnąć, odraczając długotrwałe zadania lub uruchamiając je poza wątkiem głównym.

Aby uniknąć błędów ANR związanych z inicjowaniem pakietu SDK:

  • Twórz tylko 1 instancję mapy naraz.
  • Podczas tworzenia instancji mapy ograniczaj do minimum pracę w wątku UI.

Debugowanie wycieków pamięci

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

Zanim zaczniesz, musisz wiedzieć, jak Android zarządza pamięcią. Więcej informacji znajdziesz w artykule Omówienie zarządzania pamięcią w Androidzie .

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 wykorzystanie, które wydaje się wyciekiem, nie jest w rzeczywistości pamięcią wymaganą do uruchomienia aplikacji.
  3. Debuguj na wysokim 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, Perfetto 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 je przeanalizuj.
  5. Napraw wycieki pamięci.

Więcej informacji o tych krokach znajdziesz w sekcjach poniżej.

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 od razu przejść do analizy zrzutu stosu. Jeśli jednak pobierzesz zrzut stosu podczas uruchamiania aplikacji lub w innym losowym momencie, możesz nie aktywować warunków, które spowodują wyciek. Podczas próby odtworzenia problemu rozważ różne scenariusze:

  • Jakie funkcje są aktywne?

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

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

    • Czy próbowałeś/próbowałaś wielokrotnie przechodzić przez różne stany cyklu życia?

Upewnij się, że możesz odtworzyć problem w najnowszej wersji pakietów SDK. Problem z poprzedniej wersji mógł zostać już rozwiązany.

Krok 2. Sprawdź, czy wykorzystanie pamięci przez aplikację jest zgodne z oczekiwaniami

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

  • Prawdopodobnie wyciek: aktywowanie scenariusza w wielu iteracjach powoduje wzrost wykorzystania pamięci w czasie.

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

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

Jeśli zachowanie aplikacji jest prawdopodobnie oczekiwanym wykorzystaniem 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. Debuguj na wysokim poziomie

Podczas debugowania wycieku pamięci zacznij od wysokiego poziomu, a następnie przejdź do szczegółów, gdy zawęzisz możliwości. Aby najpierw sprawdzić, czy z czasem występuje wyciek, użyj jednego z tych narzędzi do debugowania na wysokim poziomie:

Memory Profiler w Android Studio

To narzędzie wyświetla histogram zużycia pamięci. Zrzuty stosu 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 Memory Profiler w Android Studio.

Liczniki pamięci Perfetto

Perfetto umożliwia precyzyjne śledzenie kilku statystyk i przedstawia je na jednym histogramie. Więcej informacji znajdziesz w artykule Liczniki pamięci Perfetto.

Interfejs Perfetto

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

Wiele informacji, które możesz śledzić za pomocą Perfetto, jest też dostępnych jako narzędzie wiersza poleceń adb, 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 zawiera ważne zagregowane statystyki w czasie.

Kluczową statystyką, na którą należy zwrócić uwagę, jest maksymalne wykorzystanie pamięci fizycznej (maxRSS), jakiego aplikacja wymaga w czasie. MaxPSS może nie być tak dokładny. Aby zwiększyć dokładność, zapoznaj się z flagą adb shell dumpsys procstats --help –start-testing.

Śledzenie alokacji

Śledzenie alokacji identyfikuje zrzut stosu, w którym przydzielono pamięć, i sprawdza, czy nie została ona zwolniona. Ten krok jest szczególnie przydatny podczas śledzenia wycieków w kodzie natywnym. Ponieważ to narzędzie identyfikuje zrzut stosu, może być świetnym sposobem na szybkie debugowanie głównej przyczyny lub na ustalenie, jak odtworzyć problem. Aby dowiedzieć się, jak korzystać ze śledzenia alokacji, przeczytaj artykuł 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 pobranie zrzutu stosu aplikacji, a następnie sprawdzenie go pod kątem wycieków. Zrzut stosu to migawka 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ć za pomocą mechanizmu odśmiecania. Gdy przechwycisz zrzut stosu, Android Studio sprawdzi, czy istnieje aktywność lub fragment, który jest nadal dostępny, ale został już zniszczony.

  1. Przechwyć zrzut stosu.
  2. Przeanalizuj zrzut stosu, aby znaleźć wycieki pamięci.
  3. Napraw wycieki pamięci.

Więcej informacji znajdziesz w sekcjach poniżej.

Przechwytywanie zrzutu stosu

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

Przechwytywanie zrzutu stosu za pomocą adb

Aby przechwycić zrzut stosu za pomocą adb:

  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 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.

Przechwytywanie zrzutu stosu za pomocą Android Studio

Aby przechwycić zrzut stosu za pomocą Memory Profiler w Android Studio, wykonaj te czynności w sekcji Przechwytywanie zrzutu stosu w Androidzie.

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

Po przechwyceniu zrzutu stosu możesz go przeanalizować za pomocą Memory Profiler w Android Studio. Aby to zrobić:

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

  2. Kliknij Uruchom, a następnie wybierz konfigurację Debuguj.

  3. Otwórz kartę Android Profiler.

  4. Kliknij Pamięć.

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

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

    • Zidentyfikuj obiekty, które nie są już używane.

    • Zidentyfikuj obiekty, które używają dużo pamięci.

    • Sprawdź, 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 go naprawić.

Krok 5. Napraw wycieki pamięci

Gdy zidentyfikujesz źródło wycieku pamięci, możesz je naprawić. Naprawianie 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 kilka popularnych bibliotek na 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 jego debugowania. Ś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 najszybszy sposób na debugowanie.

Śledzenie alokacji ma też tę zaletę, że umożliwia udostępnianie wyników 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. Aby dowiedzieć się więcej o tym, jak używać LeakCanary w aplikacji, odwiedź stronę 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 zespołem pomocy i podaj jak najwięcej z tych informacji:

  • Kroki umożliwiające odtworzenie wycieku pamięci. Jeśli kroki wymagają złożonego kodowania, może pomóc skopiowanie kodu, który odtwarza problem, do naszej przykładowej aplikacji i podanie dodatkowych kroków, które należy wykonać w interfejsie, aby wywołać wyciek.

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

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

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

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

    Ważna uwaga: zrzuty stosu zwykle nie wystarczają do debugowania problemu z pamięcią, dlatego pamiętaj, aby podać też inne informacje.