Singletons vs. Contexto do Aplicativo no Android?

362

Lembrando esta postagem enumerando vários problemas do uso de singletons e tendo visto vários exemplos de aplicativos Android usando o padrão singleton, pergunto-me se é uma boa idéia usar Singletons em vez de instâncias únicas compartilhadas pelo estado global do aplicativo (subclassificando android.os.Application e obtê-lo através de context.getApplication ()).

Que vantagens / desvantagens teriam ambos os mecanismos?

Para ser sincero, espero a mesma resposta neste post Padrão Singleton com aplicativo Web, Não é uma boa ideia! mas aplicado ao Android. Estou correcto? O que há de diferente no DalvikVM?

EDIT: Gostaria de ter opiniões sobre vários aspectos envolvidos:

  • Sincronização
  • Reutilização
  • Teste
mschonaker
fonte

Respostas:

295

Discordo muito da resposta de Dianne Hackborn. Estamos removendo pouco a pouco todos os singletons do nosso projeto em favor de objetos leves e com escopo de tarefas que podem ser facilmente recriados quando você realmente precisa deles.

Os singletons são um pesadelo para os testes e, se forem inicializados com preguiça, introduzirão o "indeterminismo de estado" com efeitos colaterais sutis (que podem surgir repentinamente ao mover chamadas getInstance()de um escopo para outro). A visibilidade foi mencionada como outro problema e, como os singletons implicam o acesso "global" (= aleatório) ao estado compartilhado, erros sutis podem surgir quando não são sincronizados adequadamente em aplicativos simultâneos.

Eu considero isso um antipadrão, é um estilo orientado a objetos ruim que basicamente significa manter o estado global.

Para voltar à sua pergunta:

Embora o contexto do aplicativo possa ser considerado um único singleton, ele é gerenciado por estrutura e possui um ciclo de vida , escopo e caminho de acesso bem definidos . Por isso, acredito que, se você precisar gerenciar o estado global do aplicativo, ele deve ir aqui, em nenhum outro lugar. Para qualquer outra coisa, repense se você realmente precisa de um objeto singleton ou se também seria possível reescrever sua classe singleton para instanciar objetos pequenos e de curta duração que executam a tarefa em questão.

Matthias
fonte
131
Se você está recomendando o Aplicativo, está recomendando o uso de singletons. Honestamente, não há maneira de contornar isso. A aplicação é um singleton, com semântica de baixa qualidade. Não entrarei em discussões religiosas sobre singletons, algo que você nunca deve usar. Prefiro ser prático - há lugares em que eles são uma boa opção para manter o estado por processo e podem simplificar as coisas ao fazê-lo, e você também pode usá-los na situação errada e dar um tiro no pé.
hackbod
18
É verdade que mencionei que o "contexto do aplicativo pode ser considerado um único singleton". A diferença é que, com a instância do aplicativo, dar um tiro no pé é muito mais difícil, já que seu ciclo de vida é tratado pela estrutura. Estruturas de DI como Guice, Hivemind ou Spring também usam singletons, mas esse é um detalhe de implementação que o desenvolvedor não deve se importar. Eu acho que geralmente é mais seguro confiar na semântica do framework sendo implementada corretamente, e não no seu próprio código. Sim, eu admito que sim! :-)
Matthias
93
Honestamente, isso não impede que você se atire no pé mais do que um singleton. É um pouco confuso, mas não ciclo de vida do aplicativo. Ele é criado quando o aplicativo é iniciado (antes que qualquer um de seus componentes seja instanciado) e o onCreate () chamado nesse ponto e ... isso é tudo. Ele fica lá e vive para sempre, até que o processo seja morto. Como um singleton. :)
hackbod
30
Ah, uma coisa que pode confundir isso - o Android é muito projetado para executar aplicativos em processos e gerenciar o ciclo de vida desses processos. Portanto, os singletons do Android são uma maneira muito natural de tirar proveito desse gerenciamento de processos - se você deseja armazenar em cache algo em seu processo até que a plataforma precise recuperar a memória do processo para outra coisa, colocar esse estado em um singleton fará isso.
hackbod
7
Ok, justo o suficiente. Só posso dizer que não olhei para trás desde que nos afastamos dos singletons autogerenciados. Agora estamos optando por uma solução leve no estilo DI, onde mantemos um singleton de fábrica (RootFactory), que por sua vez é gerenciado pela instância do aplicativo (é um delegado, se você quiser). Esse singleton gerencia dependências comuns em que todos os componentes do aplicativo dependem, mas a instanciação é gerenciada em um único local - a classe do aplicativo. Enquanto essa abordagem permanece um singleton, ele fica confinado à classe Application, portanto, nenhum outro módulo de código sabe sobre esse "detalhe".
Matthias
231

