Modo statico per ottenere "Contesto" su Android?


970

C'è un modo per ottenere l' Contextistanza corrente all'interno di un metodo statico?

Sto cercando in quel modo perché odio salvare l'istanza 'Context' ogni volta che cambia.


57
Non salvare il contesto è una buona idea non solo perché è scomodo, ma anche perché può portare a enormi perdite di memoria!
Vikram Bodicherla,

12
@VikramBodicherla Sì, ma le risposte che seguono presuppongono che stiamo parlando del contesto dell'applicazione. Pertanto, le perdite di memoria non rappresentano un problema, ma l'utente deve utilizzare queste soluzioni solo laddove sia il contesto corretto da utilizzare.
Tom

Se devi usare un modo statico per ottenere Context, allora potrebbe esserci un modo migliore per progettare il codice.
Anonsage,

3
La documentazione di Android consiglia di passare il contesto ai getter di singoli. developer.android.com/reference/android/app/Application.html
Marco Luglio

Per preferire i single e il contesto passati con getInstance () rispetto al contesto statico, dai un'occhiata, ho cercato di spiegare il mio ragionamento qui supportato con il codice di lavoro: stackoverflow.com/a/38967293/4469112
Alessio

Risposte:


1302

Fai questo:

Nel file manifest di Android, dichiarare quanto segue.

<application android:name="com.xyz.MyApplication">

</application>

Quindi scrivi la classe:

public class MyApplication extends Application {

    private static Context context;

    public void onCreate() {
        super.onCreate();
        MyApplication.context = getApplicationContext();
    }

    public static Context getAppContext() {
        return MyApplication.context;
    }
}

Ora chiama ovunque MyApplication.getAppContext()per ottenere staticamente il contesto dell'applicazione.


81
C'è qualche svantaggio di questo metodo? Questo sembra barare. (Un trucco?)
jjnguy

203
Il rovescio della medaglia è che non vi è alcuna garanzia che onCreate () non statico sarà stato chiamato prima che un codice di inizializzazione statico tenti di recuperare l'oggetto Context. Ciò significa che il tuo codice chiamante dovrà essere pronto a gestire i valori null che in qualche modo sconfigge l'intero punto di questa domanda.
Melinda Green,

8
Anche forse .. dovremmo dichiarare questa static contextvariabile come volatile?
Vladimir Sorokin,

14
@ Tom Questo non è un caso in cui un membro di dati statici è inizialmente statico. Nel codice indicato, il membro statico viene inizializzato in modo non statico in onCreate (). Anche i dati inizializzati staticamente non sono abbastanza buoni in questo caso perché nulla assicura che l'inizializzazione statica di una data classe accadrà prima che possa accedervi durante l'inizializzazione statica di un'altra classe.
Melinda Green

10
@MelindaGreen Secondo la documentazione per l'applicazione, onCreate () viene chiamato prima che qualsiasi attività, servizio o destinatario (esclusi i fornitori di contenuti) siano stati creati. Quindi questa soluzione non sarebbe sicura finché non provi ad accedere a getAppContext () da un fornitore di contenuti?
Magnus W,

86

La maggior parte delle app che desiderano un metodo conveniente per ottenere il contesto dell'applicazione creano la propria classe che si estende android.app.Application.

GUIDA

Puoi farlo creando prima una classe nel tuo progetto come la seguente:

import android.app.Application;
import android.content.Context;

public class App extends Application {

    private static Application sApplication;

    public static Application getApplication() {
        return sApplication;
    }

