Autore Topic: Static member accessed via instance reference  (Letto 692 volte)

Offline wlf

  • Utente normale
  • ***
  • Post: 373
  • Respect: +8
    • Mostra profilo
  • Dispositivo Android:
    Xperia
Static member accessed via instance reference
« il: 02 Maggio 2017, 15:39:05 CEST »
0
Salve,
ho un Singleton nel quale leggo una serie di parametri tra i quali l'IMEI che ho salvato nelle sharedpreferences:

Codice: [Seleziona]
public class Singleton {
  private static final Object mLock = new Object();
  private static final Singleton mInstance;

  public static myIMEI;

  private Singleton(Context context) {
    //init or reload your data here
    SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(context);
    myIMEI = sharedPref.getString("myIMEI", "");
  }

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

Se accedo a tale dato con Singleton.getInstance(this).myIMEI mi ritorna un warning con:

Warning: Static member 'com.example.example.Singleton.myIMEI' accessed via instance reference
Shows references to static methods and fields via class instance rather than a class itself.

La mia intenzione era quella di accedere alla unica istanza dei campi della mia classe Singleton. Li ho definiti pubblici in modo da evitare di scrivere getter/setter. Nel caso che l'istanza viene cancellata in memoria si dovrebbe rileggere il valore dalle preferences e rivalorizzarmi correttamente la mia variabile.

Praticamente mi viene dato un warning perché accedo alla variabile tramite una istanza, ma dovrebbe essere l'unica per tutta l'App da dovunque la chiamo. Oppure mi sta sfuggendo qualche cosa?
Grazie.
 

Offline iClaude

  • Utente normale
  • ***
  • Post: 323
  • Respect: +27
    • Mostra profilo
  • Dispositivo Android:
    Samsung Galaxy S8
  • Sistema operativo:
    Windows 10
Re:Static member accessed via instance reference
« Risposta #1 il: 02 Maggio 2017, 16:01:32 CEST »
+1
Al di là del fatto che il codice funziona cmq (infatti hai un warning, non un errore) non vedo motivo per dichiarare statica la variabile myIMEI, visto che è cmq unica in quanto interna al Singleton.
Perché non la dichiari come variabile di istanza, anche pubblica se vuoi?

Offline wlf

