Singletons vs. contesto applicativo in Android?


362

Ricordando questo post che elenca diversi problemi nell'uso dei singleton e dopo aver visto diversi esempi di applicazioni Android che usano il pattern singleton, mi chiedo se sia una buona idea usare Singletons invece di singole istanze condivise attraverso lo stato globale dell'applicazione (sottoclasse android.os.Application e ottenerlo tramite context.getApplication ()).

Quali vantaggi / svantaggi avrebbero entrambi i meccanismi?

Ad essere sincero, mi aspetto la stessa risposta in questo post schema Singleton con applicazione Web, non è una buona idea! ma applicato ad Android. Ho ragione? Cosa c'è di diverso in DalvikVM altrimenti?

EDIT: vorrei avere opinioni su diversi aspetti coinvolti:

  • Sincronizzazione
  • riutilizzabilità
  • analisi

Risposte:


295

Non sono molto d'accordo con la risposta di Dianne Hackborn. A poco a poco stiamo rimuovendo tutti i singoli dal nostro progetto a favore di oggetti leggeri, con ambito di applicazione che possono essere facilmente ricreati quando ne hai realmente bisogno.

I singleton sono un incubo per i test e, se inizializzati pigramente, introdurranno "indeterminismo di stato" con sottili effetti collaterali (che possono improvvisamente emergere quando si spostano le chiamate getInstance()da un ambito all'altro). La visibilità è stata menzionata come un altro problema e poiché i singleton implicano l'accesso "globale" (= casuale) allo stato condiviso, possono insorgere bug sottili se non correttamente sincronizzati in applicazioni simultanee.

Lo considero un anti-pattern, è un cattivo stile orientato agli oggetti che equivale essenzialmente a mantenere lo stato globale.

Per tornare alla tua domanda:

Sebbene il contesto dell'app possa essere considerato un singleton stesso, è gestito dal framework e ha un ciclo di vita , un ambito e un percorso di accesso ben definiti . Quindi credo che se hai bisogno di gestire lo stato globale dell'app, dovrebbe andare qui, da nessun'altra parte. Per qualsiasi altra cosa, ripensa se hai davvero bisogno di un oggetto singleton o se sarebbe anche possibile riscrivere la tua classe singleton per creare un'istanza di piccoli oggetti di breve durata che eseguono l'attività a portata di mano.


131
Se stai raccomandando l'applicazione, stai raccomandando l'uso di singleton. Onestamente, non c'è modo di aggirarlo. L'applicazione è un singleton, con semantica più scadente. Non entrerò nelle discussioni religiose sui singoli qualcosa che non dovresti mai usare. Preferisco essere pratico - ci sono posti in cui sono una buona scelta per mantenere lo stato per processo e possono semplificare le cose facendo così, e puoi anche usarli nella situazione sbagliata e spararti ai piedi.
hackbod

18
È vero, e ho detto che il "contesto dell'app può essere considerato un singleton stesso". La differenza è che con l'istanza dell'app, spararsi al piede è molto più difficile, poiché il suo ciclo di vita è gestito dal framework. Anche i framework DI come Guice, Hivemind o Spring fanno uso di singoli, ma questo è un dettaglio di implementazione di cui lo sviluppatore non dovrebbe preoccuparsi. Penso che sia generalmente più sicuro fare affidamento sulla corretta implementazione della semantica del framework piuttosto che sul proprio codice. Sì, lo ammetto! :-)
Matthias il

93
Onestamente non ti impedisce di spararti al piede più di quanto non faccia un singleton. È un po 'confuso, ma non esiste un ciclo di vita dell'applicazione. Viene creato all'avvio dell'app (prima che uno dei suoi componenti sia istanziato) e il suo onCreate () chiamato a quel punto, e ... questo è tutto. Rimane lì e vive per sempre, fino a quando il processo non viene ucciso. Proprio come un singleton. :)
hackbod,

