Save the date! Android Dev Summit is coming to Sunnyvale, CA on Oct 23-24, 2019.

Tarefas e pilha de retorno

Os aplicativos normalmente contêm diversas atividades. Cada atividade deve ser projetada com relação a tipos específicos de ações que o usuário pode realizar e que podem iniciar outras atividades. Por exemplo, um aplicativo de e-mail pode ter uma atividade para exibir uma lista de novas mensagens. Quando o usuário seleciona uma mensagem, uma nova atividade abre para exibir essa mensagem.

As atividades podem também iniciar atividades existentes em outros aplicativos no dispositivo. Por exemplo, se seu aplicativo deseja enviar um e-mail, é possível definir um intent para realizar uma ação de "enviar" e incluir alguns dados, como um endereço de e-mail e uma mensagem. Uma atividade de outro aplicativo que se declara para processar esse tipo de intents é aberta. Nesse caso, o intent se destina ao envio de e-mails, portanto, é iniciada uma atividade de “composição" do aplicativo de e-mail (se diversas atividades forem compatíveis com o mesmo intent, o sistema permitirá que o usuário selecione qual usar). Quando o e-mail é enviado, sua atividade retoma, parecendo que a atividade de e-mail faz parte do seu aplicativo. Apesar de as atividades serem de aplicativos diferentes, o Android mantém essa experiência do usuário transparente, mantendo ambas as atividades na mesma tarefa.

Tarefas são coleções de atividades com as quais os usuários interagem ao realizar determinado trabalho. As atividades são organizadas em uma pilha (a pilha de retorno) na ordem em que cada atividade é aberta.

A tela inicial do dispositivo é o ponto de partida para a maioria das tarefas. Quando o usuário toca em um ícone no inicializador do aplicativo (ou em um atalho na tela inicial), essa tarefa do aplicativo acontece em primeiro plano. Se não existir nenhuma tarefa para o aplicativo (se o aplicativo não tiver sido usado recentemente), uma nova tarefa será criada e a atividade "principal" daquele aplicativo abrirá como a atividade raiz na pilha.

Quando a atividade atual inicia outra, a nova atividade é colocada no topo da pilha e recebe foco. A atividade anterior permanece na pilha, mas é interrompida. Quando uma atividade para, o sistema retém o estado atual da interface do usuário. Quando o usuário pressiona o botão Voltar , a atividade atual é retirada do topo da pilha (a atividade é destruída) e a atividade anterior retoma (o estado anterior da IU é restaurado). Atividades na pilha nunca são reorganizadas, somente colocadas e retiradas da pilha — colocadas na pilha quando iniciadas pela atividade atual e retiradas quando o usuário sai dela usando o botão Voltar. Desse modo, a pilha de retorno opera como uma estrutura de objeto LIFO (último que entra, primeiro que sai). A figura 1 ilustra esse comportamento com uma linha cronológica exibindo o progresso entre atividades junto com a pilha de retorno atual em cada ponto no tempo.

Figura 1. Representação de como cada nova atividade em uma tarefa adiciona um item à pilha de retorno. Quando o usuário pressiona o botão Voltar, a atividade atual é destruída e a atividade anterior retoma.

Se o usuário continua pressionando Voltar, cada atividade na pilha é retirada para revelar a anterior até que o usuário retorne à tela inicial (ou a qualquer atividade que estivesse em execução no começo da tarefa). Quando todas as atividades forem removidas da pilha, a tarefa não existirá mais.

Figura 2. Duas tarefas: A tarefa B recebe a interação do usuário em primeiro plano enquanto a tarefa A está em segundo plano aguardando para ser retomada.

Figura 3. Uma única atividade é instanciada diversas vezes.

