Maneira estática de obter 'Contexto' no Android?

970

Existe uma maneira de obter a Contextinstância atual dentro de um método estático?

Estou procurando esse caminho porque odeio salvar a instância 'Context' sempre que ela muda.

Andrea Baccega
fonte
57
Não salvar o Contexto é uma boa ideia, não apenas porque é inconveniente, mas mais porque pode levar a grandes vazamentos de memória!
Vikram Bodicherla
12
@VikramBodicherla Sim, mas as respostas abaixo assumem que estamos falando sobre o contexto do aplicativo. Portanto, vazamentos de memória não são um problema, mas o usuário só deve usar essas soluções onde esse for o contexto correto a ser usado.
Tom
Se você precisar usar uma maneira estática de obter Context, pode haver uma maneira melhor de criar o código.
Anonsage
3
A documentação do Android recomenda passar o contexto para getters de singletons. developer.android.com/reference/android/app/Application.html
Marco Luglio
Para preferir singletons e o contexto transmitidos com getInstance () sobre o contexto estático, tente dar uma olhada, tentei explicar meu raciocínio aqui suportado pelo código de funcionamento: stackoverflow.com/a/38967293/4469112
Alessio

Respostas:

1302

Faça isso:

No arquivo Android Manifest, declare o seguinte.

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

</application>

Em seguida, escreva a 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;
    }
}

Agora, em qualquer lugar, ligue MyApplication.getAppContext()para obter o contexto do aplicativo estaticamente.

Rohit Ghatol
fonte
81
Existe alguma desvantagem nesse método? Isso parece trapaça. (A hack?)
jjnguy
203
A desvantagem é que não há garantia de que onCreate () não estático tenha sido chamado antes que algum código de inicialização estático tente buscar seu objeto Context. Isso significa que seu código de chamada precisará estar pronto para lidar com valores nulos, o que meio que derrota o ponto principal desta questão.
Melinda Green
8
Também talvez .. devemos declarar essa static contextvariável como volatile?
Vladimir Sorokin 31/03
14
@ Tom Este não é um caso de um membro de dados estático ser inicialmente estaticamente. No código fornecido, o membro estático está sendo inicializado não estaticamente em onCreate (). Mesmo dados inicializados estaticamente não são bons o suficiente nesse caso, porque nada garante que a inicialização estática de uma determinada classe ocorra antes de ser acessada durante a inicialização estática de alguma outra classe.
Melinda Green
10
@MelindaGreen De acordo com a documentação do Aplicativo, onCreate () é chamado antes que qualquer atividade, serviço ou destinatário (excluindo provedores de conteúdo) tenha sido criado. Portanto, essa solução não seria segura desde que você não estivesse tentando acessar getAppContext () de um provedor de conteúdo?
Magnus W
86

A maioria dos aplicativos que deseja um método conveniente para obter o contexto do aplicativo cria sua própria classe, que se estende android.app.Application .

GUIA

Você pode fazer isso criando primeiro uma classe em seu projeto, como a seguir:

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;
    }
}

Em seu AndroidManifest, você deve especificar o nome da sua classe na tag do AndroidManifest.xml:

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

Você pode recuperar o contexto do aplicativo em qualquer método estático usando o seguinte:

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

ATENÇÃO

Antes de adicionar algo como o acima ao seu projeto, considere o que a documentação diz:

Normalmente, não há necessidade de subclassificar Application. Na maioria das situações, singletons estáticos podem fornecer a mesma funcionalidade de uma maneira mais modular. Se o seu singleton precisar de um contexto global (por exemplo, para registrar receptores de transmissão), a função para recuperá-lo poderá receber um Contexto que use internamente Context.getApplicationContext () ao construir o singleton pela primeira vez.


REFLEXÃO

Também há outra maneira de obter o contexto do aplicativo usando a reflexão. Muitas vezes, a reflexão é menosprezada no Android e, pessoalmente, acho que isso não deve ser usado na produção.

Para recuperar o contexto do aplicativo, devemos chamar um método em uma classe oculta ( ActivityThread ) que está disponível desde a API 1:

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

Há mais uma classe oculta ( AppGlobals ) que fornece uma maneira de obter o contexto do aplicativo de maneira estática. Ele obtém o contexto usando, ActivityThreadentão realmente não há diferença entre o método a seguir e o postado acima:

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

Feliz codificação!

Jared Rummler
fonte
56

Supondo que estamos falando sobre como obter o Contexto do Aplicativo, eu o implementei conforme sugerido pelo @Rohit Ghatol, que estende o Aplicativo. O que aconteceu, então, é que não há garantia de que o contexto recuperado dessa maneira sempre será não nulo. No momento em que você precisa, geralmente é porque você deseja inicializar um auxiliar ou obter um recurso que você não pode atrasar no tempo; lidar com o caso nulo não o ajudará. Então eu entendi que estava basicamente lutando contra a arquitetura Android, conforme declarado nos documentos

