Avviso: non collocare classi di contesto Android in campi statici; questa è una perdita di memoria (e interrompe anche Instant Run)


84

Android Studio:

Non collocare classi di contesto Android in campi statici; questa è una perdita di memoria (e interrompe anche Instant Run)

Quindi 2 domande:

# 1 Come si chiama a startServiceda un metodo statico senza una variabile statica per il contesto?
# 2 Come si invia un localBroadcast da un metodo statico (lo stesso)?

Esempi:

public static void log(int iLogLevel, String sRequest, String sData) {
    if(iLogLevel > 0) {

        Intent intent = new Intent(mContext, LogService.class);
        intent.putExtra("UPDATE_MAIN_ACTIVITY_VIEW", "UPDATE_MAIN_ACTIVITY_VIEW");
        mContext.startService(intent);
    }
}

o

        Intent intent = new Intent(MAIN_ACTIVITY_RECEIVER_INTENT);
        intent.putExtra(MAIN_ACTIVITY_REQUEST_FOR_UPDATE, sRequest));
        intent.putExtra(MAIN_ACTIVITY_DATA_FOR_VIEW, sData);
        intent.putExtra(MAIN_ACTIVITY_LOG_LEVEL, iLogLevel);
        LocalBroadcastManager.getInstance(mContext).sendBroadcast(intent);

Quale sarebbe il modo corretto per farlo senza utilizzare mContext?

NOTA: Penso che la mia domanda principale potrebbe essere come passare il contesto a una classe da cui vive il metodo chiamante.


Non puoi passare Context come parametro nel metodo?
Juan Cruz Soler

Chiamerei questa routine in luoghi che non avrebbero anche contesto.
John Smith

# 1 passalo come parametro # 2 lo stesso.
njzk2

Quindi devi passare il contesto anche al metodo del chiamante. Il problema è che i campi statici non vengono raccolti nella spazzatura, quindi potresti perdere un'attività con tutte le sue visualizzazioni
Juan Cruz Soler,

1
@JohnSmith Cascade dall'attività iniziale (tramite parametri del costruttore o parametri del metodo) fino al punto in cui ne hai bisogno.
AndroidMechanic - Viral Patel

Risposte:


56

Passalo semplicemente come parametro al tuo metodo. Non ha senso creare un'istanza statica di Contextsolo allo scopo di avviare un file Intent.

Ecco come dovrebbe apparire il tuo metodo:

public static void log(int iLogLevel, String sRequest, String sData, Context ctx) {
    if(iLogLevel > 0) {

        Intent intent = new Intent(ctx, LogService.class);
        intent1.putExtra("UPDATE_MAIN_ACTIVITY_VIEW", "UPDATE_MAIN_ACTIVITY_VIEW");
        ctx.startService(intent);
    }
}

Aggiornamento dai commenti sulla domanda: sovrapponi il contesto dall'attività iniziale (tramite parametri del costruttore o parametri del metodo) fino al punto in cui ne hai bisogno.


Potete fornire un esempio di costruttore?
John Smith

