Czytałem kilka artykułów na temat wycieków pamięci w Androidzie i oglądałem ten interesujący film od Google I / O na ten temat .
Mimo to nie do końca rozumiem tę koncepcję, zwłaszcza gdy jest ona bezpieczna lub niebezpieczna dla użytkowników wewnętrznych klas wewnątrz działania .
Oto co zrozumiałem:
Wyciek pamięci nastąpi, jeśli instancja klasy wewnętrznej przetrwa dłużej niż jej klasa zewnętrzna (działanie). -> W jakich sytuacjach może się to zdarzyć?
W tym przykładzie przypuszczam, że nie ma ryzyka wycieku, ponieważ nie ma możliwości, aby rozszerzenie klasy anonimowej trwało OnClickListener
dłużej niż aktywność, prawda?
final Dialog dialog = new Dialog(this);
dialog.setContentView(R.layout.dialog_generic);
Button okButton = (Button) dialog.findViewById(R.id.dialog_button_ok);
TextView titleTv = (TextView) dialog.findViewById(R.id.dialog_generic_title);
// *** Handle button click
okButton.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
dialog.dismiss();
}
});
titleTv.setText("dialog title");
dialog.show();
Czy ten przykład jest niebezpieczny i dlaczego?
// We are still inside an Activity
_handlerToDelayDroidMove = new Handler();
_handlerToDelayDroidMove.postDelayed(_droidPlayRunnable, 10000);
private Runnable _droidPlayRunnable = new Runnable() {
public void run() {
_someFieldOfTheActivity.performLongCalculation();
}
};
Mam wątpliwości co do tego, że zrozumienie tego tematu wiąże się ze szczegółowym zrozumieniem tego, co zachowuje się, gdy działanie zostanie zniszczone i odtworzone.
Czy to jest
Powiedzmy, że właśnie zmieniłem orientację urządzenia (co jest najczęstszą przyczyną wycieków). Kiedy super.onCreate(savedInstanceState)
zostanie wywołane u mnie onCreate()
, czy przywróci to wartości pól (tak jak przed zmianą orientacji)? Czy to również przywróci stany klas wewnętrznych?
Zdaję sobie sprawę, że moje pytanie nie jest zbyt precyzyjne, ale naprawdę doceniłbym każde wyjaśnienie, które mogłoby wyjaśnić sprawę.
źródło
Odpowiedzi:
To, co zadajesz, jest dość trudnym pytaniem. Chociaż możesz myśleć, że to tylko jedno pytanie, w rzeczywistości zadajesz kilka pytań naraz. Zrobię co w mojej mocy, wiedząc, że muszę to opisać i, mam nadzieję, niektórzy dołączą się, aby opisać to, co mogę przegapić.
Klasy zagnieżdżone: Wprowadzenie
Ponieważ nie jestem pewien, jak dobrze czujesz się w OOP w Javie, omówię kilka podstawowych rzeczy. Zagnieżdżona klasa ma miejsce, gdy definicja klasy jest zawarta w innej klasie. Istnieją zasadniczo dwa typy: klasyczne zagnieżdżone klasy i klasy wewnętrzne. Prawdziwa różnica między nimi to:
Odśmiecanie i klasy wewnętrzne
Odśmiecanie jest automatyczne, ale próbuje usunąć obiekty na podstawie tego, czy sądzi, że są używane. Garbage Collector jest całkiem sprytny, ale nie bezbłędny. Może jedynie określić, czy coś jest używane przez to, czy istnieje aktywne odwołanie do obiektu.
Prawdziwy problem polega na tym, że klasa wewnętrzna była utrzymywana przy życiu dłużej niż jej pojemnik. Wynika to z niejawnego odwołania do zawierającej klasy. Jedynym sposobem może się to stać, jeśli obiekt poza klasą zawierającą zachowa odniesienie do obiektu wewnętrznego, bez względu na obiekt zawierający.
Może to prowadzić do sytuacji, w której wewnętrzny obiekt żyje (poprzez referencję), ale odniesienia do obiektu zawierającego zostały już usunięte ze wszystkich innych obiektów. Dlatego wewnętrzny obiekt utrzymuje żywy obiekt zawierający, ponieważ zawsze będzie miał do niego odniesienie. Problem polega na tym, że jeśli nie jest zaprogramowany, nie ma możliwości powrotu do zawierającego obiekt, aby sprawdzić, czy w ogóle żyje.
Najważniejszym aspektem tej realizacji jest to, że nie ma znaczenia, czy jest ona w działaniu, czy jest dająca się wyciągnąć. Będziesz zawsze trzeba być metodyczny przy użyciu klas wewnętrzne i upewnij się, że nie przeżyje obiekty pojemnika. Na szczęście, jeśli nie jest to podstawowy obiekt twojego kodu, wycieki mogą być niewielkie w porównaniu. Niestety są to jedne z najtrudniejszych wycieków, ponieważ mogą pozostać niezauważone, dopóki wiele z nich nie wycieknie.
Rozwiązania: klasy wewnętrzne
Działania i opinie: Wprowadzenie
Działania zawierają wiele informacji, które można uruchomić i wyświetlić. Działania są zdefiniowane przez cechę, że muszą mieć widok. Mają także pewne automatyczne programy obsługi. Niezależnie od tego, czy ją podasz, czy nie, działanie zawiera niejawne odniesienie do widoku, który zawiera.
Aby widok mógł zostać utworzony, musi wiedzieć, gdzie go utworzyć i czy ma jakieś elementy potomne, aby mógł zostać wyświetlony. Oznacza to, że każdy widok ma odniesienie do działania (via
getContext()
). Co więcej, każdy widok zawiera odniesienia do swoich dzieci (tjgetChildAt()
.). Wreszcie każdy widok zawiera odniesienie do renderowanej mapy bitowej reprezentującej jej wyświetlanie.Ilekroć masz odniesienie do działania (lub kontekstu działania), oznacza to, że możesz śledzić cały łańcuch w dół hierarchii układu. Dlatego przecieki pamięci dotyczące działań lub widoków są tak ogromne. To może być mnóstwo pamięci wycieku wszystko naraz.
Zajęcia, widoki i klasy wewnętrzne
Biorąc pod uwagę powyższe informacje o klasach wewnętrznych, są to najczęstsze wycieki pamięci, ale także najczęściej unikane. Chociaż pożądane jest, aby klasa wewnętrzna miała bezpośredni dostęp do członków klasy Działania, wielu chce po prostu ustawić ich statycznie, aby uniknąć potencjalnych problemów. Problem z działaniami i widokami jest znacznie głębszy.
Wyciekłe działania, widoki i konteksty działań
Wszystko sprowadza się do kontekstu i cyklu życia. Istnieją pewne zdarzenia (takie jak orientacja), które zabijają kontekst działania. Ponieważ tak wiele klas i metod wymaga kontekstu, programiści czasami próbują zapisać jakiś kod, chwytając odwołanie do kontekstu i trzymając się go. Zdarza się, że wiele obiektów, które musimy stworzyć, aby uruchomić naszą Aktywność, musi istnieć poza cyklem życia Aktywności, aby Aktywność mogła wykonywać to, co powinna. Jeśli któryś z twoich obiektów ma odniesienie do działania, jego kontekstu lub któregokolwiek z jego widoków, gdy jest zniszczony, właśnie wyciekłeś z tego działania i całego jego drzewa.
Rozwiązania: Działania i widoki
getBaseContext()
lubgetApplicationContext()
). Nie przechowują referencji domyślnie.Runnables: Wprowadzenie
Runnable nie są wcale takie złe. To znaczy, mogą być, ale tak naprawdę trafiliśmy już w większość niebezpiecznych stref. Runnable to operacja asynchroniczna, która wykonuje zadanie niezależne od wątku, w którym zostało utworzone. Większość plików wykonywalnych jest tworzona z wątku interfejsu użytkownika. Zasadniczo użycie Runnable tworzy kolejny wątek, nieco bardziej zarządzany. Jeśli klasyfikujesz Runnable jak klasę standardową i postępujesz zgodnie z powyższymi wytycznymi, powinieneś napotkać kilka problemów. W rzeczywistości wielu programistów tego nie robi.
Ze względu na łatwość, czytelność i logiczny przebieg programu wielu programistów używa Anonimowych klas wewnętrznych do definiowania swoich Runnables, takich jak powyższy przykład. To daje przykład jak ten, który wpisałeś powyżej. Anonimowa klasa wewnętrzna jest w zasadzie dyskretną klasą wewnętrzną. Po prostu nie musisz tworzyć zupełnie nowej definicji i po prostu zastępować odpowiednie metody. Pod wszystkimi innymi względami jest to klasa wewnętrzna, co oznacza, że zachowuje niejawne odniesienie do swojego kontenera.
Runnable i działania / widoki
Tak! Ta sekcja może być krótka! Ze względu na fakt, że Runnables działają poza bieżącym wątkiem, niebezpieczeństwo z nimi związane wiąże się z długotrwałymi operacjami asynchronicznymi. Jeśli element runnable jest zdefiniowany w działaniu lub widoku jako anonimowa klasa wewnętrzna LUB zagnieżdżona klasa wewnętrzna, istnieje kilka bardzo poważnych zagrożeń. Jest tak, ponieważ, jak wcześniej wspomniano, musi wiedzieć, kto jest jego pojemnikiem. Wprowadź zmianę orientacji (lub zabijanie systemu). Teraz wystarczy wrócić do poprzednich sekcji, aby zrozumieć, co się właśnie wydarzyło. Tak, twój przykład jest dość niebezpieczny.
Rozwiązania: Runnables
Odpowiedź na ostatnie pytanie Teraz, aby odpowiedzieć na pytania, które nie zostały bezpośrednio poruszone w innych sekcjach tego postu. Zapytałeś: „Kiedy obiekt klasy wewnętrznej może przetrwać dłużej niż klasa zewnętrzna?” Zanim do tego przejdziemy, jeszcze raz podkreślę: chociaż masz rację martwić się tym w Działaniach, może to spowodować wyciek w dowolnym miejscu. Podam prosty przykład (bez użycia działania) tylko w celu zademonstrowania.
Poniżej znajduje się typowy przykład podstawowej fabryki (brak kodu).
To nie jest tak powszechny przykład, ale wystarczająco prosty do zademonstrowania. Kluczem tutaj jest konstruktor ...
Teraz mamy przecieki, ale nie ma fabryki. Mimo że wydaliśmy fabrykę, pozostanie w pamięci, ponieważ każdy wyciek ma odniesienie do niej. Nie ma nawet znaczenia, że klasa zewnętrzna nie ma danych. Zdarza się to znacznie częściej, niż mogłoby się wydawać. Nie potrzebujemy twórcy, tylko jego dzieła. Tworzymy więc tymczasowo, ale używamy kreacji w nieskończoność.
Wyobraź sobie, co dzieje się, gdy nieznacznie zmieniamy konstruktor.
Teraz każdy z tych nowych LeakFactories właśnie wyciekł. Co o tym myślisz? Są to dwa bardzo częste przykłady tego, jak klasa wewnętrzna może przeżyć klasę zewnętrzną dowolnego typu. Gdyby ta klasa zewnętrzna była działaniem, wyobraź sobie, o ile gorsze byłoby.
Wniosek
Wymieniają one przede wszystkim znane niebezpieczeństwa związane z niewłaściwym użyciem tych obiektów. Ogólnie rzecz biorąc, ten post powinien obejmować większość pytań, ale rozumiem, że był to długi post, więc jeśli potrzebujesz wyjaśnienia, daj mi znać. Tak długo, jak postępujesz zgodnie z powyższymi praktykami, nie będziesz się martwić o wyciek.
źródło