Gerenciar mudanças de configuração

Mantenha tudo organizado com as coleções Salve e categorize o conteúdo com base nas suas preferências.

Algumas configurações do dispositivo podem mudar enquanto o app está em execução. Elas incluem, entre outras:

  • Tamanho da tela do app
  • Orientação da tela
  • Tamanho e peso da fonte
  • Localidade
  • Modo escuro x modo claro
  • Disponibilidade de teclado

A maioria dessas mudanças de configuração ocorre devido a alguma interação do usuário. Por exemplo, girar ou dobrar o dispositivo muda a quantidade de espaço disponível na tela para o app. Da mesma forma, mudar as configurações do dispositivo, como tamanho da fonte, idioma ou tema preferido, altera esses valores no objeto Configuration.

Esses parâmetros geralmente exigem mudanças grandes na interface do app. Por isso, a plataforma Android tem um mecanismo específico para quando acontecem essas mudanças. Ele é chamado de Recriação de atividades.

Recriação de atividades

O sistema recria uma atividade quando ocorre uma mudança de configuração. Ele chama onDestroy() e destrói a instância de atividade existente. Em seguida, cria uma nova instância usando onCreate(). Essa nova instância de atividade é inicializada com a configuração atualizada. Isso também significa que o sistema recria a interface com a nova configuração.

O comportamento de recriação ajuda o app a se adaptar a novas configurações, recarregando-o automaticamente com os recursos alternativos que correspondem à nova configuração do dispositivo.

Exemplo de recriação

Considere uma TextView que exibe um título estático usando android:text="@string/title", conforme definido em um arquivo XML de layout. Quando a visualização é criada, ela define o texto exato uma vez, com base no idioma atual. Se o idioma muda, o sistema recria a atividade. Consequentemente, o sistema também recria a visualização e a inicializa com o valor correto com base no novo idioma.

A recriação também exclui qualquer estado que você tenha mantido como campos na atividade ou em qualquer um dos fragmentos, visualizações e outros objetos contidos nele. Isso acontece porque a recriação de atividades cria uma instância completamente nova da atividade e da interface. Além disso, a atividade antiga não fica mais visível ou válida, e as referências restantes a ela ou aos objetos contidos nela ficam desatualizadas. Elas podem causar bugs, vazamentos de memória e falhas.

Expectativas dos usuários

O usuário de um app espera que o estado seja preservado. Se um usuário estiver preenchendo um formulário e abrir outro app no modo Várias janelas para procurar alguma informação, ele terá uma experiência do usuário ruim se retornar a um formulário em branco ou para outra parte do app. Você precisa garantir uma experiência consistente durante mudanças de configuração e recriações de atividades.

Para verificar se o estado é preservado no aplicativo, é possível executar ações que causem mudanças de configuração enquanto o app está em primeiro plano e em segundo plano. Essas ações incluem:

  • Girar o dispositivo
  • Entrar no modo de várias janelas
  • Redimensionar o aplicativo no modo de várias janelas ou em uma janela de formato livre
  • Dobrar um dispositivo dobrável com várias telas
  • Mudar o tema do sistema, como o modo escuro ou o modo claro
  • Mudar o tamanho da fonte
  • Mudar o idioma do sistema ou do app
  • Conectar ou desconectar um teclado de hardware
  • Conectar ou desconectar uma base

Há três abordagens principais que podem ser adotadas para preservar o estado relevante durante a recriação de atividades. O que é relevante depende do tipo de estado que você quer preservar:

  • Persistência local para processar o encerramento do processo para dados complexos ou grandes. O armazenamento local persistente inclui bancos de dados ou o DataStore.
  • Objetos retidos, como ViewModels, para processar o estado relacionado à interface na memória enquanto o usuário está usando o app.
  • Estado de instância salvo para lidar com o encerramento do processo iniciado pelo sistema e manter o estado temporário que depende da entrada ou da navegação do usuário.

A página Salvar estados da interface descreve as APIs para cada um deles em detalhes e indica quando é apropriado usar cada uma.

Restringir a recriação de atividades

É possível impedir a recriação automática de atividades para determinadas mudanças de configuração. A recriação de atividades resulta na recriação de toda a interface e de qualquer objeto derivado da atividade. Pode ser que você tenha bons motivos para não fazer isso. Por exemplo, seu app pode não precisar atualizar recursos durante uma mudança de configuração específica ou pode ter uma limitação de desempenho. Nesse caso, você pode declarar que a atividade processa a mudança de configuração e impedir que o sistema reinicie a atividade.

Você pode desativar a recriação de atividades para mudanças de configuração específicas. Para fazer isso, adicione o tipo de configuração a android:configChanges na entrada <activity> no arquivo AndroidManifest.xml. Os valores possíveis aparecem na documentação do atributo android:configChanges.

