Android memory leak
How Proper Lifecycle Management Can Prevent Android Memory Leaks
OutOfMemoryException jest powszechnym i frustrującym błędem, a także jedną z głównych przyczyn nieoczekiwanego zamknięcia aplikacji.
„Dlaczego to się dzieje teraz, jeśli aplikacja działała idealnie wczoraj?”. Jest to pytanie, które wprawia w zakłopotanie zarówno początkujących, jak i zaawansowanych programistów Androida na całym świecie.
Istnieje wiele potencjalnych przyczyn OutOfMemory Exceptions, ale jedną z najczęstszych jest wyciek pamięci – alokacja pamięci w aplikacji, która nigdy nie zostaje zwolniona. Ten artykuł wyjaśni, jak zminimalizować to ryzyko poprzez efektywne zarządzanie cyklem życia – kluczową, ale często pomijaną część procesu rozwoju.
TL;DR
W tym artykule omówimy:
- Przyczyny wycieków pamięci w systemie Android
- Podstawowe kroki, aby temu zapobiec
- Najbardziej zaawansowane formy zarządzania cyklem życia
- Narzędzia, które mogą zarządzać cyklem życia Twojego produktu
Dlaczego wycieki pamięci RAM zdarzają się w systemie Android?
Problem jest prosty. Niektóre obiekty powinny mieć tylko określony czas życia, a kiedy ich przydatność się kończy, muszą zostać usunięte.
W teorii, ta pamięć powinna być usuwana, kiedy proces jest zabijany za pomocą onStop lub onDestroy. Jednak niewłaściwe wykorzystanie odwołań do obiektów może uniemożliwić garbage collectorowi deallocację nieużywanych obiektów. Na przykład: jeśli nieużywany obiekt A odwołuje się do nieużywanego obiektu B, skończysz z dwoma niepotrzebnymi obiektami, których śmieciarz nigdy nie usunie, ponieważ odwołują się one do siebie nawzajem.
Wspólne sztuczki, aby powstrzymać to zjawisko
Deweloperzy mogą podjąć szereg kroków, aby powstrzymać martwe działania przed uwięzieniem w pamięci.
- Rejestruj/wyrejestruj BroadcastReceivers w onResume()/onPause() lub onStart()/onStop()
- NIGDY nie używaj zmiennych statycznych dla Views/Activities/Contexts
- Singletony, które muszą trzymać referencje do Context powinny używać applicationContext() lub zawijać je w WeakReference
- Bądź ostrożny z anonimowymi i nie statycznymi klasami wewnętrznymi, ponieważ trzymają one implicite klasy wewnętrzne.statyczne klasy wewnętrzne, ponieważ przechowują one niejawne odniesienie do swojej klasy zamykającej.
- Używaj statycznych klas wewnętrznych zamiast anonimowych, jeśli mają one przetrwać klasę nadrzędną (jak Handlers).
- Jeśli wewnętrzna lub anonimowa klasa jest anulowalna (jak AsyncTask, Thread, RxSubscriptions), anuluj ją, gdy aktywność jest niszczona.
Komponenty Android Lifecycle Aware
Po wykonaniu powyższych podstawowych kroków, czas na coś znacznie ważniejszego: cykl życia aktywności aplikacji. Jeśli nie będziemy poprawnie zarządzać cyklem życia, skończymy wisząc na pamięci, gdy nie będzie już potrzebna.
Wiąże się z tym wiele różnych zadań. Dla każdej aktywności musimy przerwać wątki, pozbyć się subskrypcji w RxJava, anulować referencje AsyncTask i upewnić się, że referencja tej aktywności (i każdej innej aktywności z nią związanej) jest poprawnie usunięta. Wszystkie te zadania mogą pochłonąć ogromną ilość czasu dewelopera.
Rzeczy są jeszcze bardziej skomplikowane przez Model View Presenter (MVP), wzorzec architektoniczny często stosowany do budowy interfejsu użytkownika w systemie Android. MVP jest jednak przydatny do odłączenia logiki biznesowej od widoku.
W ramach wzorca MVP zarówno widok, jak i prezenter są abstrakcyjnymi implementacjami kontraktu na zachowanie między nimi. Najczęstszym sposobem implementacji MVP jest użycie Aktywności/Fragmentu jako implementacji dla widoku i prostej implementacji dla prezentera, który jest przyzwyczajony do posiadania referencji Widoku.
Więc kończymy mając Widok z referencją Prezentera i Prezentera z referencją Widoku (Wskazówka: Mamy tutaj potencjalny wyciek).
Zważywszy na te potencjalne trudności, istotne jest, abyśmy wprowadzili odpowiednią strukturę zarządzania, aby usunąć nadmiar pamięci utworzony podczas cyklu życia. Istnieje kilka sprawdzonych sposobów, aby to zrobić:
Creating Lifecycle-Aware Components Using Android Arch Lifecycle on Android Studio
Komponenty świadome cyklu życia są inteligentne. Mogą reagować na zmianę statusu cyklu życia innego komponentu, takiego jak aktywność lub fragmenty, na przykład poprzez pozbycie się pamięci. Oznacza to, że kod jest lżejszy i znacznie bardziej wydajny pamięciowo.
Arch Lifecycle to nowa biblioteka z Androida, która oferuje zestaw narzędzi do budowania komponentów świadomych cyklu życia. Biblioteka działa w sposób abstrakcyjny, co oznacza, że właściciele cyklu życia nie muszą się już martwić o zarządzanie cyklem życia konkretnych zadań i działań.
Kluczowe narzędzia i definicje Arch Lifecycle są następujące:
- LifeCycle: System sortowania, który definiuje, które obiekty mają cykl życia Androida, i pozwala je następnie monitorować.
- LifecycleObserver: Zwykły interfejs, który monitoruje każdy obiekt zidentyfikowany jako posiadający cykl życia systemu Android, używając prostej formuły do obsługi każdego kluczowego zdarzenia cyklu życia.
- @OnLifecycleEvent: Adnotacja, która może być użyta w klasach implementujących interfejs LifecycleObserver. Pozwala nam ona ustawić kluczowe zdarzenia cyklu życia, które będą wyzwalać adnotowaną metodę przy każdym jej uruchomieniu. Poniżej znajduje się lista wszystkich zdarzeń, które mogą być ustawione:
- ON_ANY
- ON_CREATE
- ON_DESTROY
- ON_PAUSE
- ON_RESUME
- ON_START
- ON_STOP
.
- LifecycleOwner jest domyślnie zaimplementowany dla każdego komponentu Androida, którego cyklem życia można zarządzać, i daje deweloperowi kontrolę nad każdym zdarzeniem.
Używając tych narzędzi, jesteśmy w stanie wysłać wszystkie czyste zadania do ich właścicieli (prezenterów w naszym przypadku), więc mamy czysty, odsprzężony kod wolny od przecieków (przynajmniej w warstwie prezentera).
Tutaj jest super-podstawowa implementacja, aby pokazać o czym mówimy:
interface View: MVPView, LifecycleOwnerclass RandomPresenter : Presenter<View>, LifecycleObserver { private lateinit var view: View override fun attachView(view: View) { this.view = view view.lifecycle.addObserver(this) } @OnLifecycleEvent(Lifecycle.Event.On_DESTROY) fun onClear() {//TODO: clean }
Używanie Android Arch View Models jako prezenterów i LiveData
Innym sposobem na uniknięcie wycieków pamięci przez niewłaściwe zarządzanie cyklem życia jest użycie nowych ViewModels z biblioteki Arch Components.
A ViewModel jest klasą abstrakcyjną, która implementuje pojedynczą funkcję znaną jako onClear, która jest wywoływana automatycznie, gdy dany obiekt musi zostać usunięty. ViewModel jest generowany przez framework i jest dołączony do cyklu życia twórcy (jako dodatkowy bonus, jest super łatwy do wstrzyknięcia za pomocą Dagger.)
As well as using ViewModel, LiveData also provides a vital channel of communication. Produkt podąża za reaktywnym, łatwym do obserwacji wzorcem, co oznacza, że poszczególne obiekty mogą go obserwować (tworząc mniej sprzężony kod).
Wspaniałym punktem tutaj jest to, że LiveData mogą być obserwowane przez właściciela cyklu życia, więc przekazywanie danych jest zawsze zarządzane przez cykl życia – i możemy zapewnić, że wszelkie odniesienia są zachowywane podczas korzystania z nich.
Używanie LeakCanary i Bugfender
W uzupełnieniu do wyżej wymienionych kroków, chcieliśmy polecić dwa ważne elementy zestawu: LeakCanary, popularne narzędzie do monitorowania wycieków, oraz nasz własny Bugfender.
LeakCanary to biblioteka do wykrywania pamięci dla Androida i Javy. Jest open-source, więc stoi za nią ogromna społeczność i nie tylko informuje o wycieku, ale także o jego prawdopodobnej przyczynie.
Bugfender, nasze narzędzie do zdalnego logowania, pozwala na debugowanie poszczególnych LeakTraces i rozszerza klasę o nazwie DisplayLeakService, która pozwala nam wiedzieć, kiedy wyciek jest podniesiony. Następnie możemy go łatwo rejestrować za pomocą Bugfendera.
public class LeakUploadService extends DisplayLeakService { override fun afterDefaultHandling(heapDump: HeapDump, result: AnalysisResult, leakInfo: String) { if (result.leakFound) { Bugfender.d("LeakCanary", result.toString()) } }}
Dodatkowo, użytkownicy otrzymują wszystkie inne korzyści Bugfendera, w tym rejestrowanie dziennika 24/7 (nawet gdy urządzenie jest offline), wbudowane raportowanie awarii i łatwą w użyciu konsolę internetową, która pozwala na drążenie poszczególnych urządzeń w celu lepszej obsługi klienta.
Aby dowiedzieć się więcej o Bugfenderze, kliknij tutaj.
Leave a Reply