android.app.Application, static singleton per variabili globali e NullPointerException

Leggendo l’Android reference guide a proposito di android.app.Application, troviamo scritto:

Base class for those who need to maintain global application state. You can provide your own implementation by specifying its name in your AndroidManifest.xml’s <application> tag, which will cause that class to be instantiated for you when the process for your application/package is created.
There is normally no need to subclass Application. In most situation, static singletons can provide the same functionality in a more modular way.

Sembra quindi il posto giusto, e piu’ elegante di un singleton, per mettere le nostre variabili globali quando non occorre fare cose troppo complicate e per inizializzarle al primo avvio dell’applicazione, mediante override del metodo OnCreate() della classe base. Qualcosa del genere:

La classe App.java

package it.rainbowbreeze.singleton;

public class App extends Application {
public static int myGlobalStaticValue;
public int myGlobalValue;

  @Override
  public void onCreate() {
    super.onCreate();
    myGlobalStaticValue = 10;
    myGlobalValue = 20;
  }
}

La modifica all’AndroidManifest.xml


E la chiamata da fare in un metodo qualunque di un’Activity, ad esempio:

//access to static field
int value = App.myGlobalStaticValue;
//access to non static fields
int value = ((App)context.getApplication()).myGlobalValue;

Da principio tutto funzionava, poi pero’ mi sono accorto di un problema non banale: che di tanto in tanto le variabili persistite in questo modo non mantengono il loro valore, soprattutto dopo che l’applicazione rimane in background per parecchio tempo o si fanno nel frattempo diverse altre cose col dispositivo.

Basito da questo comportamento, mi sono messo a cercare in giro, ed ecco che escono fuori le prime conferme, di cui riporto un estratto:

Consider a case – your app goes into the background because somebody is calling you (Phone app is in the foreground now). In this case && under some other conditions (check the above link for what they could be) the OS may kill your application process, including the Application subclass instance. As a result the state is lost. When you later return to the application, then the OS will restore its activity stack and Application subclass instance, however the myState field will be null.

This was an unplesant surprise for us in production. Believe me Android kills processes, it just depends on RAM state and other factors described in the documentation. It was a nightmare for us so I just share my real experience. Well we did not have this on emulators, but in real world some devices are ‘overloaded’ with apps, so killing a background process is a normal situation. Yes, if user then decides to get the app up into foreground – the OS restores its stack including the Application instance, however there will not be your static data you count on unless you persisted it.

In pratica, anche il contenuto dell’Application, come quello di ogni altro static singleton, puo’ venire ripulito dal sistema operativo. Come fare quindi per ovviare a questo problema?

Le soluzioni, ovviamente, possono essere le piu’ disparate: se si tratta di dati che non cambiano, occorre controllare ogni volta che non siano nulli e inizializzarli di nuovo all’occorrenza, mentre se si tratta di dati che possono cambiare nel ciclo di vita dell’applicazione, ci sono le SharedPreferences o altri meccanismi utili per persisterli e ricaricarli quando necessario.

Per farlo in maniera elegante e abbastanza generalizzata, mi e’ piaciuto il consiglio Dianne Hackborn di usare dei lazy-loading singleton, buoni per entrambe le situazioni. Ecco un veloce esempio

class Singleton {
  static final Object mLock = new Object();
  static final Singleton mInstance;

  private Singleton(Context context) {
    //init or reload your data here
  }

  static getInstance(Context context) {
    synchronized (mLock) {
      if (mInstance == null) {
        mInstance = new Singleton(context);
      }
      return sInstance;
    }
  }

  private int mMyValue;
  public int getMyValue() {
    return mMyValue;
  }
  public void setMyValue(int newValue) {
    mMyValue = newValue;
    //or choose another way to persist the data, via a Shared Preference, for example
  }
}

Se non e’ necessaria, si puo’ anche togliere la referenza al Context nel getInstance().

Altri vantaggi dei singleton: la loro modularita’, l’inizializzazione che viene fatta solo quando serve, mentre nel caso dell’Application viene fatta all’avvio dell’applicazione, rallentandone i tempi di caricamento. Poi certo, l’Application.onCreate() rimane un punto di importanza cruciale per tutta una serie di compiti da fare solo e solamente all’avvio dell’applicazione, prima di caricare ogni altra cosa. A voi la scelta di come suddividere il codice nel migliore dei modi.

3 Comments

  1. Invece di utilizzare la tecnica lazy-loading singleton nel costruttore, utilizzando Application, non sarebbe sufficiente ripristinare il valore delle variabili nella onCreate() di Application?

  2. Il problema che onCreate non viene chiamato una volta che i valori dei campi vengono settati a null, dato che l’applicazione non viene “rilanciata” di nuovo, ma il suo stato viene (parzialmente) ripristinato :(

  3. Utilizzo un Singleton istanziato nella onCreate in application con la getInstance() come consigliato e sempre in application chiamo un metodo che va a leggere le sharedpreference e le carica in variabili presenti nel mio Singleton.
    Raramente mi ritrovo tali variabili vuote; mi attendevo che se application viene terminata che venisse ricreata ripassando dalla onCreate di application stessa e ricaricasse quindi i valori delle sharedpreference richiamando il metodo che li ricarica.

    Quindi mi confermi che se application viene terminato e ricreato questo non esegue l’onCreate nuovamente ma ricrea solamente l’istanza del Singleton che aveva creato precedentemente?

    Ritornando al tuo esempio se fosse come ho capito il valore di mMyValue potrebbe comunque essere perso perché il Singleton viene istanziato nella onCreate di application che potrebbe comunque essere terminata e ricreata senza che il valore venga mantenuto. :(

    Ho capito male io oppure c’è qualcosa che mi sfugge?

Leave a Reply