Questões de mobilidade

Marcas de exclusão no Windows Phone 7

Jaime Rodriguez

Baixar o código de exemplo

Uma boa plataforma móvel deve reconhecer as restrições de hardware que a mobilidade impõe ao dispositivo. Comparados aos desktops, os dispositivos móveis possuem menos memória, menor capacidade de processamento, o espaço na tela é limitado e a vida útil da bateria é curta. Some essas restrições e você concluirá que, em um dispositivo não dedicado, em que vários aplicativos serão executados, os aplicativos serão eventualmente fechados ou encerrados para disponibilizar recursos para outros aplicativos.

O Windows Phone lida com essa restrição por meio de um recurso conhecido como marca de exclusão (tombstoning). Embora possa parecer que a marca de exclusão seja uma proposta simples, ela é realmente um ponto de discórdia entre os desenvolvedores. Alguns sustentam que ela não seria necessária. Outros argumentam que ela é muito difícil. O restante de nós simplesmente odeia o nome do recurso. E ainda, as restrições nos dispositivos móveis as tornam um mal necessário; portanto, aplicativos móveis grandes devem ser capazes de lidar com a marca de exclusão.

Ciclo de vida do aplicativo do Windows Phone

A maioria dos novos desenvolvedores do Windows Phone chega à plataforma com a expectativa de que o ciclo de vida de um aplicativo seja semelhante ao seguinte: 

  1. Iniciar
  2. Executar
  3. Sair
  4. Voltar para 1 e iniciar novamente

O Windows Phone 7 desafia essa expectativa expondo um ciclo de vida diferente — um que seja menos orientado a processos e mais orientado a sessões.

No Windows Phone 7, você deve imaginar o ciclo de vida como:

  1. Iniciar
  2. Executar
  3. Execução interrompida ou sair
  4. Se interrompida, voltar — ou, mesmo se interrompida, iniciar uma nova
  5. Se saiu, inicie uma nova 

O benefício desse novo modelo orientado a sessões é que os usuários podem navegar pelos aplicativos sem precisar se preocupar com a maneira como o SO está gerenciando seus recursos. Um usuário não se preocupa se a interrupção do seu jogo para responder a uma mensagem SMS recebida finalizará o processo do jogo. O usuário espera poder voltar ao jogo quando terminar com a mensagem. Se isso funcionar bem o suficiente, os detalhes subjacentes são irrelevantes.

A desvantagem para os desenvolvedores é que há um pouco mais para tratar para fornecer realmente a percepção de continuidade de sessão, pois suas sessões continuam em execução em um SO tradicional, centrado no processo. Para acomodar sessões em um mundo centrado no processo, crie estados lógicos para a sessão: Launched, Activated, Running, Deactivated, Tombstoned e Closed (ou Ended).

A Figura 1 mostra o ciclo de vida prático para um aplicativo do Windows Phone 7. Os eventos do ciclo de vida do aplicativo, descrito na Figura 2, são expostos pela classe Microsoft.Phone.Shell.PhoneApplicationService. 

Windows Phone 7 Application Lifecycle

Figura 1 Ciclo de vida do aplicativo do Windows Phone 7

Figura 2 Eventos do ciclo de vida do aplicativo

Estado lógico Evento PhoneApplicationService Descrição
Launched Launching O aplicativo é ativado quando o usuário pressiona o quadro inicial ou o ícone da lista de aplicativos, ou o usuário clica em uma notificação da caixa de informações. Launched significa um novo início da sessão.
Activated Activated O aplicativo é ativado quando o usuário pressiona o botão Voltar e coloca o aplicativo que foi anteriormente desativado novamente em primeiro plano. Nesse caso, o usuário espera voltar a uma sessão em andamento.
Running Running Depois de ser iniciado ou ativado, o aplicativo permanece em execução. 
Deactivated Deactivated Um aplicativo que está em execução é desativado quando o processamento em primeiro plano é transferido desse aplicativo para outro ou para um componente do SO (como um Seletor ou um Iniciador ou a tela de bloqueio). A sessão é interrompida, mas espera-se que seja retomada mais tarde.
Ended (ou Exited) Closing

O usuário está saindo do aplicativo pressionando a tecla Voltar na página principal.

Ao sair, o usuário espera voltar para um aplicativo novo.

O estado de marca de exclusão é um pouco mais complicado e não está diretamente relacionado a um evento PhoneApplicationService. Quando um aplicativo é desativado, o SO não interrompe imediatamente o processo desse aplicativo. Em teoria, o SO interrompe o aplicativo quando precisa de recursos e quando isso ocorre. O aplicativo não obtém nenhuma notificação e é simplesmente interrompido.  

