Caso prefira, você encontrará todo esse material, em inglês, no site do Developer Android. A tradução e comentários dos materiais eu faço livremente para ajudar a comunidade que fala português.

sábado, 26 de março de 2011

0 comentários

Atividades - Gerenciando Tarefas

A forma como o Android gerencia as tarefas e o back stack - colocando as atividades em uma sucessão na mesma tarefa e usando o conceito last in, first out - funciona perfeitamente para a maioria das aplicações e você não deveria se preocupar sobre como as atividades são associadas às tarefas ou como elas existem no back stack. Contudo, você deve decidir se quer interromper o comportamento padrão. Talvez você queira que uma atividade em sua aplicação comece uma nova tarefa quando ela é iniciada (ao invés de ser colocada como a tarefa corrente); ou, quando você inicia uma atividade, você queira trazer para a frente uma instância existente dela (ao invés de criar uma nova instância no topo do back stack); ou talvez você queira que seu back stack sera limpo de todas as atividades e só tenha uma atividade (a atividade root) quando o usuário saia da tarefa.

Você pode fazer isso e muitas outras coisas usando o atributo <activity> que se encontra no manifesto do Android e com flags em seus intents que você passa para o startActivity().

Nesse sentido, os atributos principals de <activity> que você pode usar são:

taskAffinity
launchMode
allowTaskReparenting
clearTaskOnLaunch
alwaysRetainsTaskState
finishOnTaskLaunch

E as flags de intent que você pode usar são:

FLAG_ACTIVITY_NEW_TASK
FLAG_ACTIVITY_CLEAR_TOP
FLAG_ACTIVITY_SINGLE_TOP

Definindo os modos de lançamento (launch modes)

Launch modes permitem a você definir ua nova instância de uma atividade que está associada à tarefa corrente. Você pode definir diferentes launch modes de duas maneiras:

Usando o arquivo de manifesto
Quando você declara uma atividade em seu arquivo de manifesto, você pode manifestar como a atividade deverá ser associada com tarefa quando ela se inicia

Usando intent flags
Quando você chama startActivity(), você pode incluir a flag dentro do Intent que declara como (ou se) uma nova atividade deverá ser associada com a tarefa corrente.

Ou seja, se a atividade A inicia a atividade B, a atividade B pode definir em seu manifesto como ela deverá ser associada com a tarefa corrente e a atividade A pode também requisitar como a atividade B deve ser associada com a tarefa corrente. Se ambas atividades definem como a atividade B deveria ser associada com a tarefa, então a requisição da atividade A (como definida em seu intent) é honrada sobre a requisição da atividade B.

Usando o arquivo de manifesto

Quando se declara uma atividade no arquivo de manifesto, você pode especificar como a atividade estará associada com a tarefa usando o elemento <activity> dentro do atributo launchMode.

O atributo launchMode especifica uma instrução sobre como a atividade deve ser lançada dentro da tarefa. Existem quatro tipos diferentes de launch modes que você pode assinalar no atributo launchMode:

"standard" (o modo padrão)
O sistema cria uma nova instância da atividade na tarefa a partir da tarefa já existente e faz a rota do intent para ele. A atividade pode ser instanciada multiplas vezes e cada instância pode pertencer a diferentes tarefas e uma tarefa pode ter multiplas intâncias.

"singleTop"
Se uma instância de uma atividade já existe no topo da atividade corrente, o sistema faz o roteamento do intent para a instância através de uma chamada para o método onNewIntent(), ao invés de criar uma nova instância da atividade. A atividade pode ser instanciada multiplas vezes e cada instância pode pertencer a tarefas diferentes e uma tarefa pode ter multiplas instâncias (mas apenas se a atividade que estiver no topo do back stack não for uma instância existente da atividade).

Por exemplo, suponha que o back stack de uma tarefa consista da atividade raiz de A com as atividades B, C e D no topo (o stack ficaria como A-B-C-D onde D está no topo). Um intent chega para a atividade do tipo D. Se D tem o modo de launch mode em standard, uma nova instância da classe é lançada e o back stack se torna A-B-C-D-D. Contudo, se a atividade D estiver em modo singleTop, a instância existente de D é entregue ao intent através de onNewIntent(), já que está no topo do back stack - o back stack continua como A-B-C-D. Mas, se um intent chega para a atividade do tipo B, então uma nova instância de B é adicionada ao back stack, mesmo que o launch mode esteja em singleTop, fazendo com que o back stack fique como A-B-C-D-B.

"singleTask"
O sistema cria uma nova tarefa e instancia a atividade na raiz da nova tarefa. Contudo, se uma instância da atividade já existe em uma tarefa separada, o sistema faz o roteamento do intent para a instância existente através de uma chamada para o método onNewIntent(), ao invés de criar uma nova instância. Apenas uma instância de cada atividade pode existir a cada vez.

"singleInstance"
O mesmo que singleTask, exceto que o sistema não lança nenhuma outra atividade dentro da tarefa que está relacionada à instância. A atividade é simple única e o único membro de sua tarefa; quaisquer outras atividades iniciadas são apenas em uma tarefa separada.

Como outro exemplo, a aplicação de navegador do Android declara que a atividade de navegação deve sempre abrir sua própria tarefa - ela especifica que é singleTask no lanch mode em seu elemento <activity>. Isso significa que se sua aplicação tem um intent para abrir o navegador, a atividade não é colocada na mesma tarefa de sua aplicação. Ao invés disso, ele inicia uma tarefa nova apenas para o navegador ou, se o navegador já está aberto, a tarefa é chamada para a tela para receber a requisição.

