Singletony kontra kontekst aplikacji w systemie Android?

362

Przywołując ten post, w którym wymieniono kilka problemów związanych z używaniem singletonów i widziałem kilka przykładów aplikacji na Androida wykorzystujących wzorzec singletonu, zastanawiam się, czy dobrym pomysłem jest używanie singletonów zamiast pojedynczych instancji współdzielonych przez globalny stan aplikacji (podklasowanie android.os. Aplikacja i uzyskiwanie jej poprzez context.getApplication ()).

Jakie zalety / wady miałyby oba mechanizmy?

Szczerze mówiąc, oczekuję tej samej odpowiedzi w tym poście Wzór Singleton z aplikacją internetową, To nie jest dobry pomysł! ale dotyczy Androida. Mam rację? Co inaczej różni się w DalvikVM?

EDYCJA: Chciałbym mieć opinie na temat kilku aspektów:

  • Synchronizacja
  • Wielokrotnego użytku
  • Testowanie
mschonaker
źródło

Odpowiedzi:

295

Bardzo nie zgadzam się z odpowiedzią Dianne Hackborn. Stopniowo usuwamy wszystkie singletony z naszego projektu na rzecz lekkich obiektów o zadaniach o zasięgu, które można łatwo odtworzyć, kiedy naprawdę ich potrzebujesz.

Singletony są koszmarem do testowania i, jeśli leniwie zainicjowane, wprowadzą „stan nieokreśloności” z subtelnymi efektami ubocznymi (które mogą nagle pojawić się podczas przenoszenia wywołań getInstance()z jednego zakresu do drugiego). Widoczność została wspomniana jako kolejny problem, a ponieważ singletony implikują „globalny” (= losowy) dostęp do stanu współdzielonego, mogą pojawić się subtelne błędy, gdy nie zostaną poprawnie zsynchronizowane w równoległych aplikacjach.

Uważam to za anty-wzorzec, jest to zły styl obiektowy, który zasadniczo sprowadza się do utrzymania stanu globalnego.

Aby wrócić do pytania:

Chociaż kontekst aplikacji można uznać za singleton sam, jest zarządzany w ramach frameworka i ma dobrze zdefiniowany cykl życia , zakres i ścieżkę dostępu. Dlatego uważam, że jeśli potrzebujesz zarządzać stanem globalnym aplikacji, powinien on przejść tutaj, nigdzie indziej. Jeśli chodzi o cokolwiek innego, zastanów się, czy naprawdę potrzebujesz obiektu singletonowego, czy też możliwe byłoby przepisanie klasy singletonowej, aby zamiast tego utworzyć instancję małych, krótkotrwałych obiektów wykonujących dane zadanie.

Matthias
źródło
131
Jeśli polecasz aplikację, zalecamy korzystanie z singletonów. Szczerze mówiąc, nie można tego obejść. Aplikacja jest singletonem, z bardziej szykowną semantyką. Nie będę wdawał się w religijne spory o singletony, których nigdy nie powinieneś używać. Wolę być praktyczny - są miejsca, w których są dobrym wyborem do utrzymania stanu per-proces i mogą uprościć to, robiąc to, i równie dobrze możesz użyć ich w niewłaściwej sytuacji i strzelić sobie w stopę.
hackbod
18
To prawda i wspomniałem, że „kontekst aplikacji można uznać za singleton”. Różnica polega na tym, że w przypadku instancji aplikacji strzelanie sobie w stopę jest znacznie trudniejsze, ponieważ jej cykl życia jest obsługiwany przez platformę. Frameworki DI, takie jak Guice, Hivemind lub Spring, również korzystają z singletonów, ale jest to szczegół implementacji, o który deweloper nie powinien się troszczyć. Myślę, że bezpieczniej jest polegać na poprawnej implementacji semantyki frameworka niż na własnym kodzie. Tak, przyznaję, że tak! :-)
Matthias
93
Szczerze mówiąc, nie przeszkadza ci to postrzelić się w stopę bardziej niż singleton. Jest to trochę mylące, ale nie ma cyklu życia aplikacji. Jest tworzony, gdy aplikacja się uruchamia (przed utworzeniem instancji dowolnego z jej składników), a jej funkcja onCreate () jest wywoływana w tym momencie i ... to wszystko. Siedzi tam i żyje wiecznie, dopóki proces nie zostanie zabity. Zupełnie jak singleton. :)
hackbod
30
Och, jedna rzecz, która może być myląca - Android jest bardzo zaprojektowany do uruchamiania aplikacji w procesach i zarządzania cyklem życia tych procesów. Tak więc na Androidzie singletony są bardzo naturalnym sposobem na skorzystanie z zarządzania procesem - jeśli chcesz buforować coś w swoim procesie, dopóki platforma nie będzie musiała odzyskać pamięci procesu na coś innego, umieszczenie tego stanu w singletonie zrobi to.
hackbod
7
W porządku, wystarczy. Mogę tylko powiedzieć, że nie oglądałem się za siebie, odkąd zrobiliśmy krok od samodzielnego zarządzania singletonami. Wybieramy teraz lekkie rozwiązanie w stylu DI, w którym utrzymujemy jeden fabryczny singleton (RootFactory), który z kolei jest zarządzany przez instancję aplikacji (jeśli jest to delegat). Ten singleton zarządza powszechnymi zależnościami, na których polegają wszystkie komponenty aplikacji, ale tworzenie instancji jest zarządzane w jednym miejscu - klasie aplikacji. Chociaż przy takim podejściu pozostaje jeden singleton, jest on ograniczony do klasy Application, więc żaden inny moduł kodu nie wie o tym „szczególe”.
Matthias,
231

