Kiedy dokładnie można korzystać z (anonimowych) klas wewnętrznych?

Czytałem kilka artykułów na temat wycieków pamięci w Androidzie i obejrzałem ciekawy filmik z Google I / o na ten temat .

Nadal nie do końca rozumiem tę koncepcję, a zwłaszcza gdy jest ona bezpieczna lub niebezpieczna dla użytkownika wewnętrzne klasy wewnątrz aktywności.

Oto co zrozumiałem:

Wyciek pamięci nastąpi, jeśli instancja klasy wewnętrznej przetrwa dłużej niż jej Klasa zewnętrzna (aktywność). - > w jakich sytuacjach może to stało się?

W tym przykładzie przypuszczam, że nie ma ryzyka wycieku, ponieważ nie ma możliwości, aby Klasa Anonymous rozszerzająca OnClickListener żyła 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 ma związek ze zrozumieniem szczegółowo tego, co jest przechowywane, gdy działanie jest niszczone i odtwarzane.

Naprawdę?

Powiedzmy, że właśnie zmieniłem orientację urządzenia (co jest najczęstszą przyczyną wycieków). Kiedy super.onCreate(savedInstanceState) zostanie wywołane w moim onCreate(), czy to przywróci wartości pól(tak jak były przed zmianą orientacji)? Czy to również przywróci stan klas wewnętrznych?

Zdaję sobie sprawę, że moje pytanie nie jest zbyt precyzyjne, ale byłbym wdzięczny za każde Wyjaśnienie, które mogłoby wyjaśnić sprawę jaśniej.

Author: gobernador, 2012-06-02

1 answers

To, o co pytasz, jest dość trudne. Chociaż możesz myśleć, że to tylko jedno pytanie, w rzeczywistości zadajesz kilka pytań na raz. Zrobię co w mojej mocy z wiedzą, że muszę to pokryć i, mam nadzieję, że niektórzy inni dołączą do pokrycia tego, co może mi brakować.

Klasy Zagnieżdżone: Wprowadzenie