Independente da atividade iniciar uma nova tarefa ou não ou a mesma tarefa como atividade ser aberta, o BACK sempre volta para a atividade anterior. Apesar disso ser verdade, se você iniciar uma atividade a partir de sua tarefa (Task A) que especifica o launch mode como singleTask, então a sua atividade deve ter uma instância no background que pertença a tarefa com seu próprio back stack (Task B). Nesse caso, quando a Task B for lançada para a tela para receber o novo intent, o BACK navega por todas as atividades da Task B antes de retornar para a atividade que está no topo da Task A. A imagem abaixo ilustra esse cenário.


Usando Intent Flags

Quando você inicia uma atividade, você pode modificar a associação padrão de uma atividade para que sua tarefa incluindo flags de intent que são entregues no startActivity(). As flags que você pode usar para modificar o comportamento padrão são:

FLAG_ACTIVITY_NEW_TASK
Inicia a atividade em uma nova tarefa. Se a tarefa já está rodando para a atividade que você está iniciando agora, a tarefa é trazida para a frente com seu último estado restaurado e a atividade receber um novo intent em onNewIntent().

Essa flag produz o mesmo comportamento de "singleTask".

FLAG_ACTIVITY_SINGLE_TOP
Se uma atividade se inicia na atividade corrente (no topo do back stack), então a instância existente recebe a chamada para onNewIntent(), ao invés de criar uma nova instância da atividade.

Essa flag produz o mesmo comportamento de "singleTop".

FLAG_ACTIVITY_CLEAR_TOP
Se a atividade iniciada já está rodando na tarefa corrente, então ao invés de lançar uma nova instância da atividade, todas as outras atividades no topo são destruídas e o intent é entregue para a instância reiniciada da atividade (que agora está no topo), através do onNewIntent().

Não existe nenhum atributo de launchMode que produza esse comportamento.

Essa flag é mais comumente usada em conjunção com FLAG_ACTIVITY_NEW_TASK. Quando usadas em conjunto, essas flags são uma forma de localizar uma atividade existente em outra tarefa e colocá-la em uma posição onde possa responder a um intent.

Gerenciando afinidades

Uma affinity (afinidade) indica para qual tarefa uma atividade prefere pertencer. Por padrão, todas as atividades de uma mesma aplicação tem afinidade umas às outras. Então, por padrão, todas as atividades de uma aplicação preferem estar dentro da mesma tarefa. Mas, você pode modificar a afinidade padrão de uma atividade. Atividades definidas em aplicações diferentes podem compartilhar uma afinidade, ou atividades definidas na mesma aplicação podem ser marcadas para ter afinidades distintas.

Você pode modificar a atividade de qualquer dada atividade com o atributo taskAffinity dentro do elemento <activity>.

O atributo taskAffinity recebe um valor string que deve ser único do nome de pacote padrão declarado no elemento <manifest>, pois o sistema usa o nome para identificar a tarefa padrão de afinidade da aplicação.

A afinidade deve ser usada em duas circunstâncias:

  • Quando um intent que lança uma atividade contém a flag FLAG_ACTIVITY_NEW_TASK.

    Uma nova atividade é, por padrão, lançada dentro da tarefa da atividade que chamou o startActivity(). Ela é colocada no mesmo back stack de quem fez a sua chamada. Mas, se o intent passado para o startActivity() contém a flag FLAG_ACTIVITY_NEW_TASK, o sistema procura por uma tarefa diferente para hospedar a nova atividade. Em muitos casos, é uma nova tarefa a ser criada. Só que não precisa ser assim. Se já existe uma tarefa com a mesma afinidade da nova atividade, a atividade é lançada nessa tarefa. Se não existe, então é criada uma nova tarefa.

    Se essa flag fizer com que a atividade seja criada numa nova tarefa e o usuário pressionar o botão HOME, então deve haver alguma maneira do usuário navegar de volta à tarefa. Algumas entidades (como o gerenciador de notificação) sempre inicia atividades em uma tarefa externa, nunca como parte de sua própria tarefa e, por conta disso, eles sempre colocar a flag de nova tarefa em seus intents quando eles chamam startActivity(). Se você tem uma atividade que pode ser chamada por uma entidade externa que use essa flag, tome cuidado para que o usuário tenha uma maneira de voltar para a tarefa que foi iniciada.
  • Quando uma atividade tem o atributo allowTaskReparenting setado para true.

    Nesse caso, a atividade pode mover-se da tarefa que ela iniciou para uma tarefa que tenha afinidade quando a tarefa vai para a tela.

    Por exemplo, suponha que uma atividade que reporte as condições de tempo em cidades selecionadas sejam definidas como parte de uma aplicação de viagens. Ela tem a mesma afinidade de outras atividades na mesma aplicação e permite o re-parenting em seus atributos. Quando uma das atividades inicia a atividade de tempo, ele inicialmente pertence à mesma tarefa de sua atividade. Contudo, quando a tarefa da aplicação de viagens vem para a frente, a atividade de tempo é reassinalada (re-parented) para essa tarefa e mostrada dentro dela.
Limpando o back stack