Bardzo polecam singletony. Jeśli masz singletona, który potrzebuje kontekstu:

MySingleton.getInstance(Context c) {
    //
    // ... needing to create ...
    sInstance = new MySingleton(c.getApplicationContext());
}

Wolę singletony od aplikacji, ponieważ pomaga to utrzymać organizację o wiele bardziej uporządkowaną i modułową - zamiast jednego miejsca, w którym musi być utrzymany cały stan globalny w aplikacji, każdy osobny element może zająć się sobą. Dobry jest także fakt, że singletony leniwie inicjalizują się (na żądanie) zamiast poprowadzić cię ścieżką wykonywania wszystkich inicjalizacji z góry w Application.onCreate ().

Używanie singletonów nie ma w sobie nic złego. Po prostu używaj ich poprawnie, kiedy ma to sens. Struktura systemu Android faktycznie ma ich wiele, aby utrzymywać pamięci podręczne załadowanych zasobów na proces i inne tego typu rzeczy.

Również w przypadku prostych aplikacji wielowątkowość nie staje się problemem dla singletonów, ponieważ z założenia wszystkie standardowe wywołania zwrotne do aplikacji są wysyłane w głównym wątku procesu, więc nie będziesz mieć wielowątkowości, chyba że wprowadzisz to jawnie przez wątki lub pośrednio przez opublikowanie dostawcy treści lub usługi IBinder innym procesom.

Zastanów się, co robisz. :)

hackbod
źródło
1
Jeśli jakiś czas później chcę odsłuchać zdarzenie zewnętrzne lub udostępnić na IBinder (myślę, że nie byłaby to prosta aplikacja) musiałbym dodać podwójne blokowanie, synchronizację, niestabilność, prawda? Dziękuję za odpowiedź :)
mschonaker
2
Nie dla zdarzenia zewnętrznego - BroadcastReceiver.onReceive () jest również wywoływany w głównym wątku.
hackbod 30.09.10
2
W porządku. Czy wskazałbyś mi jakiś materiał do czytania (wolałbym kod), w którym widzę główny mechanizm rozsyłania wątków? Myślę, że to wyjaśni mi kilka pojęć naraz. Z góry dziękuję.
mschonaker
2
To jest główny kod wysyłający po stronie aplikacji: android.git.kernel.org/?p=platform/frameworks/…
hackbod
8
Używanie singletonów nie ma w sobie nic złego. Po prostu używaj ich poprawnie, kiedy ma to sens. .. pewnie dokładnie powiedziane. Struktura systemu Android faktycznie ma ich wiele, aby utrzymywać pamięci podręczne załadowanych zasobów na proces i inne tego typu rzeczy. Dokładnie tak jak mówisz. Od znajomych w świecie iOS, „wszystko jest singletonem” w iOS. Nic nie może być bardziej naturalne na urządzeniach fizycznych niż koncepcja singletonu: GPS, zegar, żyroskopy itp. - koncepcyjnie, jak inaczej byś je zaprojektował niż jako singletony? Więc tak.
Fattie
22

Od: Deweloper> referencje - Aplikacja

Zwykle nie ma potrzeby dzielenia aplikacji. W większości sytuacji statyczne singletony mogą zapewnić tę samą funkcjonalność w bardziej modułowy sposób. Jeśli twój singleton potrzebuje globalnego kontekstu (na przykład w celu zarejestrowania odbiorników rozgłoszeniowych), funkcja do jego odzyskania może otrzymać kontekst, który wewnętrznie używa Context.getApplicationContext () podczas pierwszej budowy singletonu.

