Sprawdzone metody zarządzania pamięcią

W tym dokumencie zakładamy, że znasz sprawdzone metody zarządzania pamięcią w aplikacjach na Androida 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 niezarejestrowanie 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 kroki 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 w przypadku 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 artykule Cykl życia aktywności w dokumentacji Androida.

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 po wywołaniu 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
  }
}

Zarejestrowanie 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ć 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 rozpocznie zadanie asynchroniczne, takie jak pobieranie lub żądanie sieciowe, pamiętaj, aby anulować 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 artykule Zarządzanie pamięcią aplikacji w dokumentacji Androida.

Przekazywanie metod cyklu życia w celu zwolnienia zasobów

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 zastosowaniu 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 Zarządzanie 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 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ę. Zrób zrzut sterty i śledź alokację, a następnie przeanalizuj te dane.
  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 sterty. Jeśli jednak zrobisz zrzut sterty 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:

  • Który zestaw funkcji jest aktywny?

  • 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. Na przykład 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: pamięć jest odzyskiwana po zatrzymaniu scenariusza

  • Prawdopodobnie oczekiwane wykorzystanie pamięci: wykorzystanie pamięci wzrasta przez pewien czas, a następnie maleje. 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żytej pamięci. Z tego samego interfejsu można też wywoływać zrzuty sterty i śledzenie alokacji. 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żna ś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 sterty

Jednym ze sposobów wykrywania wycieku pamięci jest zrobienie 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 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 sterty, Android Studio sprawdzi, czy istnieje aktywność lub fragment, który jest nadal dostępny, ale został już zniszczony.

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

Więcej informacji znajdziesz w sekcjach poniżej.

Przechwytywanie zrzutu sterty

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

Przechwytywanie zrzutu sterty za pomocą adb

Aby przechwycić zrzut sterty 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 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.

Przechwytywanie zrzutu sterty za pomocą Android Studio

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

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

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

  1. Otwórz projekt 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 sterty i wybierz wygenerowany plik zrzutu sterty. Memory Profiler wyświetla wykres wykorzystania pamięci przez aplikację.

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

    • 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 zwiększyć 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, przeczytaj artykuł 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 sterty przechwycone z aplikacji, w której odtworzono problem. Przechwyć zrzuty sterty 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.