Se o usuário deixar uma tarefa por um longo período, o sistema limpa a tarefa de todas as atividades exceto a atividade raiz. Quando o usuário retorna à tarefa, apenas a atividade raiz é restaurada. O sistema se comporta dessa maneira pois após um determinado tempo é muito provável que o usuário tenha abandonado o que eles estavam fazendo e iniciaram outra tarefa.

Existem alguns atributos de atividade que podem mudar esse comportamento:

alwaysRetainTaskState
Se esse atributo está setado como true na raiz da tarefa, o comportamento padrão mencionado acima não acontece. A tarefa mantém todas as atividades no stack mesmo após um longo período.

clearTaskOnLaunch
Se esse atributo está setado como true na raiz da tarefa, o stack é limpo e só mantém a atividade raiz quando o usuário deixa a tarefa e retorna a ela. Em outras palavras, é o oposto de alwaysRetainTaskState. O usuário sempre retorna à tarefa em seu estado inicial, mesmo depois de deixar a tarefa por apenas alguns instantes.

finishOnTaskLaunch
Esse atributo é parecido com clearTaskOnLaunch mas opera como uma atividade única e não uma tarefa. Ela pode inclusive causar a destruição da atividade incluindo a atividade raiz. Quando está setado para true, a atividade permanece parte da tarefa apenas dentro da sessão corrente. Se o usuário sair da tarefa e retornar, mesmo que imediatamente, a atividade não mais estará presente por lá.

Iniciando uma tarefa

Você pode configurar qual atividade é o ponto de entrada de uma tarefa provendo um intent filter com android.intent.action.MAIN como ação específica e android.intent.category.LAUNCHER como categoria específicada. 
<activity ... >
    <intent-filter ... >
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
    ...
</activity>
Um intent filter dessa natureza faz com que o ícone e o label da atividade sejam mostrados no launcher da aplicação, dando aos usuários uma maneira de lançar a atividade e retornar para a tarefa que ela cria a qualquer momento que seja lançada.

A segunda habilidade é importante: usuários podem ser permitidos a deixar a tarefa e então voltar para ela usando o activity launcher. Por essa razão, os launch modes singleTask e singleInstance devem ser usadas apenas quando a atividade tem um filtro ACTION_MAIN e um filtro CATEGORY_LAUNCHER. Imagine, por exemplo, o que pode acontecer caso um filtro desses esteja faltando: um intent lança uma atividade singleTask, iniciando uma nova tarefa e o usuário fica um tempo nela. O usuário então pressiona a tecla HOME. A tarefa é mandada para o background e fica invisível. Como não é representada no launcher, o usuário não tem mais como retornar à tarefa.

Agora, em casos em que você não quer que o usuário possa retornar à aplicação, configure o elemento finishOnTaskLaunch como true.

quarta-feira, 23 de março de 2011

0 comentários

Atividades - Tarefas e Back Stack

Uma aplicação usualmente contém multiplas atividades. Cada atividade deve ser desenhada para um tipo específico de ação que o usuário quer executar e cada atividade pode iniciar uma nova atividade. Por exemplo, uma aplicação de email pode ter uma atividade que mostra uma lista de novos emails. Quando o usuário seleciona um email, uma nova atividade abre para que o email seja mostrado.

Uma atividade pode também iniciar atividades que existem em outras aplicações no dispositivo. Por exemplo, se sua aplicação quer enviar um email, você pode definir um intent para executar a ação de enviar o email e incluir alguns dados, como o endereço de email e a mensagem a ser enviada. Uma atividade de outra aplicação que declara a si mesmo como capaz de executar tal tarefa então se abre. Nesse caso, o intent (ou intenção) é enviar um email, então a atividade que é responsável pela composição de email se abre (se multiplas atividades suportam a mesma intent, então o sistema deixa o usuário escolher qual utilizará). Assim que o email é enviado, sua atividade é volta à tela e para o usuário vai parecer que a atividade de envio de email é parte de sua aplicação. Mesmo que a atividade seja de uma aplicação diferente, o Android mantém essa experiência bastante fluída para o usuário, mantendo ambas atividades dentro de uma mesma tarefa.

Uma tarefa é uma coleção de atividades onde os usuários interagem fazendo certos trabalhos. As atividades são arranjadas numa pilha (a "back stack") na ordem em que as atividades são abertas.

O home screen do dispositivo é normalemente o ponto de partida onde quase todas as tarefas se iniciam. Quando o usuário toca o ícone de uma aplicação dentro do launcher (ou mesmo a partir de um atalho na área de trabalho), a aplicação é aberta e vem para a frente. Se nenhuma tarefa existir para a aplicação (a aplicação não foi usada recentemente), então uma nova tarefa é criada e a atividade principal (mais) para aquela aplicação se abre como a atividade raiz na pilha.

Quando a atividade corrente chama outra atividade, uma nova atividade é colocada no topo da pilha e ganha o foco. A atividade anterior permanece na pilha, mas está parada. Quando uma atividade pára, o sistema mantém o estado corrente de sua interface de usuário. Quando o usuário pressiona o botão BACK, a atividade corrente é retirada do topo da pilha (i.e. a atividade é destruída) e a atividade anterior se reinicia (o estado anterior da interface também é restaurado). Atividades na pilha nunca são rearranjadas, apenas inseridas ou retiradas da pilha - colocadas na pilha quando iniciada pela atividade atual e retirada quando o usuário pressiona BACK. Dessa maneira, o back stack funciona com a estrutura "last in, first out". Abaixo uma imagem que mostra o timeline mostrando o progresso de atividades dentro do back stack.