As tarefas são unidades coesas que podem se mover para "segundo plano" quando usuário inicia uma nova tarefa ou vai para a tela inicial usando o botão Home. Quando em segundo plano, todas as atividades da tarefa são interrompidas, mas a pilha de retorno das tarefas continua intacta — a tarefa simplesmente perdeu o foco, que foi para outra tarefa, como ilustrado na figura 2. Uma tarefa pode, então, retornar ao “primeiro plano” para que os usuários continuem de onde pararam. Suponha, por exemplo, que a tarefa atual (tarefa A) tenha três atividades na sua pilha — duas sob a atividade atual. O usuário pressiona o botão Home e, em seguida, inicia um novo aplicativo no inicializador do aplicativo. Quando a tela inicial aparece, a tarefa A vai para segundo plano. Quando o novo aplicativo inicia, o sistema inicia uma tarefa para esse aplicativo (tarefa B) com a própria pilha de atividades. Após interagir com esse aplicativo, o usuário retorna à tela inicial novamente e seleciona o aplicativo que originalmente iniciou a tarefa A. Agora, a tarefa A fica em primeiro plano — todas as três atividades nas pilhas estão intactas e a atividade no topo da pilha retoma. Nesse momento, o usuário pode alternar para a tarefa B acessando a tela inicial e selecionando o ícone do aplicativo que iniciou essa tarefa (ou selecionando a tarefa do aplicativo na tela de visão geral). Esse é um exemplo de multitarefas no Android.

Observação: Várias tarefas podem ser mantidas em segundo plano simultaneamente. Contudo, se o usuário estiver executando diversas tarefas em segundo plano ao mesmo tempo, o sistema poderá começar a destruir atividades de segundo plano para recuperar memória, fazendo com que o estado das atividades seja perdido. Consulte a seção a seguir sobre Estado de atividades.

Como as atividades na pilha de retorno nunca são reorganizadas, se seu aplicativo permitir que usuários iniciem uma determinada atividade em mais de uma atividade, uma nova instância dela será criada e colocada na pilha (em vez de trazer qualquer instância anterior dela para o topo). Dessa forma, uma atividade no aplicativo pode ser instanciada diversas vezes (mesmo de diferentes tarefas), como ilustrado é na figura 3. Assim, se o usuário navegar inversamente usando o botão Voltar, as instâncias da atividade serão reveladas na ordem em que foram abertas (cada uma com o próprio estado da IU). No entanto, é possível modificar esse comportamento se você não deseja que uma atividade seja instanciada mais de uma vez. Esse assunto é abordado na próxima seção sobre Gerenciamento de tarefas.

Para resumir o comportamento padrão de atividades e tarefas:

  • Quando a atividade A inicia a atividade B, a atividade A é interrompida, mas o sistema retém seu estado (como posição de rolagem e texto inserido em formulários). Se o usuário pressionar o botão Voltar na atividade B, a atividade A retomará com o estado restaurado.
  • Quando o usuário sai de uma tarefa pressionando o botão Home, a atividade atual é interrompida e sua tarefa fica em segundo plano. O sistema retém o estado de cada atividade na tarefa. Se, mais tarde, o usuário retoma a tarefa selecionando o ícone de inicialização que a inicia, ela ficará em primeiro plano e reiniciará a atividade no topo da pilha.
  • Se o usuário pressionar o botão Voltar, a atividade atual será retirada da pilha e destruída. A atividade anterior na pilha é retomada. Quando uma atividade é destruída, o sistema não retém seu estado.
  • As atividades podem ser instanciadas diversas vezes, mesmo de outras tarefas.

Projeto de navegação

Para saber mais sobre o funcionamento da navegação de aplicativos no Android, leia o guia Navegação do Projeto do Android.

Gravação do estado da atividade

Como discutido acima, o comportamento padrão do sistema preserva o estado de uma atividade quando ela é interrompida. Desse modo, quando usuários navegam inversamente a uma atividade anterior, a interface do usuário aparece na forma em que foi deixada. Entretanto, é possível — e recomendável — reter proativamente o estado das atividades usando métodos de retorno de chamada nos casos em que a atividade é destruída e deve ser recriada.

Quando o sistema interrompe uma das atividades (como quando uma nova atividade inicia ou a tarefa se move para segundo plano), o sistema pode destruir essa atividade completamente se precisar recuperar a memória do sistema. Quando isso ocorre, as informações sobre o estado da atividade são perdidas. Se isso acontecer, o sistema ainda saberá que a atividade tem um lugar na pilha de retorno, mas quando a atividade for levada ao topo da pilha, o sistema precisará recriá-la (em vez de retomá-la). Para evitar perder o trabalho do usuário, deve-se retê-la proativamente implementando métodos de retorno de chamada onSaveInstanceState() na atividade.