Somatik
źródło
1
A jeśli napiszesz interfejs dla singletona, pozostawiając getInstance niestatycznym, możesz nawet wprowadzić domyślnego konstruktora klasy używającej singletonu do wstrzykiwania singletonu produkcyjnego przez konstruktora innego niż domyślny, który jest również konstruktorem używanym do tworzenia klasa wykorzystująca singleton w testach jednostkowych.
android.weasel
11

Aplikacja nie jest taka sama jak Singleton. Przyczyny są:

  1. Metoda aplikacji (taka jak onCreate) jest wywoływana w wątku interfejsu użytkownika;
  2. metodę singletona można wywołać w dowolnym wątku;
  3. W metodzie „onCreate” aplikacji można utworzyć instancję modułu obsługi;
  4. Jeśli singleton jest wykonywany w wątku bez interfejsu użytkownika, nie można utworzyć instancji programu obsługi;
  5. Aplikacja ma możliwość zarządzania cyklem życia działań w aplikacji. Ma metodę „registerActivityLifecycleCallbacks”. Ale singletony nie mają takiej możliwości.
sunhang
źródło
1
Uwaga: możesz utworzyć instancję programu obsługi dla dowolnego wątku. from doc: „Kiedy tworzysz nowy moduł obsługi, jest on powiązany z kolejką wątków / komunikatów tworzącego go wątku”
Christ
1
@Christ Dziękuję! Właśnie nauczyłem się „mechanizmu Looper”. Jeśli instancja obsługi w wątku none-ui bez kodu „Looper.prepare ()”, system zgłosi błąd „java.lang.RuntimeException: Can nie twórz programu obsługi w wątku, który nie wywołał Looper.prepare () ".
sunhang
11

Miałem ten sam problem: Singleton lub utworzyć podklasę android.os. Aplikacja?

Najpierw próbowałem z Singletonem, ale moja aplikacja w pewnym momencie dzwoni do przeglądarki

Intent myIntent = new Intent(Intent.ACTION_VIEW, Uri.parse("http://www.google.com"));

i problem polega na tym, że jeśli telefon nie ma wystarczającej ilości pamięci, większość twoich zajęć (nawet Singletonów) jest czyszczona, aby uzyskać trochę pamięci, więc po powrocie z przeglądarki do mojej aplikacji ulegał awarii za każdym razem.

Rozwiązanie: umieść potrzebne dane w podklasie klasy Application.

JoséMi
źródło
1
Często natrafiałem na posty, w których ludzie twierdzą, że może to nastąpić. Dlatego po prostu dołączam obiekty do aplikacji, takie jak singletony z leniwym ładowaniem itp., Aby mieć pewność, że cykl życia jest udokumentowany i znany. Pamiętaj tylko, aby nie zapisywać setek obrazów w obiekcie aplikacji, ponieważ rozumiem, że nie zostanie on usunięty z pamięci, jeśli aplikacja znajduje się w tle, a wszystkie działania zostaną zniszczone w celu zwolnienia pamięci dla innych procesów.
Janusz
Cóż, leniwe ładowanie Singletona po ponownym uruchomieniu aplikacji nie jest dobrym sposobem, aby pozwolić obiektom zostać zmiecionym przez GC. Słabe referencje są, prawda?
mschonaker,
15
Naprawdę? Dalvik zwalnia klasy i traci stan programu? Czy na pewno nie chodzi o to, że gromadzi śmieci w rodzaju obiektów o ograniczonym cyklu życia, których nie powinieneś umieszczać w singletonach? Musisz podać clrar przykłady tak niezwykłego roszczenia!
android.weasel
1
O ile nie nastąpią zmiany, których nie jestem świadomy, Dalvik nie zwalnia klas. Zawsze. Zachowanie, które widzą, polega na tym, że proces jest zabijany w tle, aby zrobić miejsce dla przeglądarki. Prawdopodobnie inicjalizowali zmienną w swojej „głównej” aktywności, która mogła nie zostać utworzona w nowym procesie po powrocie z przeglądarki.
Groxx
5

Rozważ oba jednocześnie:

  • posiadanie obiektów singletonów jako instancji statycznych wewnątrz klas.
  • posiadanie wspólnej klasy (Context), która zwraca instancje singletonu dla wszystkich obiektów singelton w Twojej aplikacji, co ma tę zaletę, że nazwy metod w Context będą miały znaczenie na przykład: context.getLoggedinUser () zamiast User.getInstance ().

Ponadto sugeruję, abyś rozszerzył swój kontekst, aby obejmował nie tylko dostęp do obiektów singletonowych, ale także niektóre funkcje, które muszą być dostępne globalnie, takie jak na przykład: context.logOffUser (), context.readSavedData () itp. Prawdopodobnie zmiana nazwy kontekstu na Fasada miałaby wtedy sens.

adranale
źródło
4

