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 só 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()
.