30
Oh, una cosa che potrebbe confondere tutto ciò: Android è progettato principalmente per l'esecuzione di app nei processi e la gestione del ciclo di vita di tali processi. Quindi su Android i singleton sono un modo molto naturale per trarre vantaggio dalla gestione di quel processo - se vuoi memorizzare nella cache qualcosa nel tuo processo fino a quando la piattaforma non deve recuperare la memoria del processo per qualcos'altro, mettere lo stato in un singleton lo farà.
hackbod,

7
Va bene abbastanza. Posso solo dire che non mi sono mai guardato indietro da quando abbiamo fatto il passo lontano dai singoli autogestiti. Ora stiamo optando per una soluzione leggera in stile DI, in cui manteniamo un singleton di fabbrica (RootFactory), che a sua volta è gestito dall'istanza dell'app (se lo desideri, è un delegato). Questo singleton gestisce le dipendenze comuni su cui fanno affidamento tutti i componenti dell'app, ma l'istanza è gestita in un'unica posizione: la classe dell'app. Mentre con questo approccio rimane un singleton, è limitato alla classe Application, quindi nessun altro modulo di codice conosce quel "dettaglio".
Matthias,

231

Consiglio vivamente i single. Se hai un singleton che necessita di un contesto, disponi di:

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

Preferisco i singleton rispetto alle applicazioni perché aiutano a mantenere un'app molto più organizzata e modulare - invece di avere un posto in cui è necessario mantenere tutto il tuo stato globale in tutta l'app, ogni singolo pezzo può occuparsi di se stesso. Anche il fatto che i singleton inizializzino pigramente (su richiesta) invece di guidarti lungo il percorso di fare tutte le inizializzazioni in anticipo in Application.onCreate () è buono.

Non c'è nulla di intrinsecamente sbagliato nell'uso dei singleton. Usali correttamente, quando ha senso. Il framework Android ne ha davvero molti, per mantenere cache per processo di risorse caricate e altre cose del genere.

Anche per le applicazioni semplici il multithreading non diventa un problema con i singleton, perché in base alla progettazione tutti i callback standard per l'app vengono inviati sul thread principale del processo in modo che non si verifichi il multi-threading a meno che non venga introdotto esplicitamente tramite thread o implicitamente pubblicando un fornitore di contenuti o un servizio IBinder su altri processi.

Sii solo attento a ciò che stai facendo. :)


1
Se qualche tempo dopo voglio ascoltare un evento esterno, o condividere su un IBinder (suppongo che non sarebbe una semplice app) dovrei aggiungere un doppio blocco, sincronizzazione, volatile, giusto? Grazie per la risposta :)
mschonaker

2
Non per un evento esterno - BroadcastReceiver.onReceive () viene anche chiamato sul thread principale.
hackbod,

2
Va bene. Mi indicheresti del materiale di lettura (preferirei il codice) in cui posso vedere il meccanismo di invio del thread principale? Penso che questo chiarirà diversi concetti contemporaneamente per me. Grazie in anticipo.
mschonaker,

2
Questo è il principale codice di invio sul lato app: android.git.kernel.org/?p=platform/frameworks/…
hackbod

8
Non c'è nulla di intrinsecamente sbagliato nell'uso dei singleton. Usali correttamente, quando ha senso. .. certo, appunto, ben detto. Il framework Android ne ha davvero molti, per mantenere cache per processo di risorse caricate e altre cose del genere. Esattamente come dici tu. Dai tuoi amici nel mondo di iOS, "tutto è un singleton" in iOS .. niente potrebbe essere più naturale sui dispositivi fisici di un concetto singleton: i gps, l'orologio, i giroscopi, ecc. Ecc. Concettualmente come altrimenti potresti progettare quelli di come singoli? Quindi si
Fattie,

22

Da: Sviluppatore> riferimento - Applicazione

Normalmente non è necessario sottoclassare l'Applicazione. Nella maggior parte dei casi, i singoli statici possono fornire la stessa funzionalità in un modo più modulare. Se il tuo singleton necessita di un contesto globale (ad esempio per registrare i ricevitori di trasmissione), alla funzione per recuperarlo può essere assegnato un contesto che utilizza internamente Context.getApplicationContext () quando costruisce il singleton per la prima volta.