Para obter mais informações sobre a gravação do estado da atividade, consulte o documento Atividades.

Gerenciamento de tarefas

O modo como o Android gerencia tarefas e a pilha de retorno, como descrito acima — posicionando todas as atividades iniciadas em sucessão na mesma tarefa em uma pilha LIFO (último que entra, primeiro que sai) — funciona muito bem para a maioria dos aplicativo e não é preciso se preocupar com a associação das atividades a tarefas ou como elas estão organizadas na pilha de retorno. No entanto, pode-se decidir interromper o comportamento normal. Talvez você deseje que uma atividade inicie uma nova tarefa no aplicativo ao ser iniciada (em vez de ser colocada dentro da tarefa atual); ou, quando inicia uma atividade, deseja apresentar uma instância existente (em vez de criar uma nova instância no topo da pilha de retorno); ou talvez deseje que a pilha apague todas as atividades, exceto a atividade raiz, quando o usuário sai da tarefa.

É possível fazer tudo isso e muito mais com atributos no elemento <activity> do manifesto e com sinalizadores no intent passado a startActivity().

Com relação a isso, os principais atributos de <activity> que podem ser usados são:

E os principais sinalizadores de intent que podem ser usados são:

Nas seções a seguir, você verá como usar esses atributos de manifesto e sinalizadores de intent para definir como as atividades são associadas a tarefas e como elas se comportam na pilha de retorno.

Além disso, são discutidas separadamente as considerações sobre como tarefas e atividades podem ser representadas e gerenciadas na tela de visão geral. Consulte Tela de visão geral para obter mais informações. Normalmente, é preciso permitir que o sistema defina como as tarefas e as atividades são representadas na tela de visão geral e não é necessário modificar esse comportamento.

Atenção: A maioria dos aplicativos não deve interromper o comportamento padrão de atividades e tarefas. Se você determinar que é necessário modificar os comportamentos padrão da atividade, seja prudente e certifique-se de testar a capacidade de uso da atividade durante a inicialização e ao navegar de volta para ela de outras atividades e tarefas com o botão Voltar. Certifique-se de testar os comportamentos de navegação que podem entrar em conflito com o comportamento esperado pelo usuário.

Definição de modos de inicialização

Os modos de inicialização permitem definir a forma com que uma nova instância de uma atividade é associada à tarefa atual. Pode-se definir diferentes modos de inicialização de duas maneiras:

Assim, se a atividade A iniciar a atividade B, a atividade B poderá definir no manifesto como deve se associar à tarefa atual (se ocorrer) e a atividade A poderá solicitar o modo pelo qual a atividade B deverá se associar à tarefa atual. Se ambas as atividades definem como a atividade B deve se associar a uma tarefa, a solicitação da atividade A (como definido no intent) sobrepõe a solicitação da atividade B (como definido no seu manifesto).

Observação: Alguns modos de inicialização disponíveis para o arquivo de manifesto não estão disponíveis como sinalizadores para um intent e, do mesmo modo, alguns modos de inicialização disponíveis como sinalizadores de um intent não podem ser definidos no manifesto.

Uso do arquivo de manifesto

Ao declarar uma atividade no arquivo de manifesto, pode-se especificar como a atividade deve associar-se a tarefas usando o atributo launchMode do elemento <activity>.

O atributo launchMode especifica uma instrução sobre como a atividade deve ser inicializada em uma tarefa. Há quatro modos diferentes de inicialização que podem ser designados ao atributo launchMode:

"standard" (o modo padrão)
Padrão. O sistema cria uma nova instância da atividade na tarefa pela qual foi iniciada e encaminha-lhe o intent. A atividade pode ser instanciada diversas vezes; cada instância pode pertencer a diferentes tarefas e uma tarefa pode ter diversas instâncias.
"singleTop"
Se uma instância da atividade já existir no topo da tarefa atual, o sistema encaminhará o intent àquela instância por meio de uma chamada do método onNewIntent() em vez de criar uma nova instância da atividade. A atividade pode ser instanciada diversas vezes; cada instância pode pertencer a diferentes tarefas e uma tarefa pode ter diversas instâncias (mas somente se a atividade no topo da pilha de retorno não for uma instância existente da atividade).