Eu recomendo muito singletons. Se você tem um singleton que precisa de um contexto, tenha:

MySingleton.getInstance(Context c) {
    //
    // ... needing to create ...
    sInstance = new MySingleton(c.getApplicationContext());
}

Eu prefiro singletons do que Application, porque ajuda a manter um aplicativo muito mais organizado e modular - em vez de ter um lugar em que todo o seu estado global no aplicativo precisa ser mantido, cada peça separada pode se cuidar. Também é bom o fato de os singletons inicializarem preguiçosamente (a pedido) em vez de levá-lo no caminho de fazer toda a inicialização antecipada em Application.onCreate ().

Não há nada intrinsecamente errado em usar singletons. Apenas use-os corretamente, quando fizer sentido. A estrutura do Android, na verdade, possui muitas delas, para manter caches por processo de recursos carregados e outras coisas.

Também para aplicativos simples, o multithreading não se torna um problema para os singletons, porque, por design, todos os retornos de chamada padrão do aplicativo são despachados no encadeamento principal do processo, para que você não aconteça o multi-threading, a menos que o introduza explicitamente através de encadeamentos ou implicitamente, publicando um IBinder de provedor ou serviço de conteúdo para outros processos.

Apenas pense bem no que está fazendo. :)

hackbod
fonte
11
Se algum tempo depois eu quiser ouvir um evento externo ou compartilhar em um IBinder (acho que não seria um aplicativo simples), eu teria que adicionar bloqueio duplo, sincronização, volátil, certo? Obrigado pela resposta :)
mschonaker
2
Não é para um evento externo - BroadcastReceiver.onReceive () também é chamado no thread principal.
hackbod
2
OK. Você me indicaria algum material de leitura (eu preferiria o código) onde posso ver o mecanismo principal de envio de threads? Eu acho que isso vai esclarecer vários conceitos de uma só vez para mim. Desde já, obrigado.
mschonaker
2
Este é o principal código do lado do aplicativo despachar: android.git.kernel.org/?p=platform/frameworks/...
hackbod
8
Não há nada intrinsecamente errado em usar singletons. Apenas use-os corretamente, quando fizer sentido. .. claro, precisamente, bem dito. A estrutura do Android, na verdade, tem muitas delas, para manter caches por processo de recursos carregados e outras coisas. Exatamente como você diz. Dos seus amigos no mundo iOS, "tudo é um singleton" no iOS. Nada poderia ser mais natural em dispositivos físicos do que um conceito de singleton: os gps, o relógio, os giroscópios, etc. etc - conceitualmente, de que outra forma você poderia como singletons? Então sim.
Fattie
22

De: desenvolvedor> referência - aplicativo

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.

Somatik
fonte
11
E se você escrever uma interface para o singleton, deixando getInstance não estático, você pode até tornar o construtor padrão da classe de uso singleton injetar o singleton de produção através de um construtor não padrão, que também é o construtor usado para criar o classe singleton-using em seus testes de unidade.
Android.weasel
11