O código do manifesto abaixo desativa a recriação de atividades para MyActivity quando a orientação da tela e a disponibilidade do teclado mudam:

<activity
    android:name=".MyActivity"
    android:configChanges="orientation|screenSize|screenLayout|keyboardHidden"
    android:label="@string/app_name">

Algumas mudanças de configuração sempre fazem a atividade ser reiniciada. Não é possível desativá-las. Por exemplo, não é possível desativar a mudança de cores dinâmicas lançada na API 32. Outras mudanças na configuração poderão se comportar de maneira semelhante no futuro.

Reagir a mudanças de configuração no sistema de visualização

No sistema de visualização, quando ocorre uma mudança de configuração para a qual você desativou a recriação de atividades, a atividade recebe uma chamada para Activity.onConfigurationChanged(). Todas as visualizações anexadas também recebem uma chamada para View.onConfigurationChanged(). Para mudanças de configuração que não foram adicionadas a android:configChanges, o sistema recria a atividade como de costume.

O método de callback onConfigurationChanged() recebe um objeto Configuration que especifica a nova configuração do dispositivo. Leia os campos no objeto Configuration para determinar qual precisa ser a nova configuração. Para fazer as mudanças seguintes, atualize os recursos que você usa na interface. Quando o sistema chama esse método, o objeto Resources da atividade é atualizado para retornar recursos baseados na nova configuração. Isso permite redefinir elementos da interface sem que o sistema reinicie a atividade.

Por exemplo, a implementação de onConfigurationChanged() abaixo confere se algum teclado está disponível:

Kotlin

override fun onConfigurationChanged(newConfig: Configuration) {
    super.onConfigurationChanged(newConfig)

    // Checks whether any keyboard is available
    if (newConfig.keyboardHidden === Configuration.KEYBOARDHIDDEN_YES) {
        Toast.makeText(this, "Keyboard available", Toast.LENGTH_SHORT).show()
    } else if (newConfig.keyboardHidden === Configuration.KEYBOARDHIDDEN_NO) {
        Toast.makeText(this, "No keyboard", Toast.LENGTH_SHORT).show()
    }
}

Java

@Override
public void onConfigurationChanged(Configuration newConfig) {
    super.onConfigurationChanged(newConfig);

    // Checks whether any keyboard is available
    if (newConfig.keyboardHidden == Configuration.KEYBOARDHIDDEN_YES) {
        Toast.makeText(this, "Keyboard available", Toast.LENGTH_SHORT).show();
    } else if (newConfig.keyboardHidden == Configuration.KEYBOARDHIDDEN_NO){
        Toast.makeText(this, "No keyboard", Toast.LENGTH_SHORT).show();
    }
}

Se não for necessário atualizar o aplicativo com base nessas mudanças de configuração, não implemente onConfigurationChanged(). Nesse caso, todos os recursos usados antes da mudança de configuração continuarão sendo usados, e somente o reinício da atividade será evitado. Por exemplo, no caso de um app para TV, pode não ser necessário reagir quando um teclado Bluetooth for conectado ou removido.

Reter o estado

Ao usar essa técnica, ainda será preciso manter o estado durante o ciclo de vida normal da atividade. Isso acontece pelos seguintes motivos:

  • Mudanças inevitáveis: mudanças de configuração que não podem ser impedidas podem reiniciar o aplicativo.
  • Encerramento do processo: o aplicativo precisa ser capaz de processar o encerramento do processo iniciado pelo sistema. Se o usuário sair do aplicativo e ele for para o segundo plano, o sistema poderá destruí-lo.

Reagir a mudanças de configuração no Jetpack Compose

O Jetpack Compose permite que seu app reaja com mais facilidade a mudanças de configuração. No entanto, se você desativar a recriação de atividades para todas as mudanças de configuração, ainda será necessário garantir que o app processe corretamente as mudanças de configuração.

O objeto Configuration está disponível na hierarquia de interfaces do Compose com a composição local LocalConfiguration. Sempre que ela muda, as funções combináveis que leem LocalConfiguration.current são recompostas. Para saber mais sobre como os locais de composição funcionam, consulte a documentação Dados com escopo local com CompositionLocal.

Exemplo

No exemplo abaixo, um elemento combinável mostra uma data com um formato específico. Esse elemento reage às mudanças de configuração de localidade do sistema chamando ConfigurationCompat.getLocales() com LocalConfiguration.current.

@Composable
fun DateText(year: Int, dayOfYear: Int) {
    val dateTimeFormatter = DateTimeFormatter.ofPattern(
        "MMM dd",
        ConfigurationCompat.getLocales(LocalConfiguration.current)[0]
    )
    Text(
        dateTimeFormatter.format(LocalDate.ofYearDay(year, dayOfYear))
    )
}