Se o usuário continuar a pressionar o botão BACK, então cada atividade na pilha será retirada e a atividade anterior será reaberta até que seja retornado para a home screen (ou qualquer outra atividade que estivesse sendo rodada quando a tarefa se iniciou). Quando todas as atividades são removidas da pilha, a tarefa não mais existe.

Uma tarefa é uma unidade coesa que pode ser movida para background quando o usuário inicia uma nova tarefa ou quando volta para a home screen, pressionando o botão HOME. Enquanto estiver em modo background, todas as atividades dentro da tarefa são paradas, mas o back stack para a tarefa se mantém intacto - a tarefa simplesmente perdeu o foco enquanto outra tarefa está sendo rodada, como mostrada na imagem acima. Uma tarefa pode então retornar para a tela para que usuários possam continuar do ponto onde pararam. Suponha, por exemplo, que a tarefa corrente (Task A) tem três atividades em sua pilha - dois dentro da atividade corrente. O usuário pressiona o botão HOME e inicia uma nova aplicação a partir do launcher. Quando a home screen aparece, a Task A vai para background e fica lá, aguardando ser chamada novamente. Quando a nova aplicação se inicia, o sistema inicia uma nova tarefa para essa aplicação (Task B) que tem sua própria pilha de atividades. Após interagir com a aplicação, o usuário retorna à home screen novamente e seleciona a aplicação que originalmente iniciou a tarefa Task A. Agora, Task A volta do background para a tela - todas as suas três atividades na pilha estão intactas e a atividade no topo da pilha se reinicia. Nesse ponto, o usuário pode também voltar para a Task B indo para a home screen e selecionando novamente a aplicação que iniciou a tarefa Task B. Esse é um exemplo de multitasking em Android.

Nota: Multiplas tarefas podem ser guardadas em background de uma vez. Contudo, se o usuário estiver rodando muitas tarefas em background de uma vez, o sistema pode começar a destruí-las para liberar recursos no dispositivo, causando a perda dessas atividades.

Como as atividades que ficam na back stack nunca são rearranjadas, se sua aplicação permitir iniciar uma atividade particular a partir de mais de uma atividade, uma nova instância para essa atividade é criada e inserida na pilha (ao invés de trazer a atividade já existente para o topo). Pode conta disso, uma atividade em sua aplicação pode ser instanciada multiplas vezes (mesmo a partir de diferentes tarefas). Por conta disso, quando o usuário navegar no sentido inverso usando o botão BACK, cada instancia da atividade é revelada na ordem inversa a que elas foram abertas. Você pode modificar o comportamento e veremos como fazê-lo mais tarde.

Para sumarizar o comportamento padrão das atividades e tarefas:

  • Quando a atividade A inicia a atividade B, a atividade A é parada, mas o sistema mantém seu estado. Se o usuário pressiona o botão BACK enquanto usa a atividade B, a atividade A reinicia-se com seu estado restaurado.
  • Quando o usuário deixa uma tarefa pressionando o botão HOME, a atividade corrente é parada e sua tarefa é enviada para background. O sistema mantém o estado de cada atividade dentro da tarefa. Se o usuário voltar para a tarefa, ela volta para a tela e reinicia a atividade que estiver no topo da pilha.
  • Se o usuário pressiona o botão de BACK, a atividade sendo executada é retirada da pilha e destruída. A atividade anterior é então reiniciada. Quando uma atividade é destruída, o sistema NÃO mantém seu estado de atividade.
  • Atividades podem ser instanciadas multiplas vezes, mesmo a partir de outras tarefas.
No próximo post, como gerenciar tarefas.

Atividades - Gerenciando o ciclo de vida da atividade

Gerenciar o ciclo de vida de suas atividades implementando métodos callback é crucial para desenvolver uma aplicação forte e flexível. O ciclo de vida de uma atividade é diretamente afetada por suas associações com outras atividades, suas tarefas e seu back stack.

Uma atividade pode existir em três estados essencialmente:

Resumed
A atividade está sendo executada na tela e o usuário tem o foco. Esse estado também é referido como "rodando"

Paused
Outra atividade está sendo executada na tela e tem o foco, mas a atividade pausada ainda está visível. Ou seja, outra atividade está visível mas parcialmente transparente e não cobre a tela inteira. Uma atividade pausada está completamente viva (o objeto Activity continua na memória e seu estado é mantido juntamente com as informações e membros e mantêm-se anexada ao gerenciador de janelas) mas pode ser morto pelo sistema em situações de memória extremamente baixas.

Stopped
A atividade está completamente obscurescida por outra atividade (a atividade agora está em modo "background"). Uma atividade parada também está viva (o objeto Activity continua na memória e seu estado é mantido juntamente com as informações e membros e NÃO mantêm-se anexada ao gerenciador de janelas). Contudo, ela não está mais visível ao usuário e pode ser morta pelo sistema caso memória seja requerida em outro processo.

Mas tudo isso já foi coberto no post do Ciclo de Vida da aplicação.

Implementando os callbacks do ciclo de vida

Quando uma transação de atividade entre em um dos estados descritos acima, ele é notificado através de vários métodos de callback. Todos os callbacks podem ser sobrescritos para fazer o trabalho apropriado quando seu estado de atividade muda. O esqueleto de atividades abaixo inclui cada um dos métodos fundamentais do ciclo de vida:

public class ExampleActivity extends Activity {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // A atividade está sendo criada
    }
    @Override
    protected void onStart() {
        super.onStart();
        // A atividade está para se tornar visível
    }
    @Override
    protected void onResume() {
        super.onResume();
        // A atividade se tornou visível (está em modo "resumed").
    }
    @Override
    protected void onPause() {
        super.onPause();
        // Outra atividade está ganhando o foco (está em modo "paused").
    }
    @Override
    protected void onStop() {
        super.onStop();
        //  A atividade não está mais visível (está "stopped")
    }
    @Override
    protected void onDestroy() {
        super.onDestroy();
        // A atividade está para ser destruída.
    }
}
Mais sobre os métodos em Ciclo de Vida da aplicação. Não deixe de ir lá pois há informações realmente válidas que ajudarão no entendimento desse tópico.

Salvando o estado da atividade


A introdução ao ciclo de vida da atividade rapidamente menciona que quando uma atividade está pausada ou parada, o estado da atividade é guardado. Isso é verdade pois o objeto de Activity é guardado na memória quando está pausado ou parado - todas as informações sobre seus membros e estado atual são mantidos vivos. Por isso, quaisquer alterações feitas pelo usuário dentro da atividade são mantidos em memória para que quando a atividade retorne para a tela (quando estiver em modo "resumed"), essas mudanças ainda estejam por lá.

Contudo, quando o sistema destroi uma atividade para recuperar memória, o objeto de Activity é destruído, então o sistema não pode simplesmente retornar ao estado anterior de maneira intacta. Ao invés disso, o sistema deve recriar o objeto se o usuário navegar de volta a ele. Ainda mais, o usuário não tem qualquer conhecimento de que o sistema destruiu a atividade e recriou-a e, na verdade, ele espera que a atividade esteja exatamente como estava anteriormente. Nessa situação, você pode assegurar que informações importantes sobre o estado de atividade estejam preservado implementando um método callback adicional que permite que você salve a informação sobre o estado da atividade e depois restaure-a quando o sistema precisar recriá-la.

O método callback que pode salvar informações sobre o estado atual de uma atividade é o onSaveInstanceState(). O sistema chama esse método antes de fazer com que a atividade esteja apta a ser destruída e passa esses dados para um objeto Bundle. O Bundle é onde você pode guardar informações de estado sobre a atividade como pares nome-valor, usando métodos como putString(). Então, se o sistema matar o processo de sua atividade e o usuário navegar de volta a ela, o sistema passa o Bundle para onCreate() para que quaisquer informações da atividade guardadas durante o uso de onSaveInstanceState() possam ser restauradas. Se não há nenhuma informação a ser restaurada, então o Bundle passado para o onCreate() é nulo (null).

Nota: Não há garantia que o onSaveInstanceState() seja chamado antes da atividade ser destruída, já que existem casos nos quais não será necessário salvar o estado (como quando um usuário deixa a atividade usando o botão de BACK em uma situação onde o usuário está explicitamente fechando a atividade). Se o método é chamado, ele deve ser chamado sempre antes de onStop() e possivelmente antes de onPause().

Contudo, mesmo quando você não faz nada ou não implementa o onSaveInstanceState(), algum dos estados de atividade são restaurados pela implementação padrão do onSaveInstanceState(). Especificamente há uma implementação padrão de onSaveInstanceState() para cada View do seu layout, que permite a cada View prover informações sobre si mesmo que devem ser salvas. Quase todo widget no framework Android implementa esse método como apropriado, tal como mudanças visíveis na interface que são automaticamente salvas e restauradas quando sua atividade é recriada. Por exemplo, o widget EditText salva qualquer texto que o usuário digitou dentro deste e o CheckBox salva a condição de estar ou não marcado. O único trabalho que você precisa ter é o de prover um ID único (com o atributo android:id) para cada widget que você quer ter o estado salvo. Se um widget não tem um ID, ele não terá seu estado salvo.

Apesar da implementação padrão de onSaveInstanceState() salvar dados úteis sobre a interface de uma atividade, você possivelmente ainda terá de sobrescrevê-lo para salvar informações adicionais. Por exemplo, você pode querer salvar valores que foram mudados durante o ciclo de vida da atividade.

Como a implementação padrão de onSaveInstanceState() ajuda a salvar o estado da interface, se você fizer a sobrescrita do método para salvar informações adicionais, você deve sempre chamar a implementação da supreclasse de onSaveInstanceState() antes de fazer qualquer trabalho adicional.

Nota: Como onSaveInstanceState() não é garantido de ser chamado, você deve usá-lo apenas para guardar o estado transiente da atividade - você NÃO deve usá-lo para guardar dados que precisam ser persistidos. Ao invés disso, você deve usar o onPause() para guardar dados que precisam ser persistidos (como dados que devem ser guardados em banco de dados).


Uma boa forma de testar a habilidade da aplicação para restaurar seu estado é simplesmente girar o seu dispositivo para que a orientação da tela mude. Quando a orientação muda, o sistema destrói e recria a atividade para que possa aplicar recursos alternativos que podem estar disponíveis na nova orientação. Por essa razão, é MUITO importante que sua atividade restaure completamente seu estado quando recriada, já que o usuário regularmente rotaciona a tela enquanto usa as aplicações.

