Usando o contexto do aplicativo em qualquer lugar?

476

Em um aplicativo Android, há algo errado com a seguinte abordagem:

public class MyApp extends android.app.Application {

    private static MyApp instance;

    public MyApp() {
        instance = this;
    }

    public static Context getContext() {
        return instance;
    }

}

e passá-lo para todos os lugares (por exemplo, SQLiteOpenHelper) onde o contexto é necessário (e não está vazando, é claro)?

yanchenko
fonte
23
Apenas a elaborar para os outros de execução do presente, você pode então modificar o <application>nó do seu arquivo AndroidManifest.xml para incluir a seguinte definição de atributo: android:name="MyApp". O MyApp precisa estar no mesmo pacote que seu manifesto faz referência.
quer
6
Maneira IMPRESSIONANTE de contornar o problema de fornecer um contexto para o SQLiteOpenHelper !! Eu implementei um singleton "SQLiteManager" e fiquei preso em "como eu consigo um contexto para o singleton?"
Alguém em algum lugar
8
Para que você saiba que está retornando seu aplicativo por uma de suas super interfaces, se você forneceu métodos adicionais no MyApp, não poderá usá-los. Em vez disso, seu getContext () deve ter um tipo de retorno de MyApp e, dessa forma, você pode usar os métodos adicionados posteriormente, bem como todos os métodos em ContextWrapper e Context.
5
Veja também goo.gl/uKcFn - é outra resposta relacionada a post semelhante. Melhor definir a variável estática em onCreate e não c'tor.
AlikElzin-Kilaka
1
@ChuongPham Se o quadro já matou seu aplicativo, não haverá nada acessando o contexto nulo ...
Kevin Krumwiede

Respostas:

413

Existem alguns problemas em potencial com essa abordagem, embora em muitas circunstâncias (como no seu exemplo) funcione bem.

Em particular, você deve ter cuidado ao lidar com qualquer coisa que lide com o GUIque exige a Context. Por exemplo, se você passar o aplicativo Context para o LayoutInflaterdiretório, receberá uma exceção. De um modo geral, sua abordagem é excelente: é uma boa prática usar um Activity's Contextdentro disso Activitye Application Contextao passar um contexto além do escopo de um Activitypara evitar vazamentos de memória .

Além disso, como alternativa ao seu padrão, você pode usar o atalho para chamar getApplicationContext()um Contextobjeto (como uma Atividade) para obter o Contexto do Aplicativo.

Reto Meier
fonte
22
Obrigado por uma resposta inspiradora. Acho que vou usar essa abordagem exclusivamente para a camada de persistência (como não quero ir com os provedores de conteúdo). Pensando em qual era a motivação por trás do design do SQLiteOpenHelper de uma maneira que espera que um Contexto seja fornecido em vez de adquiri-lo do próprio Aplicativo. PS E seu livro é ótimo!
Yanchenko
7
Usar o contexto do aplicativo LayoutInflatorapenas funcionou para mim. Deve ter sido alterado nos últimos três anos.
Jacob Phillips
5
@JacobPhillips O uso do LayoutInflator sem um contexto de atividade perderá o estilo dessa atividade. Portanto, funcionaria em um sentido, mas não em outro.
Mark
1
@MarkCarter Você quer dizer que usar o Contexto do Aplicativo perderá o estilo da Atividade?
21414 Jacob
1
@JacobPhillips sim, o Contexto do Aplicativo não pode ter o estilo, porque cada Atividade pode ter um estilo diferente.
Mark
28

Na minha experiência, essa abordagem não deve ser necessária. Se você precisar do contexto para qualquer coisa, normalmente é possível obtê-lo por meio de uma chamada para View.getContext () e, usando o resultado Contextobtido, pode chamar Context.getApplicationContext () para obter o Applicationcontexto. Se você estiver tentando obter o Applicationcontexto disso a partir de um, Activityvocê sempre pode chamar Activity.getApplication (), que deve ser transmitido conforme o Contextnecessário para uma chamada SQLiteOpenHelper().