    public static Context getContext() {
        return getApplication().getApplicationContext();
    }

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

Quindi, nel tuo AndroidManifest devi specificare il nome della tua classe nel tag di AndroidManifest.xml:

<application 
    ...
    android:name="com.example.App" >
    ...
</application>

È quindi possibile recuperare il contesto dell'applicazione in qualsiasi metodo statico utilizzando quanto segue:

public static void someMethod() {
    Context context = App.getContext();
}

AVVERTIMENTO

Prima di aggiungere qualcosa come sopra al tuo progetto, dovresti considerare cosa dice la documentazione:

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.


RIFLESSIONE

C'è anche un altro modo per ottenere il contesto dell'applicazione usando reflection. La riflessione viene spesso trascurata su Android e personalmente penso che questo non debba essere usato in produzione.

Per recuperare il contesto dell'applicazione, è necessario richiamare un metodo su una classe nascosta ( ActivityThread ) che è disponibile dall'API 1:

public static Application getApplicationUsingReflection() throws Exception {
    return (Application) Class.forName("android.app.ActivityThread")
            .getMethod("currentApplication").invoke(null, (Object[]) null);
}

Esiste un'altra classe nascosta ( AppGlobals ) che fornisce un modo per ottenere il contesto dell'applicazione in modo statico. Ottiene il contesto usando ActivityThreadquindi non c'è davvero alcuna differenza tra il seguente metodo e quello pubblicato sopra:

public static Application getApplicationUsingReflection() throws Exception {
    return (Application) Class.forName("android.app.AppGlobals")
            .getMethod("getInitialApplication").invoke(null, (Object[]) null);
} 

Buona programmazione!


56

Supponendo che stiamo parlando di ottenere il contesto dell'applicazione, l'ho implementato come suggerito dall'estensione dell'applicazione @Rohit Ghatol. Ciò che è accaduto allora è che non esiste alcuna garanzia che il contesto recuperato in questo modo sarà sempre non nullo. Nel momento in cui ne hai bisogno, è di solito perché vuoi inizializzare un aiutante, o ottenere una risorsa, che non puoi ritardare in tempo; la gestione del caso null non ti aiuterà. Quindi ho capito che stavo fondamentalmente combattendo contro l'architettura Android, come affermato nei documenti

Nota: 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), includi Context.getApplicationContext () come argomento Context quando invochi il metodo getInstance () del tuo singleton.

e spiegato da Dianne Hackborn

L'unica ragione per cui l'applicazione esiste come qualcosa da cui puoi derivare è perché durante lo sviluppo pre-1.0 uno dei nostri sviluppatori di applicazioni mi ha continuamente infastidito sulla necessità di avere un oggetto di applicazione di livello superiore da cui possano derivare in modo da poter avere un "più normale "a loro modello di applicazione, e alla fine mi sono arreso. Mi pentirò per sempre di averlo abbandonato. :)

Sta anche suggerendo la soluzione a questo problema:

Se quello che vuoi è uno stato globale che può essere condiviso tra diverse parti della tua app, usa un singleton. [...] E questo porta più naturalmente a come dovresti gestire queste cose - inizializzandole su richiesta.

quindi quello che ho fatto è stato sbarazzarmi dell'estensione dell'applicazione e passare il contesto direttamente al getInstance () dell'helper singleton, salvando un riferimento al contesto dell'applicazione nel costruttore privato:

private static MyHelper instance;
private final Context mContext;    

private MyHelper(@NonNull Context context) {
    mContext = context.getApplicationContext();
}

public static MyHelper getInstance(@NonNull Context context) {
    synchronized(MyHelper.class) {
        if (instance == null) {
            instance = new MyHelper(context);
        }
        return instance;
    }
}

il chiamante passerà quindi un contesto locale all'helper:

Helper.getInstance(myCtx).doSomething();

Quindi, per rispondere correttamente a questa domanda: ci sono modi per accedere staticamente al contesto dell'applicazione, ma tutti dovrebbero essere scoraggiati e dovresti preferire passare un contesto locale a getInstance () del singleton.


Per chiunque sia interessato, puoi leggere una versione più dettagliata sul blog di fwd


1
@Alessio Questo metodo non porta a perdite di memoria
Phillip Kigenyi,

2
@codephillip Non capisco di cosa stai parlando. Il singleton fa riferimento al contesto dell'applicazione recuperato dall'attività passata, non all'attività host. È legittimo e non causerà alcuna perdita di memoria. Questo è il punto principale del blog che ho scritto. Se pensi davvero di avere ragione, ti prego di inviarmi un codice di esempio in cui posso riprodurre la perdita di memoria di cui stai parlando, perché non è così.
Alessio,

1
Penso che @KigenyiPhillip sia corretto e ciò rappresenta ancora una perdita di risorse. Immagina la tabella di riferimento dopo la prima chiamata a getInstance(ctx). Hai una radice GC instancedi tipo MyHelper, che ha un campo privato mContextdi tipo Context, che fa riferimento al contesto dell'applicazione raccolto tramite il contesto passato a getInstance(). instancenon viene mai impostato una seconda volta, né cancellato, quindi GC non rileverà mai l'appcontesto a cui fa riferimento instance. Non perdi alcuna attività, quindi è IMO a basso costo.
Mark McKenna,

1
@MarkMcKenna come dici "che ha un campo privato mContext di tipo Context, che fa riferimento al contesto dell'applicazione", quindi è chiaro per te che mContext è un riferimento al contesto dell'applicazione, non a qualsiasi contesto. Nei documenti getApplicationContext () leggi: "un contesto il cui ciclo di vita è separato dal contesto corrente, che è legato alla durata del processo piuttosto che al componente corrente". Come può creare una perdita di memoria? Il contesto dell'applicazione è GC'd solo quando il processo termina.
Alessio