segunda-feira, 21 de março de 2011

0 comentários

Atividades

Uma atividade é um componente de aplicação que provê uma tela onde o usuário poderá interagir para fazer alguma coisa como discar um número, bater uma foto, enviar um email ou ver um mapa. A cada atividade é dada uma janela na qual é desenhada a interface de usuário. A janela normalmente preenche toda a tela mas pode ser menor que a tela e flutuar sobre outras janelas.

Uma aplicação usualmente consiste de multiplas atividades que são pouco relacionadas a outras. Tipicamente, uma atividade é uma especificada como a principal (main), que é apresentada ao usuário quando ele chama a aplicação pela primeira vez. Cada atividade pode começar outra atividade para realizar diferentes ações. A cada vez que uma nova atividade se inicia, a atividade anterior é parada, mas o sistema preserva a atividade numa pilha (a chamada "back stack"). Quando uma nova atividade se inicia, ela é colocada na back stack e ganha o foco. O back stack usa o conceito de fila "last in, first out", então quando o usuário terminou a operação e pressiona o botão BACK, a atividade corrente é retirada do topo da pilha (e destruída) e a atividade anterior reinicia.

Quando uma atividade é parada em razão de outra atividade se iniciando, ela é notificada da mudança do seu estado através dos métodos de ciclo de vida da atividade. Existem diversos outros métodos de callback que a atividade pode receber, de acordo com a mudança de seu estado - seja quando o sistema esteja criando a aplicação ou parando-a, reiniciando-a ou a destruindo - e cada callback dá a você a oportunidade de realizar trabalhos específicos que são apropriados para a mudança de estado. Por exemplo, quando parado, a atividade deve liberar objetos de recursos como conexões de rede e bancos de dados. Quando uma atividade se reinicia, você pode capturar os recursos necessários e continuar a ação que foi interrompida. Essas transições de estado são parte do ciclo de vida da atividade.

Criando uma atividade

Para criar uma atividade, você deve criar uma subclasse de Activity (ou de uma subclasse existente). Na sua subclasse, você precisará implementar métodos callback que o sistema chama quando a atividade faz a transição entre os vários estados de seu ciclo de vida, como quando a atividade é criada, parada, reiniciada ou destruída. Os dois métodos callback mais importantes são:

onCreate()
Você deve implementar esse método. O sistema chama esse método quando está criando a atividade. Dentro de sua implementação, você deve inicializar os componentes essenciais para sua atividade. Mais importante ainda, é aqui que você vai chamar setContentView() para definir qual layout será a interface inicial de sua atividade (i.e. aplicação).

onPause()
O sistema chama esse método como a primeira indicação que o usuário está deixando sua atividade (apesar disso não significar que a atividade está sendo destruída). É aqui que você usualmente deverá fazer o commit para quaisquer mudanças que devam ser persistidas.

Existem outros métodos callback dentro do ciclo de vida e que você deverá usar para provêr ao usuário uma experiência fluída entre as atividades e gerenciar interrupções inexperadas que causar a parada ou destruíção de sua atividade. Os métodos de callback foram discutidos em post anterior.

Implementando a interface de usuário

A interface de usuário para uma atividade é provida por uma hierarquia de views - objetos derivados da classe View. Cada view controla um espaço retangular particular dentro da janela onde a atividade está presente e pode responder a interações do usuário. Por exemplo, uma view pode ser um botão que inicia uma ação quando o usuário toca nele.

O Android provê um número de views já prontas que você poderá usar para criar o seu design e organizar o seu layout. Mas isso você já sabe, já que falamos extensivamente de interface. Essas views são os widgets, que provêem elementos visuais e os layouts, que são views derivadas das ViewGroups.

Como sabem também, a forma mais comum de definir layouts é através de arquivos XML salvos como recursos da aplicação.

Declarando a atividade no manifesto

Você deve declarar sua atividade no arquivo de manifesto para que ele possa ser acessível no sistema. Para declarar sua atividade, abra o arquivo de manifesto e adicione um elemento de <activity> como filho do elemento <application>. Por exemplo:

<manifest ... >
  <application ... >
      <activity android:name=".ExampleActivity" />
      ...
  </application ... >
  ...
</manifest >

Existem diversos outros atributos que você pode incluir nesse elemento para definir as propriedades como label para uma atividade, um ícone para uma atividade ou mesmo um tema de estilos para a interface da atividade.

Usando intent filters (filtros de intenção)

Um elemento <activity> pode também especificar vários intent filters - usando o elemento <intent-filter> - para declarar como outros componentes de aplicação devem ser ativados.

Quando você cria uma nova aplicação usando as ferramentas SDK do Android, a atividade básica criada para você automaticamente inclui um intent filter que declara como a atividade deve responder à ação principal (main) e que deveria ser colocada na categoria de launcher. O intent filter se parece como abaixo:

<activity android:name=".ExampleActivity" android:icon="@drawable/app_icon">
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
</activity>

O elemento <action> especifica que ele é o ponto principal e inicial da aplicação. O elemento <category> especifica que essa atividade deve ser listada na aplicação launcher do sistema (para permitir aos usuários lançar a atividade).