No geral, não parece haver um problema com sua abordagem para essa situação, mas ao lidar com Contextapenas verifique se você não está vazando memória em nenhum lugar, conforme descrito no blog oficial do Google Android Developers .

snctln
fonte
13

Algumas pessoas perguntaram: como o singleton pode retornar um ponteiro nulo? Eu estou respondendo a essa pergunta. (Não consigo responder em um comentário porque preciso postar código.)

Pode retornar nulo entre dois eventos: (1) a classe é carregada e (2) o objeto dessa classe é criado. Aqui está um exemplo:

class X {
    static X xinstance;
    static Y yinstance = Y.yinstance;
    X() {xinstance=this;}
}
class Y {
    static X xinstance = X.xinstance;
    static Y yinstance;
    Y() {yinstance=this;}
}

public class A {
    public static void main(String[] p) {
    X x = new X();
    Y y = new Y();
    System.out.println("x:"+X.xinstance+" y:"+Y.yinstance);
    System.out.println("x:"+Y.xinstance+" y:"+X.yinstance);
    }
}

Vamos rodar o código:

$ javac A.java 
$ java A
x:X@a63599 y:Y@9036e
x:null y:null

A segunda linha mostra que Y.xinstance e X.yinstance são nulos ; eles são nulos porque as variáveis X.xinstance e Y.yinstance foram lidas quando eram nulas.

Isso pode ser corrigido? Sim,

class X {
    static Y y = Y.getInstance();
    static X theinstance;
    static X getInstance() {if(theinstance==null) {theinstance = new X();} return theinstance;}
}
class Y {
    static X x = X.getInstance();
    static Y theinstance;
    static Y getInstance() {if(theinstance==null) {theinstance = new Y();} return theinstance;}
}

public class A {
    public static void main(String[] p) {
    System.out.println("x:"+X.getInstance()+" y:"+Y.getInstance());
    System.out.println("x:"+Y.x+" y:"+X.y);
    }
}

e este código não mostra nenhuma anomalia:

$ javac A.java 
$ java A
x:X@1c059f6 y:Y@152506e
x:X@1c059f6 y:Y@152506e

MAS isso não é uma opção para o Applicationobjeto Android : o programador não controla a hora em que é criado.

Mais uma vez: a diferença entre o primeiro exemplo e o segundo é que o segundo exemplo cria uma instância se o ponteiro estático for nulo. Mas um programador não pode criar o objeto de aplicativo Android antes que o sistema decida fazê-lo.

ATUALIZAR

Mais um exemplo intrigante, onde estão os campos estáticos inicializados null.

Main.java :

enum MyEnum {
    FIRST,SECOND;
    private static String prefix="<", suffix=">";
    String myName;
    MyEnum() {
        myName = makeMyName();
    }
    String makeMyName() {
        return prefix + name() + suffix;
    }
    String getMyName() {
        return myName;
    }
}
public class Main {
    public static void main(String args[]) {
        System.out.println("first: "+MyEnum.FIRST+" second: "+MyEnum.SECOND);
        System.out.println("first: "+MyEnum.FIRST.makeMyName()+" second: "+MyEnum.SECOND.makeMyName());
        System.out.println("first: "+MyEnum.FIRST.getMyName()+" second: "+MyEnum.SECOND.getMyName());
    }
}

E você obtém:

$ javac Main.java
$ java Main
first: FIRST second: SECOND
first: <FIRST> second: <SECOND>
first: nullFIRSTnull second: nullSECONDnull

Observe que você não pode mover a declaração da variável estática uma linha para cima, o código não será compilado.

18446744073709551615
fonte
3
Exemplo útil; é bom saber que existe esse buraco. O que retiro disso é que devemos evitar nos referir a uma variável estática durante a inicialização estática de qualquer classe.
Home
10

Classe de aplicação:

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

public class MyApplication extends Application {

    private static Context mContext;

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

    public static Context getAppContext() {
        return mContext;
    }

}

Declare o aplicativo no AndroidManifest:

<application android:name=".MyApplication"
    ...
/>

Uso:

MyApplication.getAppContext()
toha
fonte
1
Propenso a vazamentos de memória. Você nunca deveria fazer isso.
Dragas
9

Você está tentando criar um wrapper para obter o Contexto do Aplicativo e existe a possibilidade " null" de retornar o ponteiro.

De acordo com o meu entendimento, acho que é melhor abordagem para chamar qualquer um dos 2 Context.getApplicationContext() ou Activity.getApplication().

Prasanta
fonte
13
quando deve retornar nulo?
Preso
25
Não há nenhum método estático Context.getApplicationContext () que eu saiba. Estou esquecendo de algo?
dalcantara
Também implementei a mesma abordagem no meu aplicativo, mas ao chamar SQLiteOpenHelper, ele retorna o ponteiro nulo. Qualquer resposta para esse tipo de situação.
11382 ashutosh
2
Pode ser esse o caso se você chamar SQLiteOpenHelper em um provedor de conteúdo carregado antes do aplicativo.
Gunnar Bernstein
5

É uma boa abordagem. Eu também o uso. Eu apenas sugeriria substituir onCreatepara definir o singleton em vez de usar um construtor.

E desde que você mencionou SQLiteOpenHelper: onCreate ()você também pode abrir o banco de dados.

Pessoalmente, acho que a documentação errou ao dizer que normalmente não há necessidade de subclassificar Application . Eu acho que o oposto é verdadeiro: você deve sempre subclassificar Application.

Martin
fonte
3

Eu usaria o Contexto do Aplicativo para obter um Serviço do Sistema no construtor. Isso facilita os testes e os benefícios da composição

public class MyActivity extends Activity {

    private final NotificationManager notificationManager;

    public MyActivity() {
       this(MyApp.getContext().getSystemService(NOTIFICATION_SERVICE));
    }

    public MyActivity(NotificationManager notificationManager) {
       this.notificationManager = notificationManager;
    }

    // onCreate etc

}

A classe de teste usaria o construtor sobrecarregado.

O Android usaria o construtor padrão.

Blundell
fonte
1

Eu gosto, mas sugiro um singleton:

package com.mobidrone;

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

public class ApplicationContext extends Application
{
    private static ApplicationContext instance = null;

    private ApplicationContext()
    {
        instance = this;
    }

    public static Context getInstance()
    {
        if (null == instance)
        {
            instance = new ApplicationContext();
        }

        return instance;
    }
}
Franklin Peña
fonte
31
Estendendo android.app.application já garante Singleton então isso é desnecessário
Vincent
8
E se você quiser acessar as classes que não são de atividade?
Maxrunner
9
Você nunca deve fazer newo aplicativo sozinho (com a possível exceção dos testes de unidade). O sistema operacional fará isso. Você também não deve ter um construtor. É para isso que onCreateserve.
Martin
@Incent: você pode postar algum link sobre isso? código de preferência - eu estou perguntando aqui: stackoverflow.com/questions/19365797/…
Mr_and_Mrs_D
@radzio por que não devemos fazê-lo no construtor?
precisa saber é o seguinte
1

Estou usando a mesma abordagem, sugiro escrever o singleton um pouco melhor:

public static MyApp getInstance() {

    if (instance == null) {
        synchronized (MyApp.class) {
            if (instance == null) {
                instance = new MyApp ();
            }
        }
    }

    return instance;
}

mas não estou usando em todos os lugares, eu uso getContext()e getApplicationContext()onde posso fazer isso!

Serafim
fonte
Então, escreva um comentário para explicar por que você recusou a resposta para que eu possa entender. A abordagem singleton é amplamente utilizado para obter um contexto de atividades externas ou vistas corpo válida ...
de Seraphim
1
Não há necessidade, pois o sistema operacional garante que o Aplicativo seja instanciado exatamente uma vez. Se houver, sugiro definir o Singelton em onCreate ().
Martin
1
Uma boa maneira segura de thread para inicializar preguiçosamente um singleton, mas não é necessário aqui.
NaXa
2
Uau, só quando eu pensei que as pessoas finalmente tinha parado de usar bloqueio duplo verificado ... cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html
Søren Boisen