Por exemplo, suponhamos que uma pilha de retorno da tarefa consista na atividade raiz A com as atividades B, C e D no topo (a pilha é A-B-C-D, com D no topo). Um intent chega de uma atividade de tipo D. Se D for o modo de inicialização "standard" padrão, uma nova instância da classe será inicializada e a tarefa se tornará A-B-C-D-D. Entretanto, se o modo de inicialização de D for "singleTop", a instância existente de D receberá o intent por onNewIntent() porque ele está no topo da pilha — a pilha permanece A-B-C-D. Contudo, se um intent chegar de uma atividade de tipo B, uma nova instância de B será adicionada à pilha mesmo que o modo de inicialização seja "singleTop".

Observação: Quando uma nova instância de uma atividade é criada, o usuário pode pressionar o botão Voltar para retornar à atividade anterior. Porém, quando uma instância existente de uma atividade processa um novo intent, o usuário não pode pressionar o botão Voltar para retornar ao estado da atividade antes que o novo intent chegue a onNewIntent().

"singleTask"
O sistema cria uma nova tarefa e instancia a atividade na raiz dela. Entretanto, se uma instância da atividade já existir em uma tarefa separada, o sistema encaminhará o intent àquela instância por meio de uma chamada do método onNewIntent() em vez de criar uma nova instância. Somente uma instância da atividade pode existir por vez.

Observação: Embora a atividade inicie em uma nova tarefa, o botão Voltar ainda direciona o usuário à atividade anterior.

"singleInstance".
Igual a "singleTask", mas o sistema não inicializa nenhuma outra atividade na tarefa que contém a instância. A atividade é sempre o único e exclusivo membro de sua tarefa; toda atividade iniciada por ela abre em uma tarefa separada.

Outro exemplo: o aplicativo Navegador do Android declara que a atividade do navegador da Web deve sempre abrir na própria tarefa — especificando o modo de inicialização singleTask no elemento <activity>. Isso significa que, se o aplicativo emite um intent para abrir o navegador do Android, sua atividade não se coloca na mesma tarefa do aplicativo. Em vez disso, uma nova tarefa inicia para o navegador ou, se o navegador já tem uma tarefa em execução em segundo plano, essa tarefa é colocada em primeiro plano para processar o novo intent.

Se uma atividade inicia em uma nova tarefa ou na mesma tarefa que a atividade que a iniciou, o botão Voltar sempre direciona o usuário à atividade anterior. Porém, se você iniciar uma atividade que especifica o modo de inicialização singleTask e se uma instância dessa atividade existir em uma tarefa de segundo plano, a tarefa toda se colocará em primeiro plano. Nesse momento, a pilha de retorno conterá todas as atividades da tarefa colocada em primeiro plano no topo. A figura 4 ilustra essa situação.

Figura 4. Representação de como uma atividade com modo de inicialização “singleTask” é adicionada à pilha de retorno. Se a atividade já for parte de uma tarefa de segundo plano com a própria pilha de retorno, toda a pilha também virá para primeiro plano, no topo da tarefa atual.

Para saber mais sobre o uso de modos de inicialização no arquivo de manifesto, consulte a documentação do elemento <activity> , onde o atributo launchMode e os valores aceitos são discutidos mais aprofundadamente.

Observação: Os comportamentos a especificar para a atividade com o atributo launchMode podem ser modificados por sinalizadores incluídos no intent que inicia a atividade, conforme abordado na próxima seção.

Uso de sinalizadores de intent

Ao iniciar uma atividade, é possível modificar a associação padrão de uma atividade à tarefa inclusive sinalizadores no intent fornecido a startActivity(). Os sinalizadores que podem ser usados para modificar o comportamento padrão são:

FLAG_ACTIVITY_NEW_TASK
Inicia a atividade em uma nova tarefa. Se uma tarefa já estiver em execução para a atividade que você está iniciando agora, ela será colocada em primeiro plano com o último estado restaurado e a atividade receberá o novo intent em onNewIntent().

Isso produz o mesmo comportamento que o valor "singleTask" launchMode, abordado na seção anterior.

FLAG_ACTIVITY_SINGLE_TOP
Se a atividade iniciada for a atual (no topo da pilha de retorno), a instância existente receberá uma chamada de onNewIntent() em vez de criar uma nova instância da atividade.

Isso produz o mesmo comportamento que o valor "singleTop" launchMode, abordado na seção anterior.

FLAG_ACTIVITY_CLEAR_TOP
Se a atividade iniciada já estiver em execução na tarefa atual, em vez de lançar uma nova instância daquela atividade, todas as outras atividades no topo dela serão destruídas e esse intent será entregue à instância retomada da atividade (agora no topo) por onNewIntent().

Não há nenhum valor para o atributo launchMode que produza esse comportamento.

FLAG_ACTIVITY_CLEAR_TOP é mais usado em conjunto com FLAG_ACTIVITY_NEW_TASK. Quando usados juntos, esses sinalizadores são o modo de localizar uma atividade existente em outra tarefa e colocá-la em uma posição em que possa responder ao intent.

Observação: Se o modo de inicialização da atividade designada for "standard", ela também será removida da pilha e uma nova instância iniciará em seu lugar para processar o intent recebido. Isso se deve ao fato de que sempre ocorre a criação de uma nova instância para um novo intent quando o modo de inicialização é "standard".

Processamento de afinidades

A afinidade indica a que tarefa uma atividade prefere pertencer. Por padrão, todas as atividades do mesmo aplicativo têm afinidade entre si. Assim, por padrão, todas as atividades no mesmo aplicativo preferem estar na mesma tarefa. Contudo, é possível modificar a afinidade padrão de uma atividade. Atividades definidas em aplicativos diferentes podem compartilhar uma afinidade, ou atividades definidas no mesmo aplicativo podem ter diferentes afinidades de tarefa atribuídas.

É possível modificar a afinidade de qualquer atividade com o atributo taskAffinity do elemento <activity>.

O atributo taskAffinity recebe um valor de string que deve ser exclusivo do nome do pacote padrão declarado no elemento <manifest> porque o sistema usa esse nome para identificar a afinidade de tarefa padrão do aplicativo.

A afinidade tem relevância em duas circunstâncias:

  • Quando o intent que inicializa uma atividade contém o sinalizador FLAG_ACTIVITY_NEW_TASK.

    Uma nova atividade é, por padrão, inicializada na tarefa da atividade que chamou startActivity(). Ela é colocada na mesma pilha de retorno do autor da chamada. Contudo, se o intent passado a startActivity() contiver o sinalizador FLAG_ACTIVITY_NEW_TASK , o sistema procurará uma tarefa diferente para comportar a nova atividade. Na maioria das vezes, é uma nova tarefa. Porém, isso não é obrigatório. Se já houver uma tarefa com a mesma afinidade da nova atividade, ela será inicializada naquela tarefa. Caso contrário, ela iniciará uma nova tarefa.

    Se esse sinalizador fizer com que uma atividade inicie uma nova tarefa e se o usuário pressionar o botão Home para sair dela, será necessário ter um modo de o usuário navegar de volta à tarefa. Algumas entidades (como o gerenciador de notificação) sempre iniciam atividades em tarefas externas e nunca como parte de si mesmas, por isso elas sempre colocam FLAG_ACTIVITY_NEW_TASK nos intents que passam a startActivity(). Se você tiver uma atividade que possa ser chamada por uma entidade externa que possa usar esse sinalizador, certifique-se de que o usuário tenha um modo independente de voltar à tarefa iniciada, como com um ícone de inicialização (a atividade raiz da tarefa tem um filtro de intents CATEGORY_LAUNCHER — consulte a seção abaixo Início de uma tarefa).

  • Quando uma atividade tem o atributo allowTaskReparenting definido como "true".

    Nesse caso, a atividade pode se mover da tarefa que iniciou para a tarefa afim quando for colocada em primeiro plano.

    Por exemplo, suponhamos que uma atividade que relate condições do clima em cidades selecionadas seja definida como parte de um aplicativo de viagens. Ela tem a mesma afinidade que outras atividades no mesmo aplicativo (a afinidade padrão do aplicativo) e permite a redefinição da hierarquia com esse atributo. Quando uma das atividades inicia a atividade de notificação de clima, ela inicialmente pertence à mesma tarefa de sua atividade. Porém, quando a tarefa do aplicativo de viagens é colocada em primeiro plano, a atividade de notificação de clima é reatribuída a essa tarefa e exibida dentro dela.