Se você tem a intenção que sua aplicação seja, por assim dizer, privada e que não permita que outras aplicações ativem uma de suas atividades, então você não precisará de nenhum outro intent filter. Apenas uma atividade deve ser a ação principal e estar na categoria launcher, como mostrado no código acima. Atividades que você não quer que sejam disponíveis para outras aplicações não devem ter nenhum intent filter e você pode inicia-las você mesmo usando explicit intents (que serão discutidos em breve).

Contudo, se você quer que sua atividade responda a implicit intents que são entregues a partir de outras aplicações (e suas próprias), então você deve definir intent filters adicionais para sua atividade. Praa cada tipo de intent ao qual você quer responder, você deve incluir o elemento <intent-filter> que inclui um elemento <action> e, opcionalmente, um elemento <category> e/ou elemento <data>. Esses elementos especificam o tipo de intent ao qual você quer que sua atividade possa responder.

Falaremos sobre intents em breve, num post só para ele.

Iniciando uma atividade

Você pode começar uma outra atividade chamando startActivity(), passando para ele um Intent que descreve a atividade que você quer iniciar. O intent especifica ou a atividade exata que você quer iniciar ou descreve o tipo de ação que você quer executar (e o sistema seleciona a atividade apropriada para você, que pode inclusive ser uma aplicação diferente). Um intent pode também trazer consigo pequenas quantidades de dados a serem usados pela atividade que está sendo iniciada.

Quando trabalhando na sua própria aplicação, você também vai normalmente precisar lançar uma atividade conhecida. Você pode fazê-lo criando um intent que explicitamente define a aplicação que você quer iniciar, usando o nome de classe. Por exemplo, aqui está como uma atividade inicia outra atividade chamada SignInActivity:
Intent intent = new Intent(this, SignInActivity.class);
startActivity(intent);

Contudo, sua aplicação deve também querer executar alguma ação como enviar um email, mensagem de texto ou fazer o update no status ou mesmo usar dados de sua atividade. Nesse caso, sua aplicação pode não ter, dentro de suas atividades, uma que realize a ação esperada. Então, ao invés de criar uma atividade apenas para aquela ação, você poderá usar outras atividades providas por outras aplicações dentro do seu dispositivo. É aqui que os intents são realmente valiosos - você pode criar um intent que descreve uma ação que você quer realizar e o sistema lança a atividade apropriada a partir de outra aplicação.

Se existem multiplas atividades que podem realizar o intent, então o usuário poderá selecionar qual quer usar. É como quando você instala um app que realiza a tarefa de mostrar uma galeria de imagens. Caso você tenha dois ou mais apps que fazem a mesma função, ao tentar abrir uma imagem a partir de um app que não tenha essa funcionalidade, o Android perguntará na tela com qual app ele deverá realizar a tarefa. No exemplo abaixo, se você quer permitir que o usuário envie uma mensagem de email, você pode criar o intent seguinte:

Intent intent = new Intent(Intent.ACTION_SEND);
intent.putExtra(Intent.EXTRA_EMAIL, recipientArray);
startActivity(intent);
O EXTRA_EMAIL extra adicionado no intent é uma array de string de endereços de email aos quais o email pode ser enviado. Quando uma aplicação de email responde a esse intent, ele lê a array de string provida e coloca no campo "para" do formulário de composição do email. Nessa situação, a aplicação de email inicia e quando o usuário tiver terminado de enviar o email, a atividade anterior reinicia.

Iniciando uma atividade para um resultado

Algumas vezes, você pode querer receber um resultado a partir de uma atividade que você iniciou. Nesse caso, inicie a atividade chamando startActivityForResult() (ao invés de startActivity()). Para então receber o resultado de uma atividade subsequente, implemente o método callback onActivityResult(). Quando a atividade subsequente estiver finalizada, ele retorna um resultado para um Intent dentro do seu onActivityResult().

Por exemplo, talvez você queira que o usuário selecione um dos seus contatos para que a atividade possa fazer alguma coisa com a informação que está em seu contato. Aqui abaixo está como você pode criar tal intent e como manusear o resultado:

private void pickContact() {
    // Cria um intent para "pegar" um contato como definido pelo content provider URI
    Intent intent = new Intent(Intent.ACTION_PICK, Contacts.CONTENT_URI);
    startActivityForResult(intent, PICK_CONTACT_REQUEST);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    // Se a requisição foi bem sucedida (OK) e a requisição foi PICK_CONTACT_REQUEST
    if (resultCode == Activity.RESULT_OK && requestCode == PICK_CONTACT_REQUEST) {
        // Faz um query no content provider dos contatos para recuperar o nome do contato
        Cursor cursor = getContentResolver().query(data.getData(),
        new String[] {Contacts.DISPLAY_NAME}, null, null, null);
        if (cursor.moveToFirst()) { // Verdadeiro se o cursor não for vazio
            int columnIndex = cursor.getColumnIndex(Contacts.DISPLAY_NAME);
            String name = cursor.getString(columnIndex);
            // Faz algo com o nome do contato selecionado...
        }
    }
}
Esse exemplo mostra a lógica básica que você deverá usar em seu método onActivityResult() para que possamos manusear corretamente o resultado da atividade. A primeira condição checa se a requisição foi bem sucedida - e se foi, então o resultCode retornará RESULT_OK - e se a requisição para a qual o resultado está sendo retornado é conhecido - nesse caso, o requestCode bate com o segundo parâmetro enviado para startActivityForResult(). A partir dai, o código manuseia o resultado da atividade fazendo a leitura do dado retornado em um intent (a partir do parâmetro data).