1
@Alessio se si accetta che un riferimento al contesto dell'applicazione non si qualifica come una perdita di risorse, è possibile semplificarlo pubblicando un riferimento statico in thisin Application.onCreate(), che rende migliore la risposta accettata.
Mark McKenna,


38

Ecco un modo non documentato per ottenere un'applicazione (che è un contesto) da qualsiasi punto del thread dell'interfaccia utente. Si basa sul metodo statico nascosto ActivityThread.currentApplication(). Dovrebbe funzionare almeno su Android 4.x.

try {
    final Class<?> activityThreadClass =
            Class.forName("android.app.ActivityThread");
    final Method method = activityThreadClass.getMethod("currentApplication");
    return (Application) method.invoke(null, (Object[]) null);
} catch (final ClassNotFoundException e) {
    // handle exception
} catch (final NoSuchMethodException e) {
    // handle exception
} catch (final IllegalArgumentException e) {
    // handle exception
} catch (final IllegalAccessException e) {
    // handle exception
} catch (final InvocationTargetException e) {
    // handle exception
}

Si noti che è possibile che questo metodo restituisca null, ad esempio quando si chiama il metodo al di fuori del thread dell'interfaccia utente o l'applicazione non è associata al thread.

È ancora meglio usare la soluzione di @RohitGhatol se è possibile modificare il codice dell'applicazione.


1
Ho usato il metodo sopra KennyTM, ma a volte il metodo restituisce null. C'è qualche altra alternativa a questo? Come se ottenessimo un null qui, possiamo recuperare il contesto da altrove. Nel mio caso, onCreate () dell'applicazione non viene chiamato. Ma il metodo sopra viene chiamato prima di esso. Guida di Plzzz
Android Acquista

Ciò non funzionerà sempre nel caso in cui GC abbia ripulito tutti gli elementi relativi alle attività.
AlexVPerl,

32

Dipende da cosa stai usando il contesto. Posso pensare ad almeno uno svantaggio di quel metodo:

Se stai cercando di creare un AlertDialogcon AlertDialog.Builder, il Applicationcontesto non funzionerà. Credo che tu abbia bisogno del contesto per l'attuale Activity...


6
Giusto. Se usi il contesto dell'applicazione per questo, potresti vedere la tua finestra di dialogo nascosta sotto le attività in primo piano.
Nate,

3
+1 prima di tutto. E il possibile errore che viene è Impossibile avviare l'attività ComponentInfo {com.samples / com.MyActivity}: android.view.WindowManager $ BadTokenException: Impossibile aggiungere la finestra - token null non è per un'applicazione
Govind

15

Modo di Kotlin :

Manifesto:

<application android:name="MyApplication">

</application>

MyApplication.kt

class MyApplication: Application() {

    override fun onCreate() {
        super.onCreate()
        instance = this
    }

    companion object {
        lateinit var instance: MyApplication
            private set
    }
}

È quindi possibile accedere alla proprietà tramite MyApplication.instance


11

Se sei aperto all'utilizzo di RoboGuice , puoi inserire il contesto in qualsiasi classe desideri. Ecco un piccolo esempio di come farlo con RoboGuice 2.0 (beta 4 al momento in cui scrivo)

import android.content.Context;
import android.os.Build;
import roboguice.inject.ContextSingleton;

import javax.inject.Inject;

@ContextSingleton
public class DataManager {
    @Inject
    public DataManager(Context context) {
            Properties properties = new Properties();
            properties.load(context.getResources().getAssets().open("data.properties"));
        } catch (IOException e) {
        }
    }
}

8

L'ho usato ad un certo punto:

ActivityThread at = ActivityThread.systemMain();
Context context = at.getSystemContext();

Questo è un contesto valido che ho usato per ottenere i servizi di sistema e funzionare.

Ma l'ho usato solo nelle modifiche al framework / base e non l'ho provato nelle applicazioni Android.

Un avvertimento che devi sapere: quando ti registri per i ricevitori broadcast in questo contesto, non funzionerà e otterrai:

java.lang.SecurityException: dato il pacchetto chiamante android non è in esecuzione ProcessRecord


7

Kotlin

open class MyApp : Application() {
    override fun onCreate() {
        super.onCreate()
        mInstance = this
    }

    companion object {
        lateinit var mInstance: MyApp
        fun getContext(): Context? {
            return mInstance.applicationContext
        }
    }
}

e ottieni un contesto simile

MyApp.mInstance

o

MyApp.getContext()

4

È possibile utilizzare quanto segue:

MainActivity.this.getApplicationContext();

MainActivity.java:

...
public class MainActivity ... {
    static MainActivity ma;
...
    public void onCreate(Bundle b) {
         super...
         ma=this;
         ...

Qualsiasi altra classe:

public ...
    public ANY_METHOD... {
         Context c = MainActivity.ma.getApplicationContext();

3
Questo funziona solo se ti trovi all'interno di una classe interna, il che è quasi impossibile nel PO.
Richard J. Ross III,

3
Ciò funzionerebbe fino a quando viene chiamato ANY_METHOD dopo la creazione di MainActivity, ma mantenere i riferimenti statici alle attività introduce quasi inevitabilmente perdite di memoria (come già menzionato da altre risposte alla domanda dell'OP), quindi se è necessario mantenere un riferimento statico, utilizzare l'applicazione solo contesto.
handtwerk

1
Le classi interiori sono cattive. La parte peggiore è che molte persone lo fanno per AsyncTasks e cose del genere, perché molti tutorial lo fanno in questo modo ...
Reinherd,

4

Se non desideri modificare il file manifest, puoi archiviare manualmente il contesto in una variabile statica nella tua attività iniziale:

public class App {
    private static Context context;

    public static void setContext(Context cntxt) {
        context = cntxt;
    }

    public static Context getContext() {
        return context;
    }
}

E basta impostare il contesto all'avvio della tua attività (o attività):

// MainActivity

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    // Set Context
    App.setContext(getApplicationContext());

    // Other stuff
}

Nota: come tutte le altre risposte, questa è una potenziale perdita di memoria.


1
Quale sarà esattamente la perdita poiché il contesto in questo caso è associato all'applicazione? Se l'applicazione muore, così fa tutto il resto.
TheRealChx101,

3

Penso che tu abbia bisogno di un corpo per il getAppContext()metodo:

public static Context getAppContext()
   return MyApplication.context; 

3

Secondo questa fonte è possibile ottenere il proprio contesto estendendo ContextWrapper

public class SomeClass extends ContextWrapper {

    public SomeClass(Context base) {
      super(base);
    }

    public void someMethod() {
        // notice how I can use "this" for Context
        // this works because this class has it's own Context just like an Activity or Service
        startActivity(this, SomeRealActivity.class);

        //would require context too
        File cacheDir = getCacheDir();
    }
}

JavaDoc per ContextWrapper

Implementazione proxy del contesto che delega semplicemente tutte le sue chiamate a un altro contesto. Può essere sottoclassato per modificare il comportamento senza cambiare il contesto originale.


1
Questo è interessante. Buono a sapersi su ContextWrapper. Tuttavia, se è necessario passare nel contesto dell'applicazione a questo costruttore, è comunque necessario recuperarlo da qualche parte.
jk7,

2

Se per qualche motivo si desidera il contesto dell'applicazione in qualsiasi classe, non solo quelle che estendono l'applicazione / attività, forse per alcune classi factory o helper. Puoi aggiungere il seguente singleton alla tua app.

public class GlobalAppContextSingleton {
    private static GlobalAppContextSingleton mInstance;
    private Context context;

    public static GlobalAppContextSingleton getInstance() {
        if (mInstance == null) mInstance = getSync();
        return mInstance;
    }

    private static synchronized GlobalAppContextSingleton getSync() {
        if (mInstance == null) mInstance = 
                new GlobalAppContextSingleton();
        return mInstance;
    }

    public void initialize(Context context) {
        this.context = context;
    }

    public Context getApplicationContext() {
        return context;
    }
}

quindi inizializzalo in onCreate della tua classe di applicazione con

GlobalAppContextSingleton.getInstance().initialize(this);

usalo ovunque chiamando

GlobalAppContextSingleton.getInstance().getApplicationContext()

Non consiglio questo approccio a tutto tranne che al contesto dell'applicazione. Poiché può causare perdite di memoria.


Non è come se i nomi di classe / metodo fossero impostati su pietra, mantenuti lunghi e (si spera) descrittivi per una domanda e risposta, abbreviati per uso personale.
Versa,

1

Uso una variante del modello di progettazione Singleton per aiutarmi in questo.

import android.app.Activity;
import android.content.Context;

public class ApplicationContextSingleton {
    private static Activity gContext;

    public static void setContext( Activity activity) {
        gContext = activity;
    }

    public static Activity getActivity() {
        return gContext;
    }

    public static Context getContext() {
        return gContext;
    }
}

Chiamo quindi ApplicationContextSingleton.setContext( this );in my activity.onCreate () e ApplicationContextSingleton.setContext( null );in onDestroy () ;


Se tutto ciò che serve è il contesto, puoi chiamare activity.getApplicationContext (); Che può essere trattenuto staticamente senza doversi preoccupare di perdite.
MinceMan,

2
questo produrrà perdite di memoria
BlueWizard il

1

Ho appena rilasciato un framework ispirato a jQuery per Android chiamato Vapor API che mira a semplificare lo sviluppo di app.

La $classe di facciata centrale mantiene un WeakReference(link al fantastico post sul blog Java su questo di Ethan Nicholas) al Activitycontesto attuale che puoi recuperare chiamando:

$.act()

UN WeakReference mantiene un riferimento senza impedire alla garbage collection di recuperare l'oggetto originale, quindi non dovresti avere problemi con le perdite di memoria.

Il rovescio della medaglia ovviamente è che si corre il rischio che $.act()potrebbe restituire null. Non ho ancora incontrato questo scenario, quindi è forse solo un rischio minimo, degno di nota.

Puoi anche impostare il contesto manualmente se non stai usando VaporActivitycome Activityclasse:

$.act(Activity);

Inoltre, gran parte del framework API di Vapor utilizza intrinsecamente questo contesto memorizzato, il che potrebbe significare che non è necessario memorizzarlo da soli se si decide di utilizzare il framework. Dai un'occhiata al sito per ulteriori informazioni e campioni.

Spero che aiuti :)


1
Apparentemente questo è appena stato sottratto .. una spiegazione sarebbe bella !?
Dario il

1
Non ho votato a fondo questo, ma Javascript non ha nulla a che fare con la domanda a portata di mano, questo spiegherebbe qualsiasi voto che potresti aver avuto! Saluti.
Ernani Joppert,

Sarebbe piuttosto privo di senso dato che è ispirato da alcuni aspetti di jQuery come un'interfaccia fluida e dalle sue astrazioni .. questi sono principi agnostici del linguaggio sottostante!
Dario,

1
Quindi lo stai ridimensionando perché è stato ispirato dalla semantica API di un framework che non è sulla stessa piattaforma ?! Penso che voi ragazzi perdiate il punto di applicare i principi agnostici della piattaforma .....................................
Dario

3
questa risposta è totalmente estranea a JavaScript. Leggi la risposta prima di votare: /
BlueWizard,

1

La risposta di Rohit sembra corretta. Tuttavia, tieni presente che "Instant Run" di AndroidStudio dipende dal fatto di non avere static Contextattributi nel tuo codice, per quanto ne so.


1
Hai ragione. E si tradurrà anche in perdite di memoria!
user1506104

1

in Kotlin, mettere Contesto / Contesto app nell'oggetto associato produce comunque un avviso Do not place Android context classes in static fields; this is a memory leak (and also breaks Instant Run)

o se usi qualcosa del genere:

    companion object {
        lateinit var instance: MyApp
    }

Sta semplicemente ingannando la lanugine per non scoprire la perdita di memoria, l'istanza dell'App può ancora produrre perdita di memoria, poiché la classe Application e il suo discendente è un contesto.

In alternativa, puoi utilizzare l'interfaccia funzionale o le proprietà funzionali per aiutarti a ottenere il contesto della tua app.

Basta creare una classe di oggetti:

object CoreHelper {
    lateinit var contextGetter: () -> Context
}

o potresti usarlo in modo più sicuro usando il tipo nullable:

object CoreHelper {
    var contextGetter: (() -> Context)? = null
}

e nella tua classe App aggiungi questa riga:


class MyApp: Application() {

    override fun onCreate() {
        super.onCreate()
        CoreHelper.contextGetter = {
            this
        }
    }
}

e nel tuo manifest dichiarare il nome dell'app a . MyApp


    <application
            android:name=".MyApp"

Quando vuoi ottenere il contesto, chiama semplicemente:

CoreHelper.contextGetter()

// or if you use the nullable version
CoreHelper.contextGetter?.invoke()

Spero che possa aiutare.


La classe di oggetti di questo corehelper verrà inizializzata e potrà essere utilizzata attraverso le attività in una fase successiva? Mi dispiace, sono nuovo di Kotlin
Dr. aNdRO,

si, esattamente.
Hayi Nukman,

-1

Prova qualcosa del genere

import androidx.appcompat.app.AppCompatActivity;  
import android.content.Context; 
import android.os.Bundle;

public class MainActivity extends AppCompatActivity {
    private static Context context;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        context = getApplicationContext();
    }

    public static void getContext(View view){
        Toast.makeText(context, "Got my context!",
                    Toast.LENGTH_LONG).show();    
    }
}
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.