Para evitar a recriação de atividades quando a localidade muda, a atividade que hospeda o código do Compose precisa desativar as mudanças de configuração de localidade. Para isso, defina android:configChanges como locale|layoutDirection.

Principais conceitos e ideias das mudanças de configuração

Confira um resumo do que você precisa saber ao trabalhar em mudanças de configuração:

  • Configurações: as configurações do dispositivo definem como a interface será exibida para o usuário, como tamanho da tela, localidade ou tema do sistema do app.
  • Mudanças de configuração: as configurações mudam com a interação do usuário. Por exemplo, o usuário pode mudar as configurações do dispositivo ou interagir fisicamente com ele. Com isso, a configuração será alterada. Não é possível "impedir" mudanças de configuração.
  • Recreação de atividades: as mudanças de configuração resultam na recriação de atividades por padrão. Esse é um mecanismo integrado para reinicializar o estado do app na nova configuração.
  • Destruição de atividades: a recriação de atividades faz com que o sistema destrua a instância antiga da atividade e crie uma nova no lugar. A instância antiga fica obsoleta. As referências restantes resultarão em vazamentos de memória, bugs ou falhas.
  • Estado: o estado na instância da atividade antiga não está presente na nova instância da atividade porque são duas instâncias de objeto diferentes. Preserve o estado do app e do usuário, conforme descrito em Salvar estados da interface.
  • Desativação: desativar a recriação de atividades para um tipo de mudança de configuração é uma possível otimização. É necessário garantir que o app seja atualizado corretamente em resposta à nova configuração.

Práticas recomendadas

Para oferecer uma boa experiência ao usuário, observe o seguinte:

  • Mudanças de configuração são frequentes: não presuma que as mudanças de configuração sejam raras ou que nunca aconteçam, independentemente do nível da API, do formato ou do kit de ferramentas de interface. Quando um usuário causa uma mudança na configuração, ele espera que os apps sejam atualizados e continuem funcionando corretamente com a nova configuração.
  • Preserve o estado: não perca o estado do usuário quando ocorre a recriação de atividades. Preserve o estado, conforme descrito em Salvar estados da interface.
  • Evite a desativação como uma correção rápida: não desative a recriação de atividades como um atalho para evitar a perda de estado. Para desativar a recriação de atividades, é necessário cumprir a promessa de processar a mudança. Você ainda pode perder o estado devido à recriação de atividades de outras mudanças de configuração, encerramento do processo ou fechamento do app. É impossível desativar completamente a recriação de atividades. Preserve o estado, conforme descrito em Salvar estados da interface.
  • Não evite mudanças de configuração: não coloque restrições na orientação, na proporção ou na capacidade de redimensionamento para evitar mudanças de configuração e recriação de atividades. Isso afeta negativamente os usuários que querem usar seu app da maneira que preferirem.

Gerenciar mudanças de configuração de tamanho

Mudanças de configuração de tamanho podem acontecer a qualquer momento. Isso acontece com mais frequência quando o app é executado em um dispositivo de tela grande, em que os usuários podem entrar no modo de várias janelas. Eles esperam que o app funcione bem nesse ambiente.

Existem dois tipos gerais de mudanças de tamanho: significativa e insignificante. Uma mudança "significativa" é aquela em que um conjunto diferente de recursos alternativos se aplica à nova configuração devido a uma diferença no tamanho da tela, como largura, altura ou menor largura. Esses recursos incluem os definidos pelo app e qualquer uma das bibliotecas que contenham recursos.

Restringir a recriação de atividades para mudanças de configuração de tamanho

Quando você desativa a recriação de atividades para mudanças de configuração de tamanho, o sistema não recria a atividade. Em vez disso, ele recebe uma chamada para Activity.onConfigurationChanged(). Todas as visualizações anexadas recebem uma chamada para View.onConfigurationChanged().

Permitir a recriação de atividades para mudanças de configuração de tamanho

Na API 24 e em versões mais recentes, a recriação de atividades vai ocorrer para mudanças de configuração de tamanho se a mudança for significativa. Quando o sistema não recria uma atividade devido ao tamanho insuficiente, ele pode chamar Activity.onConfigurationChanged() e View.onConfigurationChanged().

Há algumas ressalvas a serem observadas em relação aos callbacks de atividade e de visualização:

  • Na API 30 e em versões mais recentes, o callback Activity.onConfigurationChanged() não é chamado.
  • Nas APIs 32 e 33, View.onConfigurationChanged() não é chamado. Esse bug foi corrigido em versões mais recentes da API.

Para um código que depende da detecção de mudanças de configuração de tamanho, recomendamos usar uma visualização de utilitário com um View.onConfigurationChanged() substituído, em vez de depender da recriação de atividades ou Activity.onConfigurationChanged().