Na prática, o Windows Phone 7 interrompe o processo imediatamente após o controle ser transferido para outro aplicativo em primeiro plano, mas isso não é um detalhe que deva ser levado em conta. A Microsoft já anunciou no Mobile World Congress, em fevereiro último, que aprimoramentos como a Troca Rápida de Usuário serão lançados; portanto, não se baseie em um detalhe da implementação para determinar quando ocorrerá a marca de exclusão. Em vez disso, prepare-se para isso fazendo o trabalho correto na desativação.

Deactivated versus Tombstoned

Um telefone pode ter vários processos em execução o tempo todo (shell, telefone, etc.), mas ele possui, no máximo, um aplicativo em execução em primeiro plano. (Ele poderá ter zero aplicativos quando não houver nada em execução em primeiro plano.)

Quando um aplicativo em primeiro plano transfere o controle para outro aplicativo ou para um componente do SO, ele é desativado. Depois de um processo ser desativado, o SO pode interromper o processo para liberar os recursos. Esse processo é chamado de marca de exclusão. 

Como você pode ver, a marca de exclusão não ocorre sempre que o aplicativo é desativado, mas sempre ocorre após uma desativação. Na verdade, Deactivated é o último evento PhoneApplicationService acionado antes da marca de exclusão; portanto, é aqui que você deve fazer seu trabalho para ativar o aplicativo novamente mais tarde.

A Figura 3 mostra todas as diferentes tarefas que levam à desativação e supõe a probabilidade de ocorrer a marca de exclusão. 

Figura 3 Tarefas de desativação

Ação Desativado? Marcado para exclusão?
O usuário pressiona o botão Voltar na primeira página do aplicativo Não; isso fecha o aplicativo Não. A desativação nunca ocorreu.
O usuário pressiona o botão Iniciar Sim Muito provavelmente, mas não é garantido. Alguns segundos após a desativação do aplicativo, ele é marcado para exclusão. Se o usuário voltar para o aplicativo rapidamente após a desativação, ele poderá não ser marcado para exclusão. Isso poderia ser considerado um tempo limite indeterminado.
O usuário invoca um seletor ou um iniciador que marca para exclusão Sim Muito provavelmente, mas o tempo limite é aplicado.
O usuário invoca um seletor ou um iniciador que não marca para exclusão Sim Pouco provável, mas ainda pode acontecer. Se o usuário pressionar o botão Iniciar no meio da tarefa, verá então a regra “Usuário pressiona o botão Iniciar”. Um novo evento Deactivated não é acionado porque o aplicativo já estava desativado. 
A tela de bloqueio aparece e o aplicativo não é configurado para ser executado sob bloqueio Sim Muito provavelmente, mas o tempo limite é aplicado.
Uma notificação da caixa de informações é exibida e o usuário toca nela, transferindo para outro aplicativo em primeiro plano Sim Muito provavelmente, mas o tempo limite é aplicado.

Há um subconjunto de seletores que não são marcados para exclusão imediatamente, mas que ainda podem ser marcados para exclusão se o usuário executar uma ação que marque o processo para exclusão. Isso inclui PhotoChooserTask (a menos que o usuário especifique o corte), CameraCaptureTask, MediaPlayerLauncher, EmailAddressChooserTask e PhoneNumberChooserTask.

Todos os outros seletores e iniciadores são marcados para exclusão logo depois de o método Show ser chamado.

Para ver o ciclo de vida do aplicativo do Windows Phone 7 em ação, inicie o exemplo LWP.TombStoning no download do código. 

Estados Saving e Restoring

Como o objetivo da navegação baseada em sessão é facilitar a passagem do usuário entre aplicativos em primeiro plano continuamente, você deverá salvar todos os estados relevantes do evento Deactivated e restaurar o estado no evento Activated. A maioria dos aplicativos possui três tipos de estados para gerenciar:

  • O Estado de aplicativo persistente deve ser sempre mantido. Isso inclui as configurações do aplicativo, os dados do usuário, etc.
  • O Estado de aplicativo específico da sessão inclui o estado temporário, como caches e ViewModels, que precisam ser restaurados na ativação, mas são iniciados como uma nova ativação do aplicativo.
  • O Estado específico da interface do usuário ou da página é necessário para restaurar uma PhoneApplicationPage quando um aplicativo é ativado. O Windows Phone 7 salva a pilha Voltar de um aplicativo ao aplicar a marca de exclusão. Na ativação, ele restaura apenas a última página ativa antes de o aplicativo ser marcado para exclusão. Se o usuário pressionar o botão Voltar, a página anterior será instanciada.