O aplicativo não é o mesmo que o Singleton. Os motivos são:

  1. O método do aplicativo (como onCreate) é chamado no thread da interface do usuário;
  2. o método singleton pode ser chamado em qualquer thread;
  3. No método "onCreate" do aplicativo, você pode instanciar o manipulador;
  4. Se o singleton for executado no thread none-ui, você não poderá instanciar Handler;
  5. O aplicativo tem a capacidade de gerenciar o ciclo de vida das atividades no aplicativo.Tem o método "registerActivityLifecycleCallbacks" .Mas os singletons não têm a capacidade.
sunhang
fonte
11
Nota: você pode instanciar Handler em qualquer thread. do doc: "Quando você cria um novo manipulador, ele é vinculado à fila de encadeamentos / mensagens do encadeamento que o está criando"
Christ
11
@Christ Obrigado! Agora mesmo eu aprendi "mecanismo do Looper" .Se instanciar manipulador no segmento none-ui sem o código 'Looper.prepare ()', o sistema reportará o erro "java.lang.RuntimeException: Can crie manipulador dentro do thread que não tenha chamado Looper.prepare () ".
sunhang
11

Eu tive o mesmo problema: Singleton ou criar uma subclasse android.os.Application?

Primeiro, tentei com o Singleton, mas meu aplicativo, em algum momento, chama o navegador

Intent myIntent = new Intent(Intent.ACTION_VIEW, Uri.parse("http://www.google.com"));

e o problema é que, se o aparelho não tiver memória suficiente, a maioria das suas aulas (até Singletons) é limpa para obter um pouco de memória; portanto, ao retornar do navegador para o meu aplicativo, ele falha sempre.

Solução: coloque os dados necessários dentro de uma subclasse da classe Application.

JoséMi
fonte
11
Muitas vezes me deparei com posts em que as pessoas afirmam que isso pode ocorrer. Portanto, simplesmente anexo objetos ao aplicativo, como singletons com carregamento lento etc., apenas para garantir que o ciclo de vida seja documentado e conhecido. Certifique-se de não salvar centenas de imagens no objeto do aplicativo, pois eu entendo que ele não será limpo da memória se o aplicativo estiver em segundo plano e todas as atividades forem destruídas para liberar memória para outros processos.
Janusz
Bem, o carregamento lento de Singleton após a reinicialização do aplicativo não é o caminho certo para permitir que os objetos sejam varridos pelo GC. As WeakReferences são, certo?
mschonaker
15
Mesmo? Dalvik descarrega classes e perde o estado do programa? Tem certeza de que não é uma coleta de lixo o tipo de objetos relacionados à atividade de ciclo de vida limitado que você não deveria colocar em singletons em primeiro lugar? Você tem que dar exemplos clrar para uma afirmação tão extraordinária!
Android_weasel
11
A menos que haja mudanças que eu desconheço, o Dalvik não descarrega classes. Sempre. O comportamento que eles estão vendo é o processo sendo morto em segundo plano para dar espaço ao navegador. Eles provavelmente estavam inicializando a variável em sua atividade "principal", que pode não ter sido criada no novo processo ao retornar do navegador.
Groxx
5

Considere os dois ao mesmo tempo:

  • tendo objetos singleton como instâncias estáticas dentro das classes.
  • ter uma classe comum (Context) que retorna as instâncias singleton para todos os objetos singelton em seu aplicativo, o que tem a vantagem de que os nomes dos métodos em Contexto serão significativos, por exemplo: context.getLoggedinUser () em vez de User.getInstance ().

Além disso, sugiro que você expanda seu Contexto para incluir não apenas o acesso a objetos singleton, mas algumas funcionalidades que precisam ser acessadas globalmente, como por exemplo: context.logOffUser (), context.readSavedData (), etc. Provavelmente renomeando o Contexto para Fachada faria sentido então.

adranale
fonte
4

