Gerenciar alterações de configuração

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

  • Tamanho de exibição 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. Esse mecanismo é a recriação de Activity.

Recriação de atividades

O sistema recria uma Activity quando ocorre uma mudança de configuração. Para fazer isso, o sistema chama onDestroy() e destrói a instância de Activity. Em seguida, ele cria uma nova instância de Activity usando o onCreate(), que é 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 mostra 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 limpa todos os estados mantidos como campos na Activity ou em qualquer um dos Fragment, View ou outros objetos contidos nela. Isso acontece porque a recriação da Activity cria uma instância completamente nova da Activity e da interface. Além disso, a Activity antiga não fica mais visível nem é 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 ruim se retornar a um formulário em branco ou para outra parte do app. Como desenvolvedor, você precisa fornecer uma experiência de usuário consistente, com mudanças de configuração e recriação 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 da Activity. Qual será usado depende do tipo de estado que você quer preservar:

  • Persistência local para lidar com o encerramento do processo para dados complexos ou grandes. O armazenamento local persistente inclui bancos de dados ou o DataStore.
  • Objetos retidos, como instâncias de ViewModel 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.

Para saber mais sobre as APIs das abordagens e quando o uso de cada uma delas é adequado, consulte Salvar estados da interface.

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 da Activity resulta na recriação de toda a interface e de todos os objetos derivados da Activity. Você pode ter bons motivos para evitar isso. Por exemplo, seu app pode não precisar atualizar recursos durante uma mudança de configuração específica ou você 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.

Se quiser desativar a recriação de atividades para mudanças específicas de configuração, 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 da Activity 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 aplicada no Android 12L (nível 32 da API).

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

No sistema de View, quando ocorre uma mudança de configuração para a qual você desativou a recriação de Activity, a atividade recebe uma chamada para Activity.onConfigurationChanged(). Todas as visualizações anexadas também recebem uma chamada para View.onConfigurationChanged(). No caso de 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 é sua nova configuração. Para fazer as mudanças subsequentes, atualize os recursos usados na interface. Quando o sistema chama esse método, o objeto Resources da atividade é atualizado para retornar recursos com base na nova configuração. Isso permite redefinir elementos da interface sem que o sistema reinicie a atividade.

Por exemplo, a seguinte implementação de onConfigurationChanged() confere se há um teclado disponível:

Kotlin

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

    // Checks whether a 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 a 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 app 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 ainda são usados, e apenas a reinicialização da atividade foi evitada. Por exemplo, é possível que um um app de TV não precise fazer nada quando um teclado Bluetooth for conectado ou desconectado.

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 é possível evitar podem reiniciar o app.
  • Encerramento do processo: seu app precisa ser capaz de lidar com o encerramento do processo iniciado pelo sistema. Se o usuário sair do aplicativo e ele for para o segundo plano, o app poderá ser destruído pelo sistema.

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 da Activity para todas as mudanças de configuração sempre que possível, o app ainda precisará processar as mudanças corretamente.

O objeto Configuration está disponível na hierarquia de interfaces do Compose com o local de composição LocalConfiguration. Sempre que ele 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 Dados com escopo local usando o 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 da Activity quando a localidade muda, a Activity 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.

Mudanças de configuração: principais conceitos e práticas recomendadas

Estes são os principais conceitos que você precisa conhecer ao trabalhar em mudanças de configuração:

  • Configurações: as configurações do dispositivo definem como a interface será mostrada para o usuário, como tamanho de exibição do app, localidade ou tema do sistema.
  • 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 a forma como interage fisicamente com ele. Não há como evitar mudanças de configuração.
  • Recriação da Activity: as mudanças de configuração resultam na recriação da Activity por padrão. Esse é um mecanismo integrado para reinicializar o estado do app na nova configuração.
  • Destruição da Activity: a recriação da Activity faz com que o sistema destrua a instância antiga da Activity e crie outra no lugar. A instância antiga agora está obsoleta. Qualquer referência restante a ela resulta em vazamentos de memória, bugs ou falhas.
  • Estado: o estado na instância antiga da Activity não está presente na nova instância da Activity, porque essas são duas instâncias diferentes de objeto. Preserve o estado do app e do usuário, conforme descrito em Salvar estados da interface.
  • Desativação: desativar a recriação de atividade para um tipo de mudança de configuração é uma possível otimização. Isso exige que o app seja devidamente atualizado de acordo com a nova configuração.

Para oferecer uma boa experiência ao usuário, siga estas práticas recomendadas:

  • Prepare-se para mudanças frequentes na configuração: não presuma que as mudanças de configuração serão raras ou nunca vão acontecer, independente do nível da API, do formato ou do kit de ferramentas da 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 da Activity. Siga as instruções descritas em Salvar estados da interface para preservar o estado.
  • Evite a desativação como uma correção rápida: não desative a recriação da Activity como um atalho para evitar a perda de estado. Se quiser desativar a recriação de atividade, é necessário cumprir a promessa de processar a mudança. Você ainda pode perder o estado devido à recriação da Activity por outras mudanças de configuração, ao encerramento do processo ou ao fechamento do app. É impossível desativar completamente a recriação da Activity. Siga as instruções descritas em Salvar estados da interface para preservar o estado.
  • Não evite mudanças de configuração: não coloque restrições de orientação, proporção ou redimensionamento para evitar mudanças na configuração e recriação de Activity. Isso afeta negativamente os usuários que querem usar seu app da maneira que preferem.

Gerenciar mudanças de configuração de tamanho

Mudanças na configuração com base no tamanho podem acontecer a qualquer momento e são mais prováveis quando o app é executado em um dispositivo com 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 aqueles que o próprio app define e os de qualquer uma das bibliotecas dele.

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

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

A recriação da Activity é desativada para mudanças de configuração com base no tamanho quando há android:configChanges="screenSize|smallestScreenSize|orientation|screenLayout" no arquivo de manifesto.

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

No Android 7.0 (nível 24 da API) e versões mais recentes, a recriação da Activity ocorre para mudanças de configuração baseadas em tamanho caso a mudança seja significativa. Quando o sistema não recria uma Activity devido ao tamanho insuficiente, ele pode chamar Activity.onConfigurationChanged() e View.onConfigurationChanged() como alternativa.

Há algumas ressalvas a serem observadas em relação aos callbacks de Activity e View quando a Activity não é recriada:

  • Do Android 11 (nível 30 da API) ao Android 13 (nível 33 da API), o método Activity.onConfigurationChanged() não é chamado.
  • Há um problema conhecido em que View.onConfigurationChanged() pode não ser chamado em alguns casos no Android 12L (nível 32 da API) e nas versões anteriores do Android 13 (nível 33 da API). Para saber mais, consulte este problema público. Isso já foi resolvido nas versões mais recentes do Android 13 e no Android 14.

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