1
E se scrivi un'interfaccia per il singleton, lasciando getInstance non statico, puoi persino rendere il costruttore predefinito della classe che usa singleton iniettare il singleton di produzione attraverso un costruttore non predefinito, che è anche il costruttore che usi per creare il classe che usa singleton nei suoi test unitari.
android.weasel,

11

L'applicazione non è la stessa del Singleton. I motivi sono:

  1. Il metodo dell'applicazione (come onCreate) viene chiamato nel thread dell'interfaccia utente;
  2. il metodo singleton può essere chiamato in qualsiasi thread;
  3. Nel metodo "onCreate" dell'applicazione, è possibile creare un'istanza di Handler;
  4. Se il singleton viene eseguito nel thread none-ui, non è possibile creare un'istanza di Handler;
  5. L'applicazione ha la capacità di gestire il ciclo di vita delle attività nell'app. Ha il metodo "registerActivityLifecycleCallbacks". Ma i singoli non hanno l'abilità.

1
Nota: è possibile creare un'istanza di Handler su qualsiasi thread. da doc: "Quando crei un nuovo gestore, questo è legato alla coda thread / messaggio del thread che lo sta creando"
Christ,

1
@Christ Grazie! Proprio ora ho appreso "meccanismo del Looper". Se istanza gestore sul thread none-ui senza il codice 'Looper.prepare ()', il sistema segnalerà l'errore "java.lang.RuntimeException: Can crea un gestore all'interno del thread che non ha chiamato Looper.prepare () ".
Sunhang,

11

Ho avuto lo stesso problema: Singleton o creare una sottoclasse android.os.Application?

Per prima cosa ho provato con Singleton ma la mia app a un certo punto effettua una chiamata al browser

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

e il problema è che, se il portatile non ha memoria sufficiente, la maggior parte delle classi (anche i Singleton) vengono ripulite per ottenere un po 'di memoria, quindi, quando ritorna dal browser alla mia app, si blocca ogni volta.

Soluzione: inserire i dati necessari in una sottoclasse di classe Application.


1
Mi sono imbattuto spesso in post in cui le persone affermano che ciò può accadere. Pertanto, allego semplicemente oggetti come singoli singlet con il caricamento lento ecc. Solo per essere sicuro che il ciclo di vita sia documentato e conosciuto. Assicurati solo di non salvare centinaia di immagini nell'oggetto dell'applicazione poiché capisco che non verrà cancellato dalla memoria se la tua app è in background e tutte le attività vengono distrutte per liberare memoria per altri processi.
Janusz,

Bene, il caricamento lento di Singleton dopo il riavvio dell'applicazione non è il modo giusto per consentire agli oggetti di essere spazzati dal GC. I riferimenti deboli sono, giusto?
mschonaker,

15
Veramente? Dalvik scarica le classi e perde lo stato del programma? Sei sicuro che non sia che sta raccogliendo spazzatura il tipo di oggetti relativi ad attività a ciclo di vita limitato che non dovresti mettere in singoletti in primo luogo? Devi fornire esempi clrar per una richiesta così straordinaria!
android.weasel,

1
A meno che non ci siano stati cambiamenti di cui non sono a conoscenza, Dalvik non scarica le classi. Mai. Il comportamento che stanno vedendo è che il loro processo viene ucciso in background per fare spazio al browser. Probabilmente stavano inizializzando la variabile nella loro attività "principale", che potrebbe non essere stata creata nel nuovo processo al ritorno dal browser.
Groxx

5

Considera entrambi allo stesso tempo:

  • avere oggetti singleton come istanze statiche all'interno delle classi.
  • avere una classe comune (Context) che restituisce le istanze singleton per tutti gli oggetti singelton nell'applicazione, con il vantaggio che i nomi dei metodi in Context saranno significativi, ad esempio: context.getLoggedinUser () invece di User.getInstance ().

Inoltre, ti suggerisco di espandere il tuo contesto per includere non solo l'accesso agli oggetti singleton, ma alcune funzionalità a cui è necessario accedere a livello globale, come ad esempio context.logOffUser (), context.readSavedData (), ecc. Probabilmente rinominando il contesto in La facciata avrebbe senso allora.


4