O Estado de aplicativo persistente deverá ser salvo em IsolatedStorage ou por meio da classe ApplicationSettings. O estado do aplicativo deverá ser salvo o quanto antes, no caso de a bateria se esgotar, por exemplo. 

Se forem dados do usuário (como um cache de sessão) e você não desejar serializar com muita frequência, eles poderão ser salvos nos eventos Deactivated e Closing, e (simetricamente) deverão ser restaurados nos eventos Activated ou Launching.

O estado específico da sessão pode ser salvo em um armazenamento isolado se você desejar ter controle sobre formatos de serialização ou se tiver muitos dados, ou pode ser salvo no dicionário PhoneApplicationService.State. Você deverá salvá-lo apenas no evento Deactivated e restaurá-lo no evento Activated.

O estado específico de página deverá ser salvo no dicionário PhoneApplicationPage.State. A chave para salvar o estado da página é lembrar-se de que o aplicativo possui uma pilha Voltar de páginas que será serializada automaticamente quando ocorrer PhoneApplicationService.Deactivated. Para manter suas páginas prontas para marcas de exclusão, você deverá monitorar a substituição de PhoneApplicationPage.OnNavigatedFrom em sua página e salvar qualquer estado de exibição que ainda não tenha sido confirmado em seu Model (ou ViewModel) no dicionário da página. Não espere até o evento Deactivated, pois não poderá obter as páginas da pilha Voltar.

É claro, se você salvar o estado da página ao receber OnNavigatedFrom, deverá restaurá-la na substituição OnNavigatedTo da página.

Você poderia também salvar o estado específico da página em ViewModels e, em seguida, serializar seus ViewModels como o estado da sessão, mas isso exigiria salvar o estado não confirmado em um ViewModel, de modo que isso não é recomendado. Aproveite a infraestrutura que já existe para garantir a durabilidade de otimizações posteriores da plataforma.

Evitando armadilhas

Usar marcas de exclusão não é difícil. É apenas um trabalho um pouco tedioso e exige consistência e planejamento. Se o seu aplicativo for desativado, mas não marcado para exclusão, o seu estado permanecerá na memória e não será reconstruído. 

Evite depender dos construtores de classes que criam o estado que seu aplicativo precisa, mas que pode ser liberado durante a desativação. A simetria é preferível. Use os eventos PhoneApplicationService Deactivated e Activated para o estado de aplicativos e OnNavigatedFrom ou OnNavigatedTo para o estado de páginas. 

Se você tiver objetos (singletons) em seu aplicativo que sejam instanciados fora de chamadas de ativação (talvez devido a atrasos na instanciação), sempre verifique se eles foram construídos e inicializados corretamente antes de tentar usá-los. Um engano comum que tenho encontrado é a leitura de dados no evento PhoneApplicationService.Activated ou em PhoneApplicationPage.OnNavigatedTo e a não redefinição dos mesmos. As páginas podem ser NavigatedTo várias vezes (independentemente da marca de exclusão) e mesmo uma sessão pode ser marcada para exclusão várias vezes durante uma sessão.

Depois de ter restaurado o estado, elimine-o. É possível defini-lo posteriormente na substituição NavigatedFrom de sua página ou no evento Deactivated do aplicativo.

Seja esperto quanto ao que salvar e quando restaurar. Parte de tornar tudo isso simples para que o usuário volte ao aplicativo é restaurar seu aplicativo imediatamente. Se você salvar muito do estado da página ou do aplicativo, isso retardará a ativação. Otimize o armazenamento isolado, se necessário, para o estado de carregamento em segundo plano que pode não ser necessário imediatamente quando o seu aplicativo for ativado. A ativação e a desativação devem ocorrer em menos de 10 segundos. Lembre-se disso ou o SO poderá finalizar seu processo antes de concluir a desativação ou quando for reativado. Você deve, no entanto, apostar em muito menos de 10 segundos.

Entenda as restrições da estrutura da serialização. É possível armazenar, no máximo, 2 MB em toda a sua página e no estado do aplicativo. Se o seu total for superior, você começará a ver exceções quando navegar e quando desativar. Você não deverá serializar tantos dados no estado da página. Se precisar armazenar em cache grandes conjuntos de dados, mantenha-os em armazenamentos isolados.