Ponieważ nie jestem pewien, jak wygodnie ci jest z OOP w Javie, to trafi kilka podstaw. Klasa zagnieżdżona jest wtedy, gdy definicja klasy jest zawarta w kolejna klasa. Istnieją zasadniczo dwa typy: klasy zagnieżdżone statyczne i klasy wewnętrzne. Prawdziwa różnica między nimi to:

  • Klasy Zagnieżdżone Statyczne:
    • są uważane za "najwyższy poziom".
    • nie wymagają konstruowania instancji klasy zawierającej.
    • nie może odwoływać się do zawierających członków klasy bez wyraźnego odniesienia.
    • Mają własne życie.
  • Wewnętrzne Klasy Zagnieżdżone:
    • zawsze wymagaj instancja klasy zawierającej, która ma być skonstruowana.
    • automatycznie mają niejawne odniesienie do instancji zawierającej.
    • może uzyskać dostęp do klasy kontenera bez odniesienia.
    • żywotność to przypuszcza się, że nie jest dłuższa niż długość pojemnika.
  • Garbage Collection and Inner Classes

    Garbage Collection jest automatyczne, ale próbuje usunąć obiekty w oparciu o to, czy myśli, że są używany. Śmieciarz jest całkiem sprytny, ale nie bezbłędny. Może tylko określić, czy coś jest używane przez to, czy nie istnieje aktywne odniesienie do obiektu.

    Prawdziwy problem polega na tym, że Klasa wewnętrzna jest utrzymywana przy życiu dłużej niż jej kontener. Wynika to z niejawnego odniesienia do klasy zawierającej. Może to nastąpić tylko wtedy, gdy obiekt spoza klasy zawierającej zachowuje odniesienie do obiektu wewnętrznego, bez względu na obiekt zawierający.

    Może to prowadzić do sytuacji, w której wewnętrzny obiekt jest żywy (poprzez odniesienie), ale odniesienia do zawierającego obiektu zostały już usunięte ze wszystkich innych obiektów. Wewnętrzny obiekt jest więc utrzymywany przy życiu, ponieważ zawsze będzie miał do niego odniesienie. Problem z tym polega na tym, że jeśli nie jest zaprogramowany, nie ma możliwości powrotu do obiektu zawierającego, aby sprawdzić, czy w ogóle żyje.

    Najważniejszym aspektem tego realizacja polega na tym, że nie ma znaczenia, czy jest to działanie, czy jest drawable. Będziesz zawsze muszą być metodyczne podczas korzystania z klas wewnętrznych i upewnić się, że nigdy nie przeżyją obiektów kontenera. Na szczęście, jeśli nie jest to główny obiekt Twojego kodu, przecieki mogą być niewielkie w porównaniu. Niestety, są to jedne z najtrudniejszych wycieków do znalezienia, ponieważ prawdopodobnie pozostaną niezauważone, dopóki wiele z nich nie wycieknie.

    Rozwiązania: Wewnętrzne Klasy

    • uzyskaj tymczasowe odniesienia z obiektu zawierającego.
    • pozwól, aby zawierający obiekt był jedynym, który przechowuje długotrwałe odniesienia do wewnętrznych obiektów.
    • użyj ustalonych wzorców, takich jak Fabryka.
    • Jeśli Klasa wewnętrzna nie wymaga dostępu do zawierających jej członków, rozważ przekształcenie jej w klasę statyczną.
    • używaj ostrożnie, niezależnie od tego, czy jest to aktywność, czy nie.

    Działania i poglądy: wprowadzenie

    Działania zawierają wiele informacji, aby móc je uruchamiać i wyświetlać. Działania są określone przez cechę, że muszą mieć pogląd. Mają również pewne automatyczne manipulatory. Niezależnie od tego, czy to określisz, czy nie, działanie ma ukryte odniesienie do widoku, który zawiera.

    Aby Widok mógł zostać utworzony, musi wiedzieć, gdzie go utworzyć i czy ma dzieci, aby móc go wyświetlać. Oznacza to, że każdy Widok ma odniesienie do aktywności (via getContext()). Co więcej, każdy pogląd zachowuje odniesienia do swoich dzieci (tj. getChildAt()). Wreszcie, każdy Widok zawiera odniesienie do renderowanej bitmapy, która reprezentuje jej wyświetlanie.

    Za każdym razem, gdy masz odniesienie do aktywności (lub kontekstu aktywności), oznacza to, że możesz śledzić cały łańcuch w dół hierarchii układu. Dlatego wycieki pamięci dotyczące działań lub poglądów są tak ogromne. Może to być ton pamięci przeciekanie na raz.

    Działania, poglądy 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łań, wielu jest skłonnych po prostu uczynić je statycznymi, aby uniknąć potencjalnych problemów. Problem z działaniami i poglądami sięga znacznie głębiej.

    Wyciekły aktywności, widoki i Konteksty Aktywności

    Wszystko sprowadza się do kontekstu i cyklu życia. Istnieją pewne zdarzenia (takie jak orientacja), które zabijają kontekst aktywności. Ponieważ tak wiele klas i metod wymaga kontekstu, Programiści czasami próbują zapisać jakiś kod, chwytając odniesienie do kontekstu i trzymając go. Tak się składa, że wiele obiektów, które musimy stworzyć, aby prowadzić naszą aktywność, musi istnieć poza cyklem życia aktywności, aby umożliwić jej działanie co musi zrobić. Jeśli któryś z Twoich obiektów ma odniesienie do aktywności, jej kontekstu lub widoków po jej zniszczeniu, właśnie wyciekłeś z tej aktywności i całego drzewa widoków.

    Rozwiązania: działania i poglądy

    • unikaj, za wszelką cenę, statycznego odniesienia do widoku lub aktywności.
    • wszystkie odniesienia do kontekstów aktywności powinny być krótkotrwałe (czas trwania funkcji)
    • Jeśli potrzebujesz długotrwałego kontekstu, użyj kontekst aplikacji (getBaseContext() lub getApplicationContext()). Nie przechowują one odniesień w sposób dorozumiany.
    • Alternatywnie można ograniczyć niszczenie aktywności poprzez nadpisanie zmian konfiguracji. Nie powstrzymuje to jednak innych potencjalnych zdarzeń przed zniszczeniem działalności. Podczas gdy ty możesz to zrobić, możesz nadal chcieć odwołać się do powyższych praktyk.

    Runnables: Wprowadzenie

    Runnables nie są takie złe. To znaczy, oni mogą być, ale tak naprawdę dotarliśmy już do większości stref zagrożenia. Runnable to asynchroniczna operacja, która wykonuje zadanie niezależne od wątku, na którym został utworzony. Większość runnables jest tworzona z wątku interfejsu użytkownika. W istocie, korzystanie z Runnable jest tworzenie innego wątku, tylko nieco bardziej zarządzane. Jeśli Klasa A działa jak standardowa klasa i postępujesz zgodnie z powyższymi wytycznymi, powinieneś napotkać kilka problemów. Rzeczywistość jest taka, że wielu programistów tego nie robi.

    Z łatwością, czytelność i logiczny przepływ programu, wielu programistów używa anonimowych klas wewnętrznych do definiowania ich Runnables, takich jak przykład, który tworzysz powyżej. Daje to przykład podobny do tego, 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 nadpisywać odpowiednich metod. We wszystkich innych aspektach jest klasą wewnętrzną, co oznacza, że zachowuje niejawne odniesienie do swojej Pojemnik.

    Runnables and Activities / Views

    Yay! Ta sekcja może być krótka! Ze względu na fakt, że Runnables działa poza bieżącym wątkiem, niebezpieczeństwo z nimi wiąże się z długotrwałymi operacjami asynchronicznymi. Jeśli runnable jest zdefiniowany w aktywności lub widoku jako anonimowa Klasa wewnętrzna lub zagnieżdżona Klasa wewnętrzna, istnieje kilka bardzo poważnych zagrożeń. Wynika to z faktu, że jak już wcześniej stwierdzono, ma wiedzieć, kto jest jego kontenerem. Wpisz zmiana orientacji (lub system kill). Teraz wystarczy odnieść się do poprzednich sekcji, aby zrozumieć, co właśnie się stał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 stały się statyczne, jeśli muszą być klasami zagnieżdżonymi.
    • Jeśli musisz używać anonimowych Runnables, unikaj ich tworzenia w dowolne obiekt, który ma długotrwałe odniesienie do aktywności lub widoku, które jest w użyciu.
    • Wiele Runnables równie łatwo mogło być asynchronicznymi zadaniami. Rozważ użycie AsyncTask, ponieważ są one domyślnie zarządzane maszynami wirtualnymi.

    Odpowiedź na pytanie końcowe Teraz, aby odpowiedzieć na pytania, które nie zostały bezpośrednio skierowane przez inne sekcje tego postu. Zapytałeś: "kiedy obiekt klasy wewnętrznej może przetrwać dłużej niż jego Klasa zewnętrzna?"Zanim przejdziemy do tego, pozwól, że powtórzę: chociaż ty masz prawo martwić się o to w działaniach, może to spowodować wyciek w dowolnym miejscu. Podam prosty przykład (bez użycia czynności) tylko po to, aby zademonstrować.

    Poniżej znajduje się 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 popularny przykład, ale wystarczająco prosty do wykazania. Kluczem 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...
        }
    }
    
    Mamy przecieki, ale nie ma fabryki. Mimo, że wypuściliśmy fabrykę, pozostanie ona w pamięci, ponieważ każdy przeciek ma nawiązanie do niego. Nie ma znaczenia, że Klasa zewnętrzna nie ma danych. Zdarza się to znacznie częściej niż mogłoby się wydawać. Nie potrzebujemy Stwórcy, tylko jego kreacje. Więc tworzymy je tymczasowo, ale używamy kreacji w nieskończoność.

    Wyobraź sobie, co się dzieje, gdy zmienimy konstruktor tylko nieznacznie.

    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ły. Co o tym myślisz? Są to dwa bardzo powszechne przykłady tego, jak Klasa wewnętrzna może przeżyj zewnętrzną klasę dowolnego typu. Gdyby ta zewnętrzna klasa była zajęciem, wyobraź sobie, jak bardzo byłoby gorzej.

    Wniosek

    Wymieniają one przede wszystkim znane zagrożenia związane z niewłaściwym używaniem tych obiektów. Ogólnie rzecz biorąc, ten post powinien obejmować większość twoich pytań, ale rozumiem, że był to post loooong, więc jeśli potrzebujesz wyjaśnienia, po prostu daj mi znać. Tak długo, jak postępujesz zgodnie z powyższymi praktykami, będziesz miał bardzo mało obaw o wyciek.

     569
    Author: Fuzzical Logic,
    Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
    2018-09-30 11:11:51