In realtà sono uguali. C'è una differenza che posso vedere. Con la classe Application è possibile inizializzare le variabili in Application.onCreate () e distruggerle in Application.onTerminate (). Con singleton devi affidarti all'inizializzazione e alla distruzione della VM.


16
i documenti per onTerminate dicono che è sempre e solo chiamato dall'emulatore. Sui dispositivi questo metodo probabilmente non verrà chiamato. developer.android.com/reference/android/app/…
danb,

3

I miei 2 centesimi:

Ho notato che alcuni campi singleton / statici sono stati ripristinati quando la mia attività è stata distrutta. Ho notato questo su alcuni dispositivi di fascia bassa 2.3.

Il mio caso è stato molto semplice: ho solo un file "init_done" archiviato in privato e un metodo "init" statico che ho chiamato da activity.onCreate (). Noto che il metodo init si stava eseguendo nuovamente su una qualche ricostruzione dell'attività.

Anche se non posso dimostrare la mia affermazione, potrebbe essere correlato a QUANDO il singleton / la classe è stata creata / utilizzata per prima. Quando l'attività viene distrutta / riciclata, sembra che anche tutte le classi a cui fa riferimento solo questa attività vengano riciclate.

Ho spostato la mia istanza di singleton in una sottoclasse di Applicazione. Li accedo dall'istanza dell'applicazione. e, da allora, non si è più accorto del problema.

Spero che questo possa aiutare qualcuno.


3

Dalla proverbiale bocca del cavallo ...

Quando si sviluppa l'app, potrebbe essere necessario condividere dati, contesto o servizi a livello globale attraverso l'app. Ad esempio, se la tua app contiene dati di sessione, come l'utente attualmente connesso, probabilmente vorrai esporre queste informazioni. In Android, il modello per risolvere questo problema è avere l'istanza di android.app.Application proprietaria di tutti i dati globali, quindi trattare l'istanza dell'applicazione come un singleton con accessi statici ai vari dati e servizi.

Quando si scrive un'app Android, si ha la certezza di avere solo un'istanza della classe android.app.Application, quindi è sicuro (e consigliato dal team di Google Android) trattarla come un singleton. In altre parole, puoi tranquillamente aggiungere un metodo getInstance () statico all'implementazione dell'applicazione. Così:

public class AndroidApplication extends Application {

    private static AndroidApplication sInstance;

    public static AndroidApplication getInstance(){
        return sInstance;
    }

    @Override
    public void onCreate() {
        super.onCreate();
        sInstance = this;
    }
}

2

La mia attività chiama finish () (che non lo termina immediatamente, ma alla fine lo farà) e chiama Google Street Viewer. Quando eseguo il debug su Eclipse, la mia connessione all'app si interrompe quando viene chiamato Street Viewer, che intendo come la (intera) applicazione chiusa, presumibilmente per liberare memoria (poiché una singola attività completata non dovrebbe causare questo comportamento) . Tuttavia, sono in grado di salvare lo stato in un pacchetto tramite onSaveInstanceState () e ripristinarlo nel metodo onCreate () della prossima attività nello stack. O usando un singleton statico o un'applicazione di sottoclasse, devo affrontare lo stato di chiusura e perdita dell'applicazione (a meno che non lo salvi in ​​un pacchetto). Quindi dalla mia esperienza sono gli stessi per quanto riguarda la conservazione dello stato. Ho notato che la connessione si perde in Android 4.1.2 e 4.2.2 ma non su 4.0.7 o 3.2.4,


"Ho notato che la connessione si perde in Android 4.1.2 e 4.2.2 ma non su 4.0.7 o 3.2.4, il che a mio avviso suggerisce che il meccanismo di recupero della memoria è cambiato ad un certo punto." ..... Penso che i tuoi dispositivi non abbiano la stessa quantità di memoria disponibile, né la stessa app installata. e quindi la tua conclusione potrebbe essere errata
Cristo,

@Christ: Sì, hai ragione. Sarebbe strano se il meccanismo di recupero della memoria cambiasse tra le versioni. Probabilmente il diverso utilizzo della memoria ha causato comportamenti distinti.
Piovezan,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.