  • Utente normale
  • ***
  • Post: 373
  • Respect: +8
    • Mostra profilo
  • Dispositivo Android:
    Xperia
Re:Static member accessed via instance reference
« Risposta #2 il: 16 Maggio 2017, 11:05:07 CEST »
0
Ho provato a togliere lo static da myIMEI come mi hai consigliato. Ora per accedere all'instanza della Singleton tutte le volte che voglio richiamare la variabile utilizzo:
Codice: [Seleziona]
Singleton.getInstance(this).myIMEI
E' comunque corretto oppure bisogna passare per:

Codice: [Seleziona]
Singleton singleton = Singleton.getInstance(this);
singleton.myIMEI

Chiedo questo perché mi ritrovo alcune volte errori che mi sembrano dovuti al Singleton; in questo, nella getInstance(), instanzio anche le sharedPreference utilizzate poi da altre activity:

Codice: [Seleziona]
public static getInstance(Context context) {
    synchronized (mLock) {
      if (mInstance == null) {
        mInstance = new Singleton(context);
        mInstance.sharedPref = PreferenceManager.getDefaultSharedPreferences(context);
      }
      return sInstance;
    }
  }

Questo poi lo utilizzo poi per recuperare dei dati persistiti con:

Codice: [Seleziona]
Singleton.getInstance(this).sharedPref.getString("Valore", "");

Alcune volte è come se il dato che viene letto sia errato. :(

Offline iClaude

  • Utente normale
  • ***
  • Post: 323
  • Respect: +27
    • Mostra profilo
  • Dispositivo Android:
    Samsung Galaxy S8
  • Sistema operativo:
    Windows 10
Re:Static member accessed via instance reference
« Risposta #3 il: 16 Maggio 2017, 11:38:23 CEST »
0
Ho provato a togliere lo static da myIMEI come mi hai consigliato. Ora per accedere all'instanza della Singleton tutte le volte che voglio richiamare la variabile utilizzo:
Codice: [Seleziona]
Singleton.getInstance(this).myIMEI
E' comunque corretto oppure bisogna passare per:

Codice: [Seleziona]
Singleton singleton = Singleton.getInstance(this);
singleton.myIMEI

Vedo che hai cambiato il codice di getInstance rispetto all'esempio iniziale.
Cmq le 2 espressioni sono equivalenti, a parte che nella 2 tieni un reference alla classe.

Offline wlf

  • Utente normale
  • ***
  • Post: 373
  • Respect: +8
    • Mostra profilo
  • Dispositivo Android:
    Xperia
Re:Static member accessed via instance reference
« Risposta #4 il: 16 Maggio 2017, 14:16:06 CEST »
0
Le getInstance() sono pressoché identiche, nella prima avevo omesso che valorizzavo sharedPref e tutta una serie di variabili. In realtà ci sarebbe pure la lettura di una serie di parametri memorizzati nelle sharedPref.

Offline Ohmnibus

  • Utente senior
  • ****
  • Post: 836
  • Respect: +184
    • Github
    • Google+
    • @ohmnibus
    • Mostra profilo
    • Lords of Knowledge GdR
  • Dispositivo Android:
    Huawei P9 Lite
  • Play Store ID:
    Ohmnibus
  • Sistema operativo:
    Windows 10 x64
Re:Static member accessed via instance reference
« Risposta #5 il: 16 Maggio 2017, 15:33:42 CEST »
0
Abbi pazienza ma "Singleton.getInstance(this)" funziona ma è molto fuorviante.

In situazioni come questa valuta di mettere un metodo di inizializzazione al singleton, poi chiama la getInstance senza parametri:

Codice (Java): [Seleziona]
  public static init(Context context) {
    synchronized (mLock) {
      if (mInstance == null) {
        mInstance = new Singleton(context.getApplicationContext());
      }
    }
  }
 
  public static getInstance() {
    if (mInstance == null) {
      throw new IllegalStateException("Singleton not initialized.");
    }
    return mInstance;
  }
Ohmnibus
Le mie app su Play Store

È stata trovata una soluzione al tuo problema? Evidenzia il post più utile premendo . È un ottimo modo per ringraziare chi ti ha aiutato.

Offline wlf

  • Utente normale
  • ***
  • Post: 373
  • Respect: +8
    • Mostra profilo
  • Dispositivo Android:
    Xperia
Re:Static member accessed via instance reference
« Risposta #6 il: 17 Maggio 2017, 15:36:12 CEST »
0
@Ohmnibus
sarà fuorviante e poco "elegante", ma in un certo senso è fatto "appositamente". Nel senso che la init  col context + la getInstance danno per scontato che tengo la referenza di classe, che è proprio quello che vorrei evitare.

Nel senso che se parcheggio l'App con una Activity con la referenza di classe può darsi che nel frattempo il  Android mi abbia già azzerato tale referenza, quindi non avrò più accesso alla mia Singleton con le variabili in memoria. :(

Era proprio per evitare questi casi che utilizzavo la getIntance passandogli il context, perché altrimenti dovrei rifare il doppio passaggio tutte le volte che voglio accedere ad una variabile.

Offline Carni

  • Utente junior
  • **
  • Post: 62
  • Respect: +6
    • Mostra profilo
  • Dispositivo Android:
    Samsung Galaxy S3
  • Play Store ID:
    R3D5HA
  • Sistema operativo:
    Windows 10
Re:Static member accessed via instance reference
« Risposta #7 il: 17 Maggio 2017, 15:56:47 CEST »
0
Ciao wlf, a mio parere dovresti evitare di inizializzare i dati dentro il metodo getInstance ed inizializzarli nel costruttore (ovvero come nel primo post).

Alcune volte è come se il dato che viene letto sia errato. :(

Invece riguardo questo è un po' vaga come spiegazione... non sappiamo come inizializzi il Singleton e come lo richiami e soprattutto se il dato è errato oppure è qualcosa di default che è stato inserito.

Offline Ohmnibus

  • Utente senior
  • ****
  • Post: 836
  • Respect: +184
    • Github
    • Google+
    • @ohmnibus
    • Mostra profilo
    • Lords of Knowledge GdR
  • Dispositivo Android:
    Huawei P9 Lite
  • Play Store ID:
    Ohmnibus
  • Sistema operativo:
    Windows 10 x64
Re:Static member accessed via instance reference
« Risposta #8 il: 17 Maggio 2017, 16:12:39 CEST »
0

Correggetemi se sbaglio, ma sono abbastanza sicuro che Android non può azzerare (=rendere null) un'activity fintanto che esiste un riferimento al suo context.

Far riferimento al context di un'activity in un singleton è un classico caso di memory leak: il singleton, facendo riferimento a sé stesso, non può essere distrutto dal gc; di conseguenza finché lui esiste anche il context (e quindi l'activity) al quale fa riferimento esiste.

E' per questo e non a caso che nel mio esempio ho messo context.getApplicationContext(): così il singleton fa riferimento al contesto dell'app (e non quello dell'activity), ed avendo sia l'app che il singleton lo stesso ciclo di vita, succede che i due comunque "vivono" finché l'app vive, senza creare memory leak.

Di conseguenza è sufficiente fare l'init del singleton una sola volta, eventualmente all'avvio dell'app. Da quel momento non c'è rischio che il singleton risulti non valido, e sicuramente non c'è bisogno di fare una doppia invocazione ogni volta.

D'altro canto se esiste la necessità di un singleton che "può dover essere reinizializzato", allora probabilmente il singleton non è il pattern che va applicato


Post unito: 17 Maggio 2017, 16:22:00 CEST
Per quanto riguarda il discorso delle shared pref che leggono il dato sbagliato, ho notato in passato un delay tra la scrittura del dato e la sua conseguente lettura (anche usando edit.commit).

Non so se esiste un modo migliore per risolvere il problema, personalmente ho risolto usando delle variabili come cache:

Codice (Java): [Seleziona]
private boolean mCacheInitialized = false;
private String mCacheImei = null;
private String mCacheImsi = null;

private void initCache() {
  if (! mCacheInitialized) {
    mCacheImei = sharedPref.getString("MyImei", "");
    mCacheImsi = sharedPref.getString("MyImsi", "");
    mCacheInitialized = true;
  }
}

public string getImei() {
  initCache();
  return mCacheImei;
}

public void setImei(String newImei) {
  mCacheImei = newImei;
  Editor edit = sharedPref.edit();
  edit.putString("MyImei", mCacheImei);
  edit.apply();
}
« Ultima modifica: 17 Maggio 2017, 16:22:01 CEST da Ohmnibus, Reason: Merged DoublePost »
Ohmnibus
Le mie app su Play Store

È stata trovata una soluzione al tuo problema? Evidenzia il post più utile premendo . È un ottimo modo per ringraziare chi ti ha aiutato.

Offline iClaude

  • Utente normale
  • ***
  • Post: 323
  • Respect: +27
    • Mostra profilo
  • Dispositivo Android:
    Samsung Galaxy S8
  • Sistema operativo:
    Windows 10
Re:Static member accessed via instance reference
« Risposta #9 il: 17 Maggio 2017, 18:25:48 CEST »
0
Ma in sostanza qual è lo scopo di questa classe? Usare un Singleton come contenitore di variabili globali ricavate dalle preferenze?
Se è così non ne capisco l'utilità.
A parte che combina due cose che sono entrambe sconsigliate (il Singleton e le variabili globali), ma poi le SharedPreferences:
- sono già un Singleton
- una volta aperte sono messe in una cache in memoria, per cui la lettura è istantanea
- per leggere una preferenza bastano 2 righe di codice, esattamente uguale a quelle che impieghi col Singleton
E se le preferenze cambiano?

Offline wlf

  • Utente normale
  • ***
  • Post: 373
  • Respect: +8
    • Mostra profilo
  • Dispositivo Android:
    Xperia
Re:Static member accessed via instance reference
« Risposta #10 il: 18 Maggio 2017, 18:08:53 CEST »
0
@Carni
Condivido che andrebbero inizializzati nel costruttore, ma questi facendo riferimento al costruttore stesso mInstance perché non definisco più static myIMEI mi diventava un riferimento circolare. :(

Quanto al riferimento vago nella classe Singleton ho una sharadPreference con dati criptati; dopo che l'App è stata parcheggiata mi capita che leggendo un dato che non è stato cambiato la decrypt mi vada in errore. E' come se legga un valore errato ed il metodo di decrypt non riesce a convertirmelo ... :(

@Ohmnibus
Application sembrerebbe il posto più giusto ed elegante per mettere delle variabili globali invece che creare un singleton. Peccato che a volte dopo che un App è rimasta parecchio in backgrount la persistenza di queste variabili viene persa. :(
Per quanto riguarda le shared Pref non ci sono delay, è il ritorno da un background e possono passare delle ore o dei giorni dall'ultima scrittura. :(

@iClaude
Concordo; l'ho fatto principalmente perché le shard pref sono scritte criptate, per non spargere la password in tutto il codice ho preferito centralizzare il tutto con il singleton. Leggo le preference e le metto in variabili globali; se le devo cambiare prima modifico la variabile globale e poi faccio la edit delle preference. ;)