Nota: Normalmente não há necessidade de subclassificar Aplicativo. Na maioria das situações, singletons estáticos podem fornecer a mesma funcionalidade de uma maneira mais modular. Se o seu singleton precisar de um contexto global (por exemplo, para registrar receptores de transmissão), inclua Context.getApplicationContext () como argumento de Contexto ao chamar o método getInstance () do seu singleton.

e explicado por Dianne Hackborn

A única razão pela qual o Aplicativo existe como algo de que você pode derivar é porque, durante o desenvolvimento anterior à 1.0, um de nossos desenvolvedores de aplicativos me incomodava continuamente por precisar de um objeto de aplicativo de nível superior do qual eles pudessem derivar, para que pudessem ter uma aparência mais "normal". "para o modelo de aplicação, e acabei cedendo. Vou me arrepender para sempre de ceder a esse. :)

Ela também está sugerindo a solução para este problema:

Se o que você deseja é um estado global que possa ser compartilhado em diferentes partes do seu aplicativo, use um singleton. [...] E isso leva mais naturalmente a como você deve gerenciar essas coisas - inicializando-as sob demanda.

então o que eu fiz foi me livrar da extensão Application e passar o contexto diretamente para o getInstance () do auxiliar singleton, enquanto salvava uma referência ao contexto do aplicativo no construtor privado:

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;
    }
}

o chamador passará um contexto local para o auxiliar:

Helper.getInstance(myCtx).doSomething();

Portanto, para responder adequadamente a essa pergunta: existem maneiras de acessar o Contexto do Aplicativo estaticamente, mas todas elas devem ser desencorajadas, e você deve preferir passar um contexto local para o getInstance do singleton ().


Para qualquer pessoa interessada, você pode ler uma versão mais detalhada no blog fwd

Alessio
fonte
1
@Alessio não faz este método levar a vazamentos de memória
Phillip Kigenyi
2
@ codephillip Não entendo do que você está falando. O singleton faz referência ao contexto do aplicativo recuperado da atividade passada, não da atividade do host. Isso é legítimo e não causará nenhum vazamento de memória. Esse é o ponto principal do blog que escrevi. Se você realmente acha que está certo, envie-me um código de exemplo onde eu possa reproduzir o vazamento de memória que você está falando, porque esse não é o caso.
Alessio
1
Acho que o @KigenyiPhillip está correto, e isso ainda representa um vazamento de recursos. Imagine a tabela de referência após sua primeira chamada para getInstance(ctx). Você tem uma raiz instancedo tipo GC MyHelper, que possui um campo particular mContextdo tipo Context, que faz referência ao contexto do aplicativo coletado por meio do contexto passado para getInstance(). instancenunca é definido pela segunda vez, nem limpo, para que o GC nunca pegue o contexto do aplicativo mencionado por instance. Você não vaza nenhuma atividade, por isso é IMO de baixo custo.
Mark McKenna
1
@MarkMcKenna como você declara "que possui um campo privado mContext do tipo Context, que faz referência ao contexto do aplicativo", portanto, é claro para você que o mContext é uma referência ao contexto do aplicativo, e não a qualquer contexto. Nos documentos getApplicationContext (), você lê: "um Contexto cujo ciclo de vida é separado do contexto atual, vinculado à vida útil do processo, e não ao componente atual". Como isso pode criar um vazamento de memória? O contexto do aplicativo é GC'd somente quando o processo termina.
Alessio
1
@Alessio, se você aceitar que uma referência ao contexto do aplicativo não se qualifica como um vazamento de recursos, você pode simplificar isso postando uma referência estática em thisin Application.onCreate(), o que torna a resposta aceita melhor.
Mark McKenna
49

Não, acho que não existe. Infelizmente, você não consegue ligar getApplicationContext()de Activityou de uma das outras subclasses de Context. Além disso, esta questão está um pouco relacionada.

Erich Douglass
fonte
8
O link certo para o artigo: android-developers.blogspot.co.il/2009/01/…
Tal Weiss
38

Aqui está uma maneira não documentada de obter um Aplicativo (que é um Contexto) de qualquer lugar no thread da interface do usuário. Ele se baseia no método estático oculto ActivityThread.currentApplication(). Deve funcionar pelo menos no 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
}

Observe que é possível que esse método retorne nulo, por exemplo, quando você chama o método fora do thread da interface do usuário ou o aplicativo não está vinculado ao thread.