O que acontece é que o ContentResolver realiza uma query no content provider que retorna um Cursor que permite que os dados retornados possam ser lidos. Mas vamos falar de content provider com mais propriedade também em um post só para ele.

Encerrando uma atividade

Você pode encerrar uma atividade chamando o método finish(). Você também pode encerrar uma atividade separada que você iniciou anteriormente chamando finishActivity().

Nota: Em muitos casos, você não deve explicitamente finalizar uma atividade usando esses métodos. O ideal é deixar o sistema Android gerenciar a vida da atividade para você. Chamar esses métodos podem afetar negativamente a experiência do usuário e deverá apenas ser usando quando absolutamente necessário.

Interface de Usuários - 100%. E agora?

Ok. A interface eu já sei como fazer? E o restante? Como juntar isso tudo? Vamos então falar, a partir de agora, de atividades. Sei que pode parecer que estou me repetindo. Afinal, já falei disso anteriormente. Só que eu falei rapidamente e em apenas um único post (Ciclo de Vida de uma aplicação)

Voltarei com uma abordagem melhor e mais específica. E, com os conhecimentos que já temos a respeito da interface, tenho certeza que ficará mais fácil para todos entender o que são as atividades dentro do contexto da interface do usuário.

Volto em breve.

domingo, 20 de março de 2011

2 comentários

Interface de Usuários - Fazendo o bind de dados com AdapterView

Pessoal, desculpe pelo meu sumiço. Eu achava que estava bem e resolvi que podia fazer tudo como se estivesse 100%. Não estava e tive uma recaída. Com dengue não se brinca. Ela se parece com uma gripe comum (mais forte, no entanto) mas não é. Se tiverem a infelicidade de pegar dengue, fiquem de repouso mesmo e não vacilem como eu, ok?

--

O AdapterView é uma subclasse do tipo ViewGroup cujas View filhas são determinadas por um Adapter que faz o bind de dados de algum repositório. O AdapterView é útil onde quer que você precise mostrar dados que estão guardados em bancos de dados dentro da sua interface.

Gallery, ListView e Spinner são exemplos de subclasses AdapterView que você pode usar para fazer o bind a partir de dados.

Os objetos AdapterView têm duas responsabilidades principais:

  • Preencher o layout com dados
  • Gerenciar as seleções de usuário
Preencher o layout com dados

Inserir dados em um layout é tipicamente alcançado fazendo o binding de uma classe AdapterView a um Adapter, que retorna dados de uma fonte externa (talvez uma lista de fornecedores ou o resultado de uma query a partir do banco de dados do dispositivo).

O código abaixo faz o seguinte:
  1. Cria um Spinner com uma View existente e faz o bind para um novo ArrayAdapter que lê uma array de cores do recurso local.
  2. Cria um outro Spinner a partir de uma View e faz o bind para um novo SimpleCursosAdapter que vai ler os nomes das pessoas dos contatos do dispositivo (ver depois Contacts.People).
// Pega o Spinner e faz o bind para um ArrayAdapter
// que referencia uma array de String

Spinner s1 = (Spinner) findViewById(R.id.spinner1);
ArrayAdapter adapter = ArrayAdapter.createFromResource(
    this, R.array.colors, android.R.layout.simple_spinner_item);
 
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
s1.setAdapter(adapter);
 
// Carrega um Spinner e faz o bind para uma query
private static String[] PROJECTION = new String[] {
        People._ID, People.NAME
    };
 
Spinner s2 = (Spinner) findViewById(R.id.spinner2);
Cursor cur = managedQuery(People.CONTENT_URI, PROJECTION, null, null);
   
SimpleCursorAdapter adapter2 = new SimpleCursorAdapter(this,
    android.R.layout.simple_spinner_item, // Use um template
                                                                   // que mostra um
                                                                   // text view

    cur, // Dá ao cursos ao list adapter
    new String[] {People.NAME}, // Mapeia a coluna NAME do banco
    new int[] {android.R.id.text1}); // A view "text1" definida no XML
                                       
adapter2.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
s2.setAdapter(adapter2);
Note que é necessário ter uma coluna People._ID em projeção usada no CursorAdapter ou você terá uma exceção.

Se, durante o curso da vida da aplicação, você mudar os dados que foi lido pelo seu Adapter, você deverá chamar notifyDataSetChanged(). Isso vai notificar as Views anexadas que os dados foram modificados e que elas deverão se atualizar.

Gerenciar as seleções de usuário

Você gerencia a seleção do usuário setando o membro AdapterView.OnItemClickListener para um listener e capturando as mudanças de seleção.
// Cria uma mensagem de handle para uma classe anônima
private OnItemClickListener mMessageClickedHandler = new OnItemClickListener() {
    public void onItemClick(AdapterView parent, View v, int position, long id)
    {
        // Mostra uma messageBox.
        Toast.makeText(mContext,"You've got an event",Toast.LENGTH_SHORT).show();
    }
};
// Agora pega o seu objeto e seta o membro onItemClickListener
// para que nossa classe possa gerenciar o objeto
mHistoryView = (ListView)findViewById(R.id.history);
mHistoryView.setOnItemClickListener(mMessageClickedHandler); 

Portions of this page are modifications based on work created and shared by the Android Open Source Project and used according to terms described in the Creative Commons 2.5 Attribution License.
Related Posts Plugin for WordPress, Blogger...