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:
- Klasy zagnieżdżone statycznie:
- Są uważane za „najwyższego poziomu”.
- Nie wymaga budowania instancji klasy zawierającej.
- Nie mogą odwoływać się do zawierających członków klasy bez wyraźnego odwołania.
- Mają własne życie.
- Wewnętrzne klasy zagnieżdżone:
- Zawsze należy zbudować instancję klasy zawierającej.
- Automatycznie mają niejawne odniesienie do zawierającej instancji.
- Może uzyskać dostęp do członków klasy kontenera bez odniesienia.
- Żywotność powinna być nie dłuższa niż żywotność pojemnika.
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
- Uzyskaj tymczasowe odwołania z obiektu zawierającego.
- Pozwól, aby zawierający obiekt był jedynym, który zachowuje długotrwałe odniesienia do wewnętrznych obiektów.
- Użyj ustalonych wzorów, takich jak Fabryka.
- Jeśli klasa wewnętrzna nie wymaga dostępu do elementów klasy zawierającej, rozważ przekształcenie jej w klasę statyczną.
- Używaj ostrożnie, bez względu na to, czy jest w działaniu, czy nie.
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 (tj getChildAt()
.). 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
- Za wszelką cenę unikaj statycznego odniesienia do Widoku lub Działania.
- Wszystkie odniesienia do kontekstów działań powinny być krótkotrwałe (czas trwania funkcji)
- Jeśli potrzebujesz długowiecznego kontekstu, użyj kontekstu aplikacji (
getBaseContext()
lub getApplicationContext()
). Nie przechowują referencji domyślnie.
- Alternatywnie możesz ograniczyć zniszczenie działania, zastępując zmiany konfiguracji. Nie powstrzymuje to jednak innych potencjalnych zdarzeń przed zniszczeniem działania. Chociaż możesz to zrobić, nadal możesz odwołać się do powyższych praktyk.
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
- Spróbuj rozszerzyć Runnable, jeśli nie złamie to logiki twojego kodu.
- Staraj się, aby rozszerzone Runnables były statyczne, jeśli muszą to być klasy zagnieżdżone.
- Jeśli musisz użyć Anonimowych Runnables, unikaj ich tworzenia w dowolnym obiekcie, który ma długotrwałe odwołanie do używanego działania lub widoku.
- Wiele Runnables mogło równie łatwo być AsyncTasks. Rozważ użycie AsyncTask, ponieważ są one domyślnie zarządzane przez VM.
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).
public class LeakFactory
{//Just so that we have some data to leak
int myID = 0;
// Necessary because our Leak class is an Inner class
public Leak createLeak()
{
return new Leak();
}
// Mass Manufactured Leak class
public class Leak
{//Again for a little data.
int size = 1;
}
}
To nie jest tak powszechny przykład, ale wystarczająco prosty do zademonstrowania. Kluczem tutaj jest konstruktor ...
public class SwissCheese
{//Can't have swiss cheese without some holes
public Leak[] myHoles;
public SwissCheese()
{//Gotta have a Factory to make my holes
LeakFactory _holeDriller = new LeakFactory()
// Now, let's get the holes and store them.
myHoles = new Leak[1000];
for (int i = 0; i++; i<1000)
{//Store them in the class member
myHoles[i] = _holeDriller.createLeak();
}
// Yay! We're done!
// Buh-bye LeakFactory. I don't need you anymore...
}
}
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.
public class SwissCheese
{//Can't have swiss cheese without some holes
public Leak[] myHoles;
public SwissCheese()
{//Now, let's get the holes and store them.
myHoles = new Leak[1000];
for (int i = 0; i++; i<1000)
{//WOW! I don't even have to create a Factory...
// This is SOOOO much prettier....
myHoles[i] = new LeakFactory().createLeak();
}
}
}
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.