W rzeczywistości są takie same. Jest jedna różnica, którą widzę. Za pomocą klasy Application możesz zainicjować swoje zmienne w Application.onCreate () i zniszczyć je w Application.onTerminate (). W singletonie musisz polegać na inicjowaniu i niszczeniu statyki VM.

Fedor
źródło
16
Dokumenty dla onTerminate mówią, że jest on zawsze wywoływany tylko przez emulator. Na urządzeniach ta metoda prawdopodobnie nie zostanie wywołana. developer.android.com/reference/android/app/…
danb
3

Moje 2 centy:

Zauważyłem, że niektóre pola singletonowe / statyczne zostały zresetowane po zniszczeniu mojej aktywności. Zauważyłem to na niektórych niskiej jakości urządzeniach 2.3.

Mój przypadek był bardzo prosty: mam po prostu prywatny plik „init_done” i statyczną metodę „init”, którą wywołałem z activity.onCreate (). Zauważam, że metoda init ponownie wykonała się po pewnym odtworzeniu działania.

Chociaż nie mogę udowodnić mojej afirmacji, może to dotyczyć KIEDY singleton / klasa została utworzona / wykorzystana jako pierwsza. Kiedy działanie zostaje zniszczone / poddane recyklingowi, wydaje się, że wszystkie klasy, które odnoszą się tylko do tego działania, są również poddawane recyklingowi.

Przeniosłem instancję singletona do podklasy aplikacji. Uzyskuję do nich dostęp z instancji aplikacji. i od tego czasu nie zauważyłem problemu ponownie.

Mam nadzieję, że to może komuś pomóc.

Chrystus
źródło
3

Z przysłowiowego pyska konia ...

Podczas opracowywania aplikacji może być konieczne globalne udostępnianie danych, kontekstu lub usług w obrębie całej aplikacji. Na przykład, jeśli aplikacja ma dane sesji, takie jak aktualnie zalogowany użytkownik, prawdopodobnie będziesz chciał ujawnić te informacje. W Androidzie wzór rozwiązania tego problemu polega na tym, że instancja android.app.Application jest właścicielem wszystkich danych globalnych, a następnie traktuje instancję aplikacji jako singleton ze statycznymi akcesoriami do różnych danych i usług.

Pisząc aplikację na Androida, masz gwarancję, że masz tylko jedną instancję klasy android.app.Application, więc jest to bezpieczne (i zalecane przez zespół Google Android), aby traktować ją jako singleton. Oznacza to, że można bezpiecznie dodać statyczną metodę getInstance () do implementacji aplikacji. Tak jak:

public class AndroidApplication extends Application {

    private static AndroidApplication sInstance;

    public static AndroidApplication getInstance(){
        return sInstance;
    }

    @Override
    public void onCreate() {
        super.onCreate();
        sInstance = this;
    }
}
RMcGuigan
źródło
2

Moje działania wywołują finish () (co nie oznacza, że ​​kończy się natychmiast, ale ostatecznie to zrobi) i wywołuje Google Street Viewer. Kiedy debuguję go w Eclipse, moje połączenie z aplikacją zrywa się, gdy wywoływana jest Street Viewer, co rozumiem jako zamknięcie (całej) aplikacji, rzekomo w celu zwolnienia pamięci (ponieważ pojedyncze działanie zakończone nie powinno powodować takiego zachowania) . Niemniej jednak jestem w stanie zapisać stan w pakiecie za pomocą onSaveInstanceState () i przywrócić go w metodzie onCreate () następnej aktywności na stosie. Albo za pomocą statycznego pojedynczego lub subklasowania aplikacji napotykam stan zamknięcia i utraty aplikacji (chyba że zapiszę ją w pakiecie). Tak więc z mojego doświadczenia są takie same w odniesieniu do zachowania państwa. Zauważyłem, że połączenie zostało utracone w Androidzie 4.1.2 i 4.2.2, ale nie w 4.0.7 lub 3.2.4,

Piovezan
źródło
„Zauważyłem, że połączenie zostało utracone w Androidzie 4.1.2 i 4.2.2, ale nie w 4.0.7 lub 3.2.4, co w moim rozumieniu sugeruje, że mechanizm odzyskiwania pamięci zmienił się w pewnym momencie.” ..... Myślę, że twoje urządzenia nie mają takiej samej ilości dostępnej pamięci ani zainstalowanej tej samej aplikacji. i dlatego wasz wniosek może być niepoprawny
Chrystus,
@Christ: Tak, musisz mieć rację. Byłoby dziwne, gdyby mechanizm odzyskiwania pamięci zmieniał się między wersjami. Prawdopodobnie różne użycie pamięci spowodowało odmienne zachowania.
Piovezan