lightbulb_outline Help shape the future of the Google Play Console, Android Studio, and Firebase. Start survey

Processamento de alterações no tempo de execução

Algumas configurações do dispositivo podem mudar durante o tempo de execução (como orientação de tela, disponibilidade do teclado e idioma). Quando ocorre uma alteração, o Android precisa reiniciar a execução de Activity (onDestroy() é chamado, seguido de onCreate()). O comportamento de reinício foi projetado para ajudar o aplicativo a se adaptar a novas configurações recarregando automaticamente o aplicativo com recursos alternativos que correspondam com a configuração do dispositivo.

Para processar adequadamente um reinício, é importante que a atividade se restaure ao estado anterior por meio do ciclo de vida da atividade, no qual o Android chama onSaveInstanceState() antes de destruir a atividade para que seja possível salvar os dados acerca do estado do aplicativo. Em seguida, é possível restaurar o estado durante onCreate() ou onRestoreInstanceState().

Para testar se o aplicativo se reinicia com o estado de aplicativo intacto, deve-se invocar as alterações de configuração (como alterações na orientação da tela) enquanto executa diversas tarefas no aplicativo. O aplicativo deve ser capaz de reiniciar a qualquer momento sem perda de dados do usuário ou estado para processar eventos como alterações de configuração ou quando o usuário recebe uma chamada telefônica e, em seguida, retorna ao aplicativo bem depois de destruído o processo do aplicativo. Para ver como restaurar o estado da atividade, leia sobre o Ciclo de vida da atividade.

No entanto, pode-se encontrar uma situação em que o reinício do aplicativo e a restauração representem quantidades significativas de dados que podem ser custosos e prejudicar a experiência do usuário. Nessa situação, há duas opções:

  1. Reter um objeto durante uma alteração de configuração

    Permita que a atividade reinicie quando uma configuração muda, mas transporte um objeto de estado para a nova instância da atividade.

  2. Processar a alteração de configuração por conta própria

    Evite que o sistema reinicie a atividade durante certas alterações de configuração, mas receba um retorno de chamada quando as configurações se alteram, para que você atualize manualmente a atividade conforme necessário.

Retenção de um objeto durante uma alteração de configuração

Se a retenção da atividade exigir a recuperação de grandes conjuntos de dados, restabelecer uma conexão de rede ou executar outras operações intensivas, um reinício completo devido a uma alteração de configuração pode prejudicar a experiência do usuário. Além disso, pode não ser possível restaurar completamente o estado da atividade com o Bundle que o sistema salva com o retorno de chamada onSaveInstanceState() — ele não foi projetado para transportar objetos grandes (como bitmaps) e os dados contidos devem ser serializados e, em seguida, desserializados, o que pode consumir muita memória e retardar a alteração de configuração. Nesse caso, para aliviar o peso de reinicializar a atividade, pode-se reter um Fragment quando a atividade for reiniciada devido a uma alteração de configuração. Esse fragmento pode conter referências a objetos com estado que seja preciso reter.

Quando o sistema Android encerra a atividade devido a uma alteração de configuração, os fragmentos da atividade marcados para serem retidos não são destruídos. É possível adicionar esses fragmentos à atividade para preservar objetos de estado.

Para reter objetos de estado em um fragmento durante uma alteração de configuração em tempo de execução:

  1. Estenda a classe Fragment e declare referências aos objetos de estado.
  2. Chame setRetainInstance(boolean) quando o fragmento for criado.
  3. Acrescente o fragmento à atividade.
  4. Use FragmentManager para recuperar o fragmento quando a atividade for reiniciada.

Por exemplo, defina o fragmento da seguinte forma:

public class RetainedFragment extends Fragment {

    // data object we want to retain
    private MyDataObject data;

    // this method is only called once for this fragment
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // retain this fragment
        setRetainInstance(true);
    }

    public void setData(MyDataObject data) {
        this.data = data;
    }

    public MyDataObject getData() {
        return data;
    }
}

Atenção: Ao restaurar qualquer objeto, não se deve nunca passar um objeto vinculado a Activity, como um Drawable, um Adapter, um View ou qualquer outro objeto associado a um Context. Se o fizer, ele vazará todas as exibições e recursos da instância da atividade original. (vazar recursos significa que o aplicativo mantém a retenção deles, que não podem ser recolhidos, o que causa perda de memória).

Em seguida, use FragmentManager para adicionar o fragmento à atividade. É possível obter o objeto de dados do fragmento quando a atividade for reiniciada durante as alterações de configuração em tempo de execução. Por exemplo, defina a atividade da seguinte forma:

public class MyActivity extends Activity {

    private RetainedFragment dataFragment;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        // find the retained fragment on activity restarts
        FragmentManager fm = getFragmentManager();
        dataFragment = (DataFragment) fm.findFragmentByTag(“data”);

        // create the fragment and data the first time
        if (dataFragment == null) {
            // add the fragment
            dataFragment = new DataFragment();
            fm.beginTransaction().add(dataFragment, “data”).commit();
            // load the data from the web
            dataFragment.setData(loadMyData());
        }

        // the data is available in dataFragment.getData()
        ...
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        // store the data in the fragment
        dataFragment.setData(collectMyLoadedData());
    }
}