Use sequências de caracteres de consulta para navegação de páginas. Se você passar o contexto para uma nova página, use a sequência de caracteres de consulta passada para a página para transmitir todos os dados ou transmitir um identificador exclusivo (um token) para um localizador de serviço que possa buscar dados com base nesse token. Não presuma que ViewModels ou o estado da página estejam disponíveis quando suas páginas forem ativadas após a marca de exclusão.

Entenda seus seletores e iniciadores. Nem todos são marcas de exclusão, e há regras específicas sobre como conectar ouvintes de eventos aos seletores. Para conhecer essas regras, leia “Como: Usar seletores para o Windows Phone”, em bit.ly/edEsGQ.

Lembre-se do relacionamento entre OnNavigatedTo e PhoneApplicationPage.Loaded. Em OnNavigatedTo, a árvore visual da página não foi totalmente criada ainda. Normalmente, você terá de extrair o estado restaurado, mas aguarde até o evento Loaded da página para restaurar o estado da interface do usuário. Exemplos de ações que devem ser retardadas incluem a configuração de Focus, a configuração de SelectedIndex em uma tabela dinâmica e a rolagem.

Se estiver fazendo muito para salvar e restaurar dados — sendo que não deveria estar — considere algumas otimizações avançadas. Observe que você deve fazer isso apenas se for necessário, e deve certificar-se de testá-las integralmente.

Para detectar marcas de exclusão, defina um sinalizador em sua classe de aplicativos em Deactivated. Se o sinalizador não for redefinido em Activated, isso significa que você não foi marcado para exclusão e todas as suas páginas ainda estarão na memória e não exigirão restauração. Para combiná-lo com a detecção para marca de exclusão em páginas, você poderá usar um token para cada ativação dentro de uma sessão.

Outra otimização é ouvir a substituição OnNavigatingFrom da página e detectar a direção. Se NavigationMode estiver voltando, a página será destruída e não haverá razão para salvar o seu estado.

Mais uma vez, o planejamento é a chave para uma marca de exclusão adequada. Não deixe a marca de exclusão para o final do ciclo de desenvolvimento e tente readaptá-la. Planeje-a antecipadamente, implemente-a à medida que for avançando e teste-a integralmente. 

Você pode torná-la excelente

Uma última dica para os desenvolvedores é pensar bastante sobre como tornar sua experiência perfeita. Os controles do Windows Phone 7 permitem controlar uma página com facilidade. Para torná-la realmente simples para o usuário — para que ele sinta como se nunca tivesse saído da página ou do aplicativo — você deverá considerar a restauração do seguinte:

  • SelectedIndex para a tabela dinâmica (se a página contiver uma tabela dinâmica)
  • Posição de rolagem em uma ListBox ou qualquer outra ScrollViewer na página
  • Níveis de zoom e outras transformações em um controle de mapa, um visualizador de imagens ou qualquer outro controle que suporte manipulação
  • Texto não confirmado em uma TextBox; se a TextBox for crítica para o aplicativo (o texto escrito em um aplicativo do Twitter, por exemplo), considere restaurar a seleção do texto, a posição do sinal de intercalação e o foco
  • Elemento que tinha o foco em uma página (especialmente se for uma TextBox, que precisa mostrar SIP)

O que você não deve restaurar é o SelectedItem em um panorama. O panorama não dá suporte a esse recurso, e configurar o DefaultItem em um panorama não é o mesmo que um deslocamento panorâmico para a página correta. Recomendo que você evite usar DefaultItem como um meio de voltar para o item de panorama selecionado antes de ocorrer a marca de exclusão.

Para ver essas dicas em ação, inicie o exemplo LPW.TombstoningWithState no download do código. O arquivo readme.txt possui ponteiros e scripts para cada cenário.

Alguns comentários finais

Este artigo não constitui uma referência abrangente sobre o assunto de marcas de exclusão. Mas agora que você sabe o que procurar, comece apresentando alguns padrões de marca de exclusão em seus próprios aplicativos do Windows Phone 7. Acredito que você verá imediatamente o quanto ele melhora a experiência do usuário.

Jaime Rodriguez é um dos principais especialistas da Microsoft e orienta a adoção de tecnologias de clientes emergentes, como do Silverlight e do Windows Phone 7. Você pode contatá-lo pelo Twitter em twitter.com/jaimerodriguez ou pode ler seu blog em blogs.msdn.com/jaimer.

Agradecemos ao seguinte especialista técnico pela revisão deste artigo: Peter Torr