Ainda é melhor usar a solução do @RohitGhatol se você puder alterar o código do aplicativo.

kennytm
fonte
1
Eu usei o método acima KennyTM, mas às vezes o método retorna nulo. Existe alguma outra alternativa para isso? Como se obtivermos um nulo aqui, podemos recuperar o contexto de outro lugar. No meu caso, onCreate () of Application não é chamado. Mas o método acima é chamado antes dele. Ajuda plzzz
AndroidGuy
Isso nem sempre funcionará no caso em que o GC limpou todos os itens relacionados à atividade.
AlexVPerl
32

Depende do que você está usando o contexto. Eu posso pensar em pelo menos uma desvantagem para esse método:

Se você estiver tentando criar um AlertDialogcom AlertDialog.Builder, o Applicationcontexto não funcionará. Eu acredito que você precisa do contexto para o atual Activity...

gulchrider
fonte
6
Está certo. Se você usar o contexto do aplicativo para isso, poderá ver sua caixa de diálogo oculta em atividades em primeiro plano.
Nate
3
+1 antes de tudo. E o possível erro que surge é Não é possível iniciar a atividade ComponentInfo {com.samples / com.MyActivity}: android.view.WindowManager $ BadTokenException: não é possível adicionar janela - o token nulo não é para um aplicativo
Govind
15

Kotlin way :

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
    }
}

Você pode acessar a propriedade via MyApplication.instance

phnmnn
fonte
11

Se você estiver aberto para usar o RoboGuice , poderá injetar o contexto em qualquer classe que desejar. Aqui está uma pequena amostra de como fazer isso com o RoboGuice 2.0 (beta 4 no momento da redação deste artigo)

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) {
        }
    }
}
user605331
fonte
8

Eu usei isso em algum momento:

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

Esse é um contexto válido que eu usei para obter serviços do sistema e trabalhei.

Mas, usei apenas em modificações de estrutura / base e não tentei em aplicativos Android.

Um aviso que você deve saber: Ao se registrar para receptores de transmissão com esse contexto, ele não funcionará e você receberá:

java.lang.SecurityException: dado que o pacote de chamadas android não está sendo executado no processo ProcessRecord

ungalcrys
fonte
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 obtenha um contexto como

MyApp.mInstance

ou

MyApp.getContext()
Khemraj
fonte
4

Você pode usar o seguinte:

MainActivity.this.getApplicationContext();

MainActivity.java:

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

Qualquer outra classe:

public ...
    public ANY_METHOD... {
         Context c = MainActivity.ma.getApplicationContext();
barwnikk
fonte
3
Isso só funciona se você estiver dentro de uma classe interna, o que dificilmente ocorre no OP.
Richard J. Ross III
3
Isso funcionaria desde que o ANY_METHOD seja chamado após a criação da MainActivity, mas manter referências estáticas às atividades quase inevitavelmente introduz vazamentos de memória (como outras respostas à pergunta do OP já mencionam), portanto, se você realmente precisar manter uma referência estática, use o aplicativo apenas contexto.
handtwerk
1
Classes internas são más. A pior parte é que um monte de gente fazer isso para AsyncTasks e coisas assim, porque muitos tutoriais fazê-lo dessa maneira ...
Reinherd
4

Se você não quiser modificar o arquivo de manifesto, poderá armazenar manualmente o contexto em uma variável estática em sua atividade inicial:

public class App {
    private static Context context;

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

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

E apenas defina o contexto quando sua atividade (ou atividades) iniciar:

// MainActivity

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

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

    // Other stuff
}

Nota: Como todas as outras respostas, esse é um possível vazamento de memória.

Sheharyar
fonte
1
O que exatamente estará vazando, já que o contexto neste caso está vinculado ao aplicativo? Se o aplicativo morre, o mesmo acontece com todo o resto.
TheRealChx101 12/12/19
3

Eu acho que você precisa de um corpo para o getAppContext()método:

public static Context getAppContext()
   return MyApplication.context; 
Kognos
fonte
3

De acordo com esta fonte, você pode obter seu próprio Contexto estendendo o 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 for ContextWrapper

Proxicar a implementação do Contexto que simplesmente delega todas as suas chamadas para outro Contexto. Pode ser subclassificado para modificar o comportamento sem alterar o contexto original.

BlueWizard
fonte
1
Isto é interessante. É bom aprender sobre o ContextWrapper. No entanto, se você precisar passar o contexto do aplicativo para esse construtor, ainda precisará obtê-lo de algum lugar.
Jk7
2

Se, por algum motivo, você desejar o contexto Aplicativo em qualquer classe, não apenas aqueles que estendem o aplicativo / atividade, talvez para algumas classes de fábrica ou auxiliares. Você pode adicionar o seguinte singleton ao seu aplicativo.

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;
    }
}