Nesse exemplo, onCreate() adiciona um fragmento ou restaura uma referência a ele. onCreate() também armazena o objeto de estado dentro da instância de fragmento. onDestroy() atualiza o objeto de estado dentro da instância de fragmento retida.

Processamento da alteração de configuração por conta própria

Se o aplicativo não tiver que atualizar recursos durante uma alteração de configuração específica e se houver alguma limitação de desempenho que impeça a atividade de reiniciar, será possível declarar que a atividade processa ela mesma da alteração de configuração, o que evitará que o sistema reinicie a atividade.

Observação: Processar a alteração de configuração por conta própria pode dificultar muito o uso de recursos alternativos, pois o sistema não os aplicará automaticamente. Essa técnica deve ser considerada um último recurso, quando é preciso evitar reinícios devido a uma alteração de configuração e não é recomendada para a maioria dos aplicativos.

Para declarar que a atividade processa uma alteração de configuração, edite o elemento <activity> apropriado no arquivo de manifesto para que inclua o atributo android:configChanges com um valor que represente a configuração a ser processada. Os valores possíveis estão listados na documentação do atributo android:configChanges (os valores mais comumente usados são "orientation", para impedir reinicializações durante alterações na orientação da tela, e "keyboardHidden" para impedir reinicializações quando a disponibilidade do teclado mudar). Para declarar vários valores de configuração no atributo, usa-se um separador na forma de caractere barra reta |.

Por exemplo, o código de manifesto a seguir declara uma atividade que processa tanto a alteração de orientação da tela quanto a disponibilidade do teclado:

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

Agora, quando uma dessas configurações mudar, MyActivity não será reiniciada. Em vez disso, a MyActivity recebe uma chamada para onConfigurationChanged(). Um objeto Configuration é passado a esse método e especifica a nova configuração do dispositivo. Ao ler os campos em Configuration, pode-se determinar a nova configuração e atualizar os recursos na interface para fazer as alterações adequadas. No momento em que o método é chamado, o objeto Resources da atividade é atualizado para retornar recursos com base na nova configuração, o que facilita a redefinição de elementos da IU sem que o sistema reinicie a atividade.

Atenção: A partir do Android 3.2 (nível da API 13), o "tamanho da tela" também muda quando o dispositivo alterna entre as orientações retrato e paisagem. Assim, se você deseja evitar que o tempo de execução seja reiniciado devido a uma mudança da orientação ao desenvolver uma API de nível 13 ou posterior (conforme declarado pelos atributos minSdkVersion e targetSdkVersion), é preciso incluir o valor "screenSize" além do valor "orientation". Ou seja, é preciso declarar android:configChanges="orientation|screenSize". No entanto, se o aplicativo tem como alvo uma API nível 12 ou inferior, a atividade sempre processa ela mesma a alteração de configuração (essa mudança de configuração não reinicia a atividade, mesmo em execução em Android 3.2 ou dispositivo posterior).

Por exemplo, a implementação de onConfigurationChanged() a seguir verifica a orientação de dispositivo atual:

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

    // Checks the orientation of the screen
    if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) {
        Toast.makeText(this, "landscape", Toast.LENGTH_SHORT).show();
    } else if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT){
        Toast.makeText(this, "portrait", Toast.LENGTH_SHORT).show();
    }
}

O objeto Configuration representa todas as configurações atuais, não somente as que foram alteradas. Na maior parte do tempo, não importa como a configuração foi alterada; basta reatribuir todos os recursos que apresentam alternativas à configuração que estão sendo processadas. Por exemplo, como o objeto Resources está atualizado, pode-se redefinir qualquer ImageView com setImageResource() e será usado o recurso adequado à nova configuração (conforme é descrito em Fornecimento de recursos).

Observe que os valores dos campos de Configuration são números inteiros que correspondem a constantes específicas da classe Configuration. Para ver a documentação sobre as constantes a usar em cada campo, consulte o campo em questão na referência sobre Configuration.

Lembre-se: Ao declarar a atividade para processar uma alteração de configuração, você é responsável por redefinir todos os elementos que fornecem alternativas. Se você declarar a atividade para processar a alteração de orientação e tiver imagens que alterariam entre paisagem e retrato, é preciso reatribuir cada recurso a cada elemento durante onConfigurationChanged().

Se não for necessário atualizar o aplicativo com base nessas alterações de configuração, pode-se não implementar onConfigurationChanged(). Nesse caso, todos os recursos usados antes da alteração de configuração ainda são usados e somente o reinício da atividade é evitado. No entanto, o aplicativo deve sempre ser capaz de encerrar e reiniciar com o estado anterior intacto, portanto, essa técnica não deve ser considerada uma fuga da retenção do estado durante o ciclo de vida normal da atividade, Não somente porque há outras alterações de configuração impossíveis de evitar que reiniciem o aplicativo, mas também porque devem-se processar eventos como o do usuário que sai do aplicativo e ele é destruído antes de o usuário voltar a ele.

Para saber mais sobre as alterações de configuração que devem ser processadas na atividade, consulte a documentação sobre android:configChanges e a classe Configuration.