Android Innerclass Referencia de TextView cuando se reanuda la actividad

Tengo una clase interna que extiende CountDownTimer. Básicamente es un temporizador de cuenta atrás simple que actualiza un TextView en la actividad y reproduce un sonido cuando el temporizador está terminado. El código para la clase interna es:

public class SetTimer extends CountDownTimer { public SetTimer(long millisInFuture, long countDownInterval) { super(millisInFuture, countDownInterval); } @Override public void onFinish() { timeLeft.setText("0"); Uri notification = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION); Ringtone r=RingtoneManager.getRingtone(getApplicationContext(), notification); r.play(); } @Override public void onTick(long millisUntilFinished) { String t; t=String.format("%02d:%02d", TimeUnit.MILLISECONDS.toMinutes(millisUntilFinished), TimeUnit.MILLISECONDS.toSeconds(millisUntilFinished) -TimeUnit.MINUTES.toSeconds(TimeUnit.MILLISECONDS.toMinutes(millisUntilFinished))); timeLeft.setText(t); } } 

El código que crea y hace referencia al TextView es:

 TextView timeLeft; 

Y en el método onCreate:

 timeLeft=(TextView) findViewById(R.id.txtTimeLeft); 

Esto funciona bien hasta que gire la pantalla. En ese momento, el temporizador sigue funcionando y reproduce el sonido al final pero no actualiza el TextView. El TextView se declara en la parte superior de la clase y se hace referencia en el método onCreate de la actividad. Si reinicio el temporizador entonces funciona. Utilicé Log.d para comprobar si el método onTick todavía era llamado y lo era. Mi conjetura es que la referencia al TextView ha cambiado pero no puedo figurar hacia fuera cómo fijarlo de nuevo al contador de tiempo. Traté de declarar un TextView en el método onTick y actualizar que figurando que, a continuación, recoger una referencia a la instancia actual de la TextView, pero que también no funcionó. La única otra cosa a tener en cuenta es que el objeto SetTimer se crea cuando el usuario hace clic en un botón. Ese código es:

 timer=new SetTimer(interval, 100); timer.start(); 

¿Alguna idea sobre cómo hacer que el SetTimer siga actualizando el TextView después de que la pantalla se rota?

Actualización He reescrito completamente la respuesta ya que no noté una cosa terrible en tu código a primera vista.

Es posible que pierda recursos y el hecho de que su aplicación no se bloquee – podría ser sólo una cuestión de tiempo. En primer lugar, su clase interna de SetTimer implica implícitamente una referencia a la actividad (supongo, usted declaró esta clase en la actividad, ¿no?). Eso evita que su actividad sea ​​recolectada de basura, y supongo que es por eso que no obtiene una Excepción al establecer un valor para un TextView que está "fuera de la vista".

De modo que debe declarar su clase como una clase privada estática , una clase estática (clases internas) o una clase pública (en su propio archivo). De esta manera, no tendrá una referencia implícita a su actividad y no causará una pérdida de memoria cuando se destruye.

Pero ahora no podrá acceder a una vista de texto directamente, ya que es un miembro de su clase de actividad . Vamos a resolver de esa manera:

  1. Declare una interfaz dentro de SetTimer:

     interface OnTickUpdateListener{ public void onTickUpdate(String text); } 
  2. Declare una instancia de dicha interfaz en su SetTimer y modifique el constructor:

     public class SetTimer extends ... {//this class IS IN IT's OWN FILE!!!! private OnTickUpdateListener listener; public void registerListener(OnTickUpdateListener listener){ this.listener = listener; } public void unregisterListener(){ this.listener = null; } ... } 
  3. Vamos a activar el oyente cuando el tiempo marca:

     @Override public void onTick(long millisec){ if(listener != null){ String t; t = String.valueOf(millisec);//or whatever listener.onTickUpdate(t); } } 
  4. Ahora, haga que su actividad implemente su interfaz:

     public class MyActivity extends Activity implements SetTimer.OnTickUpdateListener{ @Override public void onTickUpdate(String text){ textView.setText(text); } 

Ahora a la parte más difícil. Tenemos que guardar una instancia de SetTimer cuando se destruye la actividad. Eso sería un buen truco para poner un SetTimer dentro de un Fragmento retenido invisible para el usuario, que funcionaría como un "recipiente inmortal" 🙂

  1. Crear una clase de fragmento:

     public class MyFragment extends Fragment{ public static final String TAG = MyFragment.class.getSimpleName(); private SetTimer timer; private static final int interval = 10; private MyActivity myActivity; @Override public void onAttach(Activity activity){ super.onAttach(activity); this.myActivity = (MyActivity) activity; } @Override public void onCreate(Bundle savedInstanceState){ super.onCreate(savedInstanceState); setRetainInstanceState(true); timer = new SetTimer(interval, 10); timer.start(); } @Override public void onActivityCreated(Bundle state){ super.onActivityCreated(state); timer.registerListener(myActivity);//activity should receive ticks } @Override public void onDetach(){ super.onDetach(); timer.unregisterListener();//ensure we do not post a result to non-existing Activity } } 
  2. Y por último, agregue su MyFragment en el método onCreate de MyActivity:

     public class MyActivity extends Activity implements SetTimer.OnTickUpdateListener{ private MyFragment fragment; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); FragmentManager fm = getFragmentManager(); fragment = (MyFragment) fm.findFragmentByTag(MyFragment.TAG); if(fragment == null){ fragment = new MyFragment(); fm.beginTransaction().add(R.id.container, fragment, MyFragment.TAG).commit(); } } 

De esta manera, restauramos el fragmento existente en Recreación de actividad, y MyFragment registra MyActivity como un oyente, que recibirá actualizaciones de tick.

PS: Escribí esto desde cero y no lo probé, así que si encuentras algún error, publícalo para que podamos resolverlo.

Como su referencia a la vista de texto se está cambiando, estoy adivinando que ha agregado

android:configChanges="keyboardHidden|orientation|screenSize"

En su AndroidManifest para que la actividad no se reinicie en la rotación.

Aquí está mi código de MainActivity que debería funcionar para usted

 public class MainActivity extends Activity { TextView timeLeft; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); timeLeft = (TextView)findViewById(R.id.textview); SetTimer st=new SetTimer(10000, 1000); st.start(); } @Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu; this adds items to the action bar if it is present. getMenuInflater().inflate(R.menu.main, menu); return true; } @Override public void onConfigurationChanged(Configuration newConfig) { // TODO Auto-generated method stub super.onConfigurationChanged(newConfig); setContentView(R.layout.activity_main); timeLeft = (TextView)findViewById(R.id.textview); } public class SetTimer extends CountDownTimer { public SetTimer(long millisInFuture, long countDownInterval) { super(millisInFuture, countDownInterval); } @Override public void onFinish() { timeLeft.setText("0"); Uri notification = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION); Ringtone r=RingtoneManager.getRingtone(getApplicationContext(), notification); r.play(); } @Override public void onTick(long millisUntilFinished) { String t; t=String.format("%02d:%02d", TimeUnit.MILLISECONDS.toMinutes(millisUntilFinished), TimeUnit.MILLISECONDS.toSeconds(millisUntilFinished) -TimeUnit.MINUTES.toSeconds(TimeUnit.MILLISECONDS.toMinutes(millisUntilFinished))); timeLeft.setText(t); } } 

}

FlipAndroid es un fan de Google para Android, Todo sobre Android Phones, Android Wear, Android Dev y Aplicaciones para Android Aplicaciones.