em seguida, inicialize-o no onCreate da sua classe de aplicativo com

GlobalAppContextSingleton.getInstance().initialize(this);

use-o em qualquer lugar chamando

GlobalAppContextSingleton.getInstance().getApplicationContext()

No entanto, não recomendo essa abordagem para nada além do contexto do aplicativo. Como isso pode causar vazamentos de memória.

Versa
fonte
Não é como se os nomes de classe / método fossem definidos, mantidos por muito tempo e (esperançosamente) descritivos para uma sessão de perguntas e respostas, abreviados para meu próprio uso.
Versa
1

Eu uso uma variação do padrão de design Singleton para me ajudar com isso.

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;
    }
}

Então eu ligo ApplicationContextSingleton.setContext( this ); no meu activity.onCreate () e ApplicationContextSingleton.setContext( null );em onDestroy () ;

Bamaco
fonte
Se tudo o que você precisa é de contexto, você pode chamar activity.getApplicationContext (); Isso pode ser mantido estaticamente sem a necessidade de se preocupar com vazamentos.
MinceMan
2
isto produzirá vazamentos de memória
BlueWizard
1

Acabei de lançar um framework inspirado em jQuery para Android chamado Vapor API que visa simplificar o desenvolvimento de aplicativos.

A Central $ classe de fachada mantém um WeakReference(link para o incrível post do blog sobre Java de Ethan Nicholas) sobre o Activitycontexto atual que você pode recuperar chamando:

$.act()

UMA WeakReference mantém uma referência sem impedir que a coleta de lixo recupere o objeto original, para que você não tenha problemas com vazamentos de memória.

A desvantagem, claro, é que você corre o risco de $.act()retornar nulo. Ainda não me deparei com esse cenário, talvez seja apenas um risco mínimo, que vale a pena mencionar.

Você também pode definir o contexto manualmente se não estiver usando VaporActivity como sua Activityclasse:

$.act(Activity);

Além disso, grande parte dos estrutura da API Vapor usa esse contexto armazenado de forma inerente, o que pode significar que você não precisa armazená-lo sozinho se decidir usar a estrutura. Confira o site para mais informações e amostras.

Espero que ajude :)

Darius
fonte
1
Aparentemente, isso acabou com o voto negativo. Uma explicação seria legal !?
Dario
1
Eu não reduzi o voto disso, mas o Javascript não tem nada a ver com a pergunta em questão, que explicaria todos os votos negativos que você já teve! Felicidades.
Ernani Joppert
Isso seria bastante absurdo, uma vez que é inspirado por alguns aspectos do jQuery, como uma interface fluente e suas abstrações ... esses são princípios independentes da linguagem subjacente!
Darius
1
Então você está votando negativamente porque foi inspirado pela semântica da API de uma estrutura que não está na mesma plataforma ?! Eu acho que vocês perdem o ponto de aplicar princípios agnósticos de plataforma .....................................
Darius
3
esta resposta não tem nenhuma relação com JavaScript. Leia a resposta antes que você
diminua o voto
1

A resposta de Rohit parece correta. No entanto, saiba que o "Instant Run" do AndroidStudio depende de não ter static Contextatributos no seu código, tanto quanto eu saiba.

Payne
fonte
1
Você está certo. E isso também resultará em vazamentos de memória!
user1506104
1

no Kotlin, colocar o Contexto / Contexto do Aplicativo no objeto complementar ainda gera aviso Do not place Android context classes in static fields; this is a memory leak (and also breaks Instant Run)

ou se você usar algo como isto:

    companion object {
        lateinit var instance: MyApp
    }

É simplesmente enganar o fiapo para não descobrir o vazamento de memória, a instância do App ainda pode produzir vazamento de memória, pois a classe Application e seu descendente são um Contexto.

Como alternativa, você pode usar a interface funcional ou as propriedades funcionais para ajudá-lo a obter o contexto do aplicativo.

Basta criar uma classe de objeto:

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

ou você pode usá-lo com mais segurança usando o tipo anulável:

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

e na sua classe App, adicione esta linha:


class MyApp: Application() {

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

e no seu manifesto, declare o nome do aplicativo como . MyApp


    <application
            android:name=".MyApp"

Quando você quiser obter o contexto, basta ligar para:

CoreHelper.contextGetter()

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

Espero que ajude.

Hayi Nukman
fonte
A classe de objeto deste corehelper será inicializada e poderá ser usada em atividades posteriores? Desculpe, eu sou novo no kotlin
Dr. aNdRO
sim, exatamente.
Hayi Nukman
-1

Tente algo como isto

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();    
    }
}
chandra shekar
fonte