se il nome della tua classe è MyClassaggiungere un costruttore pubblico proprio come un metodo ad esso public MyClass(Context ctx) { // put this ctx somewhere to use later }(questo è il tuo costruttore) Ora crea una nuova istanza di MyClassutilizzo di questo costruttore ad esempioMyClass mc = new MyClass(ctx);
AndroidMechanic - Viral Patel

Non credo sia così semplice passare su richiesta. Anche se ci sono vantaggi evidenti come non doversi preoccupare di un contesto stantio o come qui, uno statico. Supponiamo che tu abbia bisogno del contesto [potrebbe essere che tu voglia scrivere in prefs] in un callback di risposta che verrà invocato in modo asincrono. Quindi, a volte, sei costretto a metterlo in un campo membro. E ora devi pensare a come non renderlo statico. stackoverflow.com/a/40235834/2695276 sembra funzionare.
Rajat Sharma,

1
Va bene usare ApplicationContext come campo statico? A differenza delle attività, l'oggetto dell'applicazione non viene distrutto, giusto?
NeoWang

ma la domanda è: perché all'inizio perde la memoria?
juztcode

51

Assicurati solo di passare context.getApplicationContext () o chiamare getApplicationContext () su qualsiasi contesto passato tramite metodi / costruttore al tuo singleton se decidi di memorizzarlo in qualsiasi campo membro.

Esempio di prova di idiota (anche se qualcuno passasse un'attività, afferrerà il contesto dell'app e lo userà per istanziare il singleton):

public static synchronized RestClient getInstance(Context context) {
    if (mInstance == null) {
        mInstance = new RestClient(context.getApplicationContext());
    }
    return mInstance;
}

getApplicationContext () secondo la documentazione: "Restituisce il contesto del singolo oggetto Application globale del processo corrente."

Significa che il contesto restituito da "getApplicationContext ()" vivrà attraverso l'intero processo e quindi non importa se memorizzi un riferimento statico ovunque poiché sarà sempre lì durante il runtime della tua app (e sopravviverà a qualsiasi oggetto / singleton istanziati da esso).

Confrontalo con il contesto all'interno di viste / attività che contengono grandi quantità di dati, se perdi un contesto detenuto da un'attività, il sistema non sarà in grado di liberare quella risorsa che ovviamente non è buona.

Un riferimento a un'attività in base al suo contesto dovrebbe vivere lo stesso ciclo di vita dell'attività stessa, altrimenti manterrà il contesto in ostaggio causando una perdita di memoria (che è la ragione dietro l'avviso di lanugine).

EDIT: Per il ragazzo che picchia l'esempio dei documenti sopra, c'è anche una sezione di commenti nel codice su ciò che ho appena scritto:

    // getApplicationContext() is key, it keeps you from leaking the
    // Activity or BroadcastReceiver if someone passes one in.

8
al ragazzo che ha picchiato il ragazzo che ha colpito l'esempio sopra: il punto di questo thread è l'avvertimento Lint in conflitto con il modello consigliato da Google di creare singleton.
Raphael C

7
Leggi: "Non posizionare le classi di contesto Android in campi statici; si tratta di una perdita di memoria (e interrompe anche Instant Run)" Sai cosa sono le classi di contesto? L'attività è una di queste e non dovresti memorizzare l'attività come un campo statico, come ti sei descritto (o perderà memoria). Tuttavia, puoi memorizzare Context (purché sia ​​il contesto dell'applicazione) come campo statico, poiché sopravvive a tutto. (E quindi ignora l'avvertimento). Sono sicuro che possiamo essere d'accordo su questo semplice fatto, giusto?
Marcus Gruneau

come veterinario iOS, nella mia prima settimana di Android ... Spiegazioni come questa mi aiutano a capire questa assurdità del contesto .. Quindi, quell'avviso di lanugine (oh, come non mi piacciono gli avvisi) rimarrà in giro, ma la tua risposta risolve il vero problema .
eric

1
@ Marcus se la tua classe figlia non è a conoscenza di chi la sta istanziando con quale contesto, allora è solo una cattiva pratica memorizzarla come membro statico. inoltre, il contesto dell'applicazione vive come parte dell'oggetto Application della tua app, l'oggetto dell'applicazione non rimarrà in memoria per sempre, verrà ucciso. contrariamente alla credenza popolare, l'app non verrà riavviata da zero. Android creerà un nuovo oggetto Application e avvierà l'attività in cui si trovava l'utente prima per dare l'illusione che l'applicazione non sia mai stata uccisa in primo luogo.
Raphael C

@RaphaelC hai la documentazione di tale? Sembra che sia completamente sbagliato perché Android garantisce un solo contesto dell'applicazione per esecuzione di ciascun processo.
HaydenKai

6

È solo un avvertimento. Non preoccuparti. Se si desidera utilizzare un contesto dell'applicazione, è possibile salvarlo in una classe "singleton", che viene utilizzata per salvare tutta la classe singleton nel progetto.


2

Nel tuo caso non ha molto senso averlo come campo statico ma non credo che sia un male in tutti i casi. Se ora cosa stai facendo puoi avere un campo statico con contesto e annullarlo in seguito. Sto creando un'istanza statica per la mia classe modello principale che ha un contesto all'interno, il contesto dell'applicazione non il contesto dell'attività e inoltre ho un campo di istanza statica della classe contenente attività che ho annullato durante la distruzione. Non vedo che ho una perdita di memoria. Quindi se qualche ragazzo intelligente pensa che io abbia torto sentiti libero di commentare ...

Anche Instant Run funziona bene qui ...


Non credo che ti sbagli sul principio, ma devi stare molto attento che l'attività di cui stai parlando abbia solo un massimo di una singola istanza in un dato momento prima che possa utilizzare campi statici. Se la tua app finisce con più di un back stack perché può essere avviata da luoghi diversi (notifica, deep linking, ...), le cose andranno storte a meno che tu non usi qualche flag come singleInstance nel manifest. Quindi è sempre più facile evitare i campi statici dalle attività.
BladeCoder

android: launchMode = "singleTask" dovrebbe essere sufficiente, quindi sto passando a quello, ho usato singleTop ma non sapevo che non fosse abbastanza perché voglio sempre solo singole istanze delle mie attività principali, ecco come sono progettate le mie app.
Renetik

2
"singleTask" garantisce solo un'istanza per attività. Se la tua app ha più punti di ingresso come il collegamento diretto o il suo avvio da una notifica, potresti ritrovarti con più attività.
BladeCoder

2

Utilizzare WeakReferenceper memorizzare il contesto nelle classi Singleton e l'avviso sparirà

private WeakReference<Context> context;

//Private contructor
private WidgetManager(Context context) {
    this.context = new WeakReference<>(context);
}

//Singleton
public static WidgetManager getInstance(Context context) {
    if (null == widgetManager) {
        widgetManager = new WidgetManager(context);
    }
    return widgetManager;
}

Ora puoi accedere a Context come

  if (context.get() instanceof MainActivity) {
            ((MainActivity) context.get()).startActivityForResult(pickIntent, CODE_REQUEST_PICK_APPWIDGET);
        }

1

In genere, evitare di definire i campi di contesto come statici. L'avviso stesso spiega il motivo: è una perdita di memoria. Tuttavia, interrompere la corsa istantanea potrebbe non essere il problema più grande del pianeta.

Ora, ci sono due scenari in cui dovresti ricevere questo avviso. Per un esempio (il più ovvio):

public static Context ctx;

E poi c'è quello un po 'più complicato, in cui il contesto è racchiuso in una classe:

public class Example{
    public Context ctx;
    //Constructor omitted for brievety 
}

E quella classe è definita statica da qualche parte:

public static Example example;

E riceverai l'avvertimento.

La soluzione in sé è abbastanza semplice: non posizionare i campi di contesto in istanze statiche , sia che si tratti di una classe wrapping, sia che la dichiari statica direttamente.

E la soluzione all'avviso è semplice: non posizionare il campo in modo statico. Nel tuo caso, passa il contesto come istanza al metodo. Per le classi in cui vengono effettuate più chiamate Context, utilizzare un costruttore per passare il contesto (o un'attività per quella materia) alla classe.

Nota che è un avvertimento, non un errore. Se per qualsiasi motivo hai bisogno di un contesto statico, puoi farlo. Anche se crei una perdita di memoria quando lo fai.


come possiamo farlo senza creare una perdita di memoria?
Julian00

1
Non puoi. Se hai assolutamente bisogno di passare i contesti in giro, potresti guardare in un bus degli eventi
Zoe

ok questo era il problema che stavo avendo se potessi per favore darci un'occhiata forse c'è un altro modo per farlo, btw il metodo deve essere statico perché lo chiamo dal codice c ++ stackoverflow.com/questions/54683863/…
èJulian00

0

Se ti assicuri che sia un contesto dell'applicazione. Importa. Aggiungi questo

@SuppressLint("StaticFieldLeak")

1
Non consiglierei di farlo comunque. Se hai bisogno del contesto puoi usare il metodo requireContext (), se usi le librerie AndroidX. Oppure puoi passare il contesto direttamente al metodo che ne ha bisogno. Oppure puoi anche solo ottenere il riferimento alla classe dell'app, ma preferirei raccomandare di non utilizzare questo suggerimento SuppressLint.
Oleksandr n.
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.