Dica: Se um arquivo .apk contiver mais de um "aplicativo" do ponto de vista do usuário, você provavelmente desejará usar o atributo taskAffinity para designar diferentes afinidades às atividades associadas a cada “aplicativo”.

Limpeza da pilha de retorno

Se o usuário sair de uma tarefa por muito tempo, o sistema apagará a tarefa de todas as atividades exceto a da atividade raiz. Quando o usuário retornar à tarefa, somente a atividade raiz será restaurada. O sistema se comporta dessa maneira porque, após longo tempo de uso, os usuários provavelmente abandonaram o que estavam fazendo antes e são direcionados de volta à tarefa para começar algo novo.

Há alguns atributos de atividade que podem ser usados para modificar esse comportamento:

alwaysRetainTaskState
Se esse atributo for definido como "true" na atividade raiz de uma tarefa, o comportamento padrão descrito não acontecerá. A tarefa manterá todas as atividades em sua pilha mesmo após um longo período.
clearTaskOnLaunch
Se esse atributo for definido como "true" na atividade raiz de uma tarefa, a pilha será apagada da atividade raiz sempre que o usuário sair da tarefa e retornar a ela. Em outras palavras, é o oposto de alwaysRetainTaskState. O usuário sempre retorna à tarefa no estado inicial, mesmo ao retirar-se da tarefa somente por um momento.
finishOnTaskLaunch
Esse atributo é como clearTaskOnLaunch, mas opera em uma única atividade e não em uma tarefa inteira. Ele também apaga todas as atividades, inclusive a atividade raiz. Quando definido como "true", a atividade permanece parte da tarefa somente para a sessão atual. Se o usuário sair e, em seguida, retornar à tarefa, ela não estará mais presente.

Início de uma tarefa

É possível configurar uma atividade como ponto de entrada de uma tarefa fornecendo-lhe um filtro de intents com "android.intent.action.MAIN" como a ação especificada e "android.intent.category.LAUNCHER" como a categoria especificada. Por exemplo:

<activity ... >
    <intent-filter ... >
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
    ...
</activity>

Um filtro de intents desse tipo faz com que um ícone e o rótulo da atividade sejam exibidos no inicializador do aplicativo, fornecendo aos usuários um modo de inicializar a atividade e de retornar à tarefa criada a qualquer tempo após sua inicialização.

Este segundo recurso é importante: É preciso que os usuários possam sair de uma tarefa e voltar a ela mais tarde usando esse inicializador de atividades. Por isso, os dois modos de inicialização que marcam atividades como sempre iniciando uma tarefa ("singleTask" e "singleInstance") devem ser usados somente quando a atividade tiver um filtro ACTION_MAIN e um CATEGORY_LAUNCHER. Imagine, por exemplo, o que aconteceria se o filtro não estivesse presente: Um intent inicializaria uma atividade "singleTask", iniciando uma nova tarefa, e o usuário perderia algum tempo trabalhando nessa tarefa. O usuário, então, pressiona o botão Home. A tarefa é enviada para segundo plano e não fica mais visível. O usuário não tem como voltar à tarefa porque ela não é representada no inicializador do aplicativo.

Para casos em que se deseja que o usuário não seja capaz de retornar a uma atividade, defina <activity> o finishOnTaskLaunch do elemento como "true" (consulte Apagamento da pilha).

Veja mais informações sobre a representação e o gerenciamento de atividades na tela de visão geral em Tela de visão geral.