Eles são realmente iguais. Há uma diferença que eu posso ver. Com a classe Application, você pode inicializar suas variáveis ​​em Application.onCreate () e destruí-las em Application.onTerminate (). Com o singleton, você precisa confiar na inicialização e destruição de estática da VM.

Fedor
fonte
16
os documentos do onTerminate dizem que ele só é chamado pelo emulador. Em dispositivos, esse método provavelmente não será chamado. developer.android.com/reference/android/app/…
danb
3

Meus 2 centavos:

Notei que alguns campos estáticos / singleton foram redefinidos quando minha atividade foi destruída. Notei isso em alguns dispositivos 2.3 low-end.

Meu caso foi muito simples: eu apenas tenho um arquivo "init_done" particular e um método estático "init" que chamei de activity.onCreate (). Percebo que o método init estava se re-executando em alguma recriação da atividade.

Embora eu não possa provar minha afirmação, ela pode estar relacionada a QUANDO o singleton / classe foi criado / usado primeiro. Quando a atividade é destruída / reciclada, parece que todas as classes que apenas essa atividade se referem também são recicladas.

Mudei minha instância de singleton para uma subclasse de aplicativo. Eu os acesso a partir da instância do aplicativo. e, desde então, não percebeu o problema novamente.

Espero que isso possa ajudar alguém.

Cristo
fonte
3

Da boca do proverbial cavalo ...

Ao desenvolver seu aplicativo, você pode achar necessário compartilhar dados, contexto ou serviços globalmente em seu aplicativo. Por exemplo, se seu aplicativo tiver dados de sessão, como o usuário conectado no momento, é provável que você exponha essas informações. No Android, o padrão para resolver esse problema é ter sua instância android.app.Application como proprietária de todos os dados globais e, em seguida, tratar sua instância de Aplicativo como um singleton com acessadores estáticos para os vários dados e serviços.

Ao escrever um aplicativo Android, você garante apenas uma instância da classe android.app.Application e, portanto, é seguro (e recomendado pela equipe do Google Android) tratá-lo como um singleton. Ou seja, você pode adicionar com segurança um método estático getInstance () à sua implementação de Aplicativo. Igual a:

public class AndroidApplication extends Application {

    private static AndroidApplication sInstance;

    public static AndroidApplication getInstance(){
        return sInstance;
    }

    @Override
    public void onCreate() {
        super.onCreate();
        sInstance = this;
    }
}
RMcGuigan
fonte
2

Minha atividade chama finish () (que não faz com que termine imediatamente, mas acabará eventualmente) e chama o Google Street Viewer. Quando eu depuro no Eclipse, minha conexão com o aplicativo é interrompida quando o Street Viewer é chamado, que eu entendo como o aplicativo (inteiro) sendo fechado, supostamente para liberar memória (pois uma única atividade concluída não deve causar esse comportamento) . No entanto, sou capaz de salvar o estado em um Bundle via onSaveInstanceState () e restaurá-lo no método onCreate () da próxima atividade na pilha. Usando um Aplicativo estático de singleton ou subclasse, eu enfrento o estado de fechamento e perda do aplicativo (a menos que eu o salve em um Pacote configurável). Então, pela minha experiência, eles são os mesmos em relação à preservação do estado. Percebi que a conexão está perdida no Android 4.1.2 e 4.2.2, mas não no 4.0.7 ou 3.2.4,

Piovezan
fonte
"Percebi que a conexão está perdida no Android 4.1.2 e 4.2.2, mas não no 4.0.7 ou 3.2.4, o que, no meu entender, sugere que o mecanismo de recuperação de memória mudou em algum momento". ..... Eu acho que seus dispositivos não têm a mesma quantidade de memória disponível, nem o mesmo aplicativo instalado. e, portanto, sua conclusão pode estar incorreta #
236
@Christ: Sim, você deve estar certo. Seria estranho se o mecanismo de recuperação de memória mudasse entre as versões. Provavelmente, o uso diferente da memória causou comportamentos distintos.
Piovezan