13 setembro 2012

Mudanças de perspectivas em aplicações Android: evite recriar a Activity

Usuários de aplicativos em geral podem requerer mudar a orientação do aparelho para visualizar melhor os conteúdos sendo exibidos na tela. Dependendo do que está sendo visto é comum girar a orientação da tela para o sentido horizontal (landscape) ou sentido vertical (portrait).

(1) Tela em modo Portrait


Em aplicações Android e iOS (smartphones e tablets) é comum implementar telas que vão funcionar em diferentes perspectivas de orientação de tela. No entanto, programadores de Android precisam estar atentos que quando ocorrem mudanças de orientação no aplicativo, o comportamento padrão do Android recria a Activity em execução, chamando os métodos do ciclo de vida: onPause(), onStop(), onDestroy(), para em seguida chamar o método onCreate() novamente, além dos métodos do ciclo de vida: onStart() e onResume().



Problema / Solução
Se a tela atual da sua aplicação apenas desenha elementos visuais sem obter dados de fontes custosas (para memória em geral ou uso da Internet para baixar os dados), então esse procedimento padrão do Android não será um problema. Contudo, caso sua Activity requeira dados da internet ou algum outro tipo de esquema de processamento “mais pesado” para ser realizado, talvez seja necessário evitar recarregar a instância da Activity, mantendo o estado dos objetos carregados na memória. Desta forma podemos evitar consultar a rede novamente, além de otimizar o uso do processamento do aparelho, melhorando o consumo da bateria. :)

Esse comportamento pode ser realizado de 2 formas: (que eu conheço)
  1. Salvar os dados atuais (o estado atual) utilizando o mecanismo do ciclo de vida da Activity do Android, através dos métodos onSaveInstanceState(bundle) para salvar, e o onRetainNonConfigurationInstance() para recuperar os dados do estado atual;
  2. Alterar o comportamento default do Android, descrito acima, evitando que a Activity seja automaticamente recriada. Desta forma o Android apenas irá notificar Activity que ocorreu uma mudança de orientação, através do método onConfigurationChanged(Configuration c).
Neste post vou mostrar como podemos implementar abordagem (2), ou seja, como evitar que o Android recrie Activity do ciclo de vida do aplicativo.

Essa dica eu encontrei no site oficial do desenvolvedor Android. Está neste link: Handling Runtime Changes: http://developer.android.com/guide/topics/resources/runtime-changes.html

Vou descrever passo a passo:

Passo 1: altere o arquivo AndroidManifest.xml conforme o exemplo abaixo adicionando a propriedade chamada (android:configChanges="orientation|screenSize") da sua Activity:



    

    
        
            
                

                
            
        
    



Passo 2: implementar no código Java da sua Activity o método conforme exemplo:
package br.com.blogspot.profdouglas.dicas3.orientationchange;

import android.app.Activity;
import android.content.res.Configuration;
import android.os.Bundle;
import android.util.Log;
import android.view.Menu;
import android.widget.TextView;

public class OrientationChange extends Activity {

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

  Log.d("DOUGLAS", "OrientationChange.onCreate");

  TextView orientationTextView = (TextView) findViewById(R.id.orientationTextView);

  // detect the current orientation
  int currentOrientation = getResources().getConfiguration().orientation;

  if (currentOrientation == Configuration.ORIENTATION_LANDSCAPE) {
   // setting orientation landscape
   orientationTextView.setText(R.string.orientation_landscape);

  } else if (currentOrientation == Configuration.ORIENTATION_PORTRAIT) {
   // setting orientation portrait
   orientationTextView.setText(R.string.orientation_portrait);
  }

 }

 @Override
 public boolean onCreateOptionsMenu(Menu menu) {
  getMenuInflater().inflate(R.menu.activity_orientation_change, menu);
  return true;
 }

 @Override
 protected void onStart() {
  super.onStart();
  Log.i("DOUGLAS", "OrientationChange.onStart");
 }

 @Override
 protected void onResume() {
  super.onResume();
  Log.i("DOUGLAS", "OrientationChange.onResume");
 }

 @Override
 protected void onStop() {
  super.onStop();
  Log.i("DOUGLAS", "OrientationChange.onStop");
 }

 @Override
 protected void onPause() {
  super.onPause();
  Log.i("DOUGLAS", "OrientationChange.onPause");
 }

 @Override
 protected void onDestroy() {
  super.onDestroy();
  Log.i("DOUGLAS", "OrientationChange.onDestroy");
 }

 /**
  * Caution: Beginning with Android 3.2 (API level 13), the "screen size"
  * also changes when the device switches between portrait and landscape
  * orientation. Thus, if you want to prevent runtime restarts due to
  * orientation change when developing for API level 13 or higher (as
  * declared by the minSdkVersion and targetSdkVersion attributes), you must
  * include the "screenSize" value in addition to the "orientation" value.
  * That is, you must decalare
  * android:configChanges="orientation|screenSize". However, if your
  * application targets API level 12 or lower, then your activity always
  * handles this configuration change itself (this configuration change does
  * not restart your activity, even when running on an Android 3.2 or higher
  * device).
  */
 @Override
 public void onConfigurationChanged(Configuration newConfig) {
  super.onConfigurationChanged(newConfig);
  Log.d("DOUGLAS", "MainActivity.onConfigurationChanged");

  TextView orientationTextView = (TextView) findViewById(R.id.orientationTextView);

  if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT) {
   Log.i("DOUGLAS",
     "MainActivity.onConfigurationChanged (ORIENTATION_PORTRAIT)");
   // setting orientation portrait
   orientationTextView.setText(R.string.orientation_portrait);

  } else if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) {
   Log.i("DOUGLAS",
     "MainActivity.onConfigurationChanged (ORIENTATION_LANDSCAPE)");
   orientationTextView.setText(R.string.orientation_landscape);
  }
 }

}


Observe o javadoc que deixei para que você entenda porque a propriedade do manifest precisou utilizar dois valores. A figura (2) demonstra o log (da view LogCat) demonstrando que a Activity não foi recriada, mas o método onConfigurationChanged foi notificado.

(2) Tela em modo Landscape sem recriar a Activity após mudar orientação


Conclusão
Ao executar seu aplicativo agora, quando ocorrer mudanças de orientação de tela sua Activity não será recriada e o método onConfigurationChanged será automaticamente notificada a cada mudança de orientação. 

Como sugestão, caso queira fazer algo específico para cada modo de orientação, pode-se usar a lógica da condição implementada no método, que atualmente possui instruções com logs. Dependendo do cenário no qual seu aplicativo está sendo usado, essa dica pode ser útil para você.

Assim que possível vou fazer uma vídeo aula sobre isso também e colocar aqui. O código fonte do exemplo do projeto pode ser baixado aqui.



3 comentários:

Eric B. Braga disse...

Explicação perfeita. Esta questão de mudança de orientação é algo simples de ser tratado (como mostrado) e que da uma dor de cabeça quando o desenvolvedor não toma o devido cuidado com estas questões.

Nilson Silva disse...

otimo post douglas... tudo bem ? queria conversar com vc .... podemos falar no skype ? ou talvez no gmail mesmo
mastertouchpools eh meu username

Nilson Silva disse...

otimo post douglas... tudo bem ? queria conversar com vc .... podemos falar no skype ? ou talvez no gmail mesmo
mastertouchpools eh meu username