page.title=Tratar alterações no tempo de execução page.tags=atividade,ciclo de vida @jd:body

Neste documento

  1. Retenção de um objeto durante uma alteração de configuração
  2. Tratar você mesmo da alteração de configuração

Veja também

  1. Fornecimento de recursos
  2. Acesso aos recursos
  3. Alteração mais rápida da orientação da tela

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 {@link android.app.Activity} ({@link android.app.Activity#onDestroy()} é chamado, seguido de {@link android.app.Activity#onCreate(Bundle) 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 tratar 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 {@link android.app.Activity#onSaveInstanceState(Bundle) 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 {@link android.app.Activity#onCreate(Bundle) onCreate()} ou {@link android.app.Activity#onRestoreInstanceState(Bundle) 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 tratar 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, temos 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. Tratar você mesmo da alteração de configuração

    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 {@link android.os.Bundle} que o sistema salva com o retorno de chamada {@link android.app.Activity#onSaveInstanceState(Bundle) 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 {@link android.app.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 {@link android.app.Fragment} e declare referências aos objetos de estado.
  2. Chame {@link android.app.Fragment#setRetainInstance(boolean)} quando o fragmento for criado.
  3. Acrescente o fragmento à atividade.
  4. Use {@link android.app.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 {@link android.app.Activity}, como um {@link android.graphics.drawable.Drawable}, um {@link android.widget.Adapter}, um {@link android.view.View} ou qualquer outro objeto associado a um {@link android.content.Context}. Se o fizer, ele vazará todas as vistas 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 {@link android.app.FragmentManager} para adicionar o fragmento à atividade. É possível obter o objeto de dados do fragmento quando a atividade reiniciar 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, {@link android.app.Activity#onCreate(Bundle) onCreate()} adiciona um fragmento ou restaura uma referência a ele. {@link android.app.Activity#onCreate(Bundle) onCreate()} também armazena o objeto de estado dentro da instância de fragmento. {@link android.app.Activity#onDestroy() onDestroy()} atualiza o objeto de estado dentro da instância de fragmento retida.

Tratar você mesmo da alteração de configuração

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, pode-se declarar que a atividade trata ela mesma da alteração de configuração, o que evita que o sistema reinicie a atividade.

Observação: Tratar você mesmo da alteração de configuração pode dificultar muito o uso de recursos alternativos, pois o sistema não os aplicará automaticamente. Esta 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 manipula uma alteração de configuração, edite o elemento {@code <activity>} apropriado no arquivo de manifesto para que inclua o atributo {@code android:configChanges} com um valor que represente a configuração a tratar. Os valores possíveis estão listados na documentação do atributo {@code android:configChanges} (os valores mais comumente usados são {@code "orientation"}, para impedir reinícios durante alterações na orientação da tela, e {@code "keyboardHidden"} para impedir reinícios quando a disponibilidade do teclado muda). Para declarar vários valores de configuração no atributo, usa-se um separador na forma de caractere barra reta {@code |}.

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

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

Agora, quando uma dessas configurações mudar, {@code MyActivity} não reiniciará. Em vez disso, a {@code MyActivity} recebe uma chamada para {@link android.app.Activity#onConfigurationChanged(Configuration) onConfigurationChanged()}. Um objeto {@link android.content.res.Configuration} é passado a esse método e especifica a nova configuração do dispositivo. Ao ler os campos em {@link android.content.res.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 {@link android.content.res.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 reinicie devido a uma mudança da orientação ao desenvolver uma API nível 13 ou posterior (conforme declarado pelos atributos {@code minSdkVersion} e {@code targetSdkVersion}), é preciso incluir o valor {@code "screenSize"} além do valor {@code "orientation"}. Ou seja, é preciso declarar {@code android:configChanges="orientation|screenSize"}. No entanto, se o aplicativo tem como alvo uma API nível 12 ou inferior, a atividade sempre trata 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 a seguir {@link android.app.Activity#onConfigurationChanged(Configuration) onConfigurationChanged()} 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 {@link android.content.res.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 tratadas. Por exemplo: como o objeto {@link android.content.res.Resources} está atualizado, pode-se redefinir qualquer {@link android.widget.ImageView} com {@link android.widget.ImageView#setImageResource(int) setImageResource()} e será usado o recurso adequado à nova configuração (conforme descrito em Como fornecer recursos).

Observe que os valores dos campos de {@link android.content.res.Configuration} são inteiros que correspondem a constantes específicas da classe {@link android.content.res.Configuration}. Para ver a documentação sobre as constantes a usar em cada campo, consulte o campo em questão na referência sobre {@link android.content.res.Configuration}.

Lembre-se: ao declarar a atividade para tratar uma alteração de configuração, você é responsável por redefinir todos os elementos que fornecem alternativas. Se você declarar a atividade para tratar a alteração de orientação e tiver imagens que alterariam entre paisagem e retrato, é preciso reatribuir cada recurso a cada elemento durante {@link android.app.Activity#onConfigurationChanged(Configuration) onConfigurationChanged()}.

Se não for necessário atualizar o aplicativo com base nessas alterações de configuração, pode-se não implementar {@link android.app.Activity#onConfigurationChanged(Configuration) 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 se encerrar e reiniciar com seu 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 tratar eventos como o do usuário que sai do aplicativo e ele é destruído antes de o usuário voltar a ele.

Para obter mais informações sobre as alterações de configuração que devem ser tratadas na atividade, consulte a documentação sobre {@code android:configChanges} e a classe {@link android.content.res.Configuration}.