page.title=Tratar alterações no tempo de execução page.tags=atividade,ciclo de vida @jd:body
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:
Permita que a atividade reinicie quando uma configuração muda, mas transporte um objeto de estado para a nova instância da atividade.
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.
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:
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.
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}.