简体   繁体   中英

Android activity lifecycle issues when turning on landscape

I've got a problem with my code. I have a running timer on my screen with a start and a stop button. I click on start and wait for some seconds then when I turn the phone in landscape mode it restarts from zero. Can you help me somehow? I would like to have the timer continue running even if I turn the phone from portrait to landscape. Here you can find the code of the Activity:

public class TimerActivity extends Activity {

private static String CLASS_NAME; //variabile statica per aggiungere log 
private static long UPDATE_EVERY=200; //tempo ogni quanto lo schermo deve aggiornarsi 

protected TextView counter;
protected Button start;
protected Button stop;
protected boolean timerRunning; //x memorizzare lo stato del timer e se è partito o no. Dice se il bottone è stato attivano o no.
protected long startedAt; //queste mi servono per aggiornare il counter 
protected long lastStopped;
protected long lastSeconds; //questa variabile serve perchè il metodo run è chiamato molte volte al secondo e noi vogliamo che il dispositivo vibri una volta sola
protected Handler handler;
protected UpdateTimer updateTimer;
protected Vibrator vibrate; 


public TimerActivity(){  //aggiungo questo metodo 
    CLASS_NAME=getClass().getName(); //CLASS_NAME lo uso per raggruppare tutti i messaggi di log 

}
@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    //Per essere sicuri di non fare niente di strano!
  /*  if (BuildConfig.DEBUG) {
        StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder().detectAll().penaltyLog().build());
        StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder().detectAll().penaltyLog().penaltyDeath().build());
    }*/
    setContentView(R.layout.activity_timer);
    counter= (TextView) findViewById(R.id.timer); //setto queste variabili in modo che corrispondano alle view del layout
    start=(Button) findViewById(R.id.start_button);
    stop= (Button) findViewById(R.id.stop_button);

     vibrate = (Vibrator) getSystemService(VIBRATOR_SERVICE);

     if (vibrate == null) {
            Log.w(CLASS_NAME, "No vibration service exists.");
        }


}

public void clickedStart(View view) {
    Log.d(CLASS_NAME, "clicked start button");
    timerRunning=true;
    startedAt=System.currentTimeMillis(); //calcolo quanto tempo è passato da quando ho cliccato start a quando ho cliccato stop
    enableButtons();
    setTimeDisplay();
    handler= new Handler();
    updateTimer= new UpdateTimer(this);
    handler.postDelayed(updateTimer, UPDATE_EVERY); //questo chiama il metodo run della classe UpdateTimer ogni 200 millisecondi
}
public void clickedStop(View view) {
    Log.d(CLASS_NAME, "clicked stop button");
    timerRunning=false;
    lastStopped=System.currentTimeMillis(); 
    enableButtons();
    setTimeDisplay();
    handler.removeCallbacks(updateTimer);   //stoppo qualsiasi chiamata pendente al metodo run e setto l'handler a null
    //updateTimer = null;   //aggiunto
    handler=null;

}
@Override
public void onStart() { //chiamato quando l'activity viene vista per la prima volta 
    super.onStart();
    Log.d(CLASS_NAME, "onStart");

    vibrate= (Vibrator) getSystemService(VIBRATOR_SERVICE); //qui controllo se il device ha la funzionalità di vibrazione
    if (vibrate == null){
        Log.w(CLASS_NAME, "Vibration on this devise doesn't exist");
    }

    if(timerRunning){   //se il tempo sta scorrendo, quando l'activity viene ricreata chiamo di nuovo di metodo run
        handler=new Handler();
        updateTimer=new UpdateTimer(this);
        handler.postDelayed(updateTimer, UPDATE_EVERY);
    }
}
@Override
public void onPause() {     //chiamato quando un'altra activity si mette davanti
    super.onPause();
    Log.d(CLASS_NAME, "onPause");
}
@Override
public void onResume() {    //quando un'activity sta funzionando in foreground e un utente può interagire con essa
    super.onResume();
    Log.d(CLASS_NAME, "onResume");
    enableButtons();
    setTimeDisplay();
}
@Override
public void onStop() {  //quando un'activity è invisibile all'utente. Qui non devo continuare ad aggiornare il display
    super.onStop();
    Log.d(CLASS_NAME, "onStop");
    if(timerRunning){
        handler.removeCallbacks(updateTimer);
        updateTimer=null;
        handler=null;
    }
}
@Override
public void onDestroy() {   //quando un'activity è rimossa dal sistema e non si può più interagire
    super.onDestroy();
    Log.d(CLASS_NAME, "onDestroy");
}
@Override
public void onRestart() {   //quando un'activity è ripartita e torna in foreground
    super.onRestart();
    Log.d(CLASS_NAME, "onRestart");
}


@Override
public boolean onCreateOptionsMenu(Menu menu) {
    // Inflate the menu; this adds items to the action bar if it is present.
    Log.d(CLASS_NAME, "Showing menu."); 
    getMenuInflater().inflate(R.menu.settings, menu);
    return true;
}



protected void enableButtons(){ //questo metodo serve per attivare il button selezionato e disattivare l'altro
    Log.d(CLASS_NAME, "Set buttons enabled/disabled");
    start.setEnabled(!timerRunning);
    stop.setEnabled(timerRunning);
    setTimeDisplay();
}

protected void setTimeDisplay(){    //setta il text del counter con il tempo trascorso
    String display;
    long timeNow;
    long diff;
    long seconds;
    long minutes;
    long hours;

    //Log.d(CLASS_NAME, "Setting time display");

    if (timerRunning){  //se il timer sta andando avanti
        Log.d(CLASS_NAME, "timer is running");
        timeNow=System.currentTimeMillis(); //setto nella variabile timeNow il tempo corrente 
    }else{
        Log.d(CLASS_NAME, "setto last stopped");
        timeNow=lastStopped;    //altrimenti memorizzo nella variabile quello dell'ultima volta in cui ho stoppato
    }
    diff = timeNow - startedAt; //diff è il tempo trascorso e si calcola con il tempo attuale (o l'ultimo stoppato) meno il tempo a cui è iniziato

    if(diff < 0){   //il tempo non può essere negativo
        diff = 0;
    }

    seconds = diff / 1000;  //dato che diff è in millisecondi lo trasformo
    minutes = seconds / 60;
    hours = minutes / 60;
    seconds = seconds % 60;
    minutes = minutes % 60;

    display = String.format("%d", hours) + ":" + String.format("%02d", minutes) + ":" + String.format("%02d", seconds);

     Log.i(CLASS_NAME, "Time is " + display);
    counter.setText(display);

}

public void clickedSettings (View view){
    Log.d(CLASS_NAME, "clickedSettings");
    Intent settingsIntent= new Intent (getApplicationContext(), SettingsActivity.class);
    startActivity(settingsIntent);
}

protected void vibrateCheck(){  // metodo per dire al device di vibrare 
    long timeNow = System.currentTimeMillis();
    long diff = timeNow - startedAt;
    long seconds = diff/1000;
    long minutes = seconds/1000;

    Log.d(CLASS_NAME, "vibrate check");

    seconds = seconds % 60;
    minutes = minutes % 60;

    if(vibrate != null && seconds == 0 && seconds != lastSeconds){  //seconds!=lastSeconds fa si che non vibri più di una volta al secondo dato che questo metodo può essere chiamato più volte nello stesso secondo.per vibrare gli passo degli array di numeri
        long[] once = {0,500};      //questi numeri rappresentano un pattern di vibrazione: il primo è il numero di millisecondi da aspettare prima di iniziare a vibrare e il secondo indica per quanto tempo deve vibrare
        long[] twice = {0,100,400,100}; // il terzo parametro indica quanto tempo deve aspettare prima di vibrare di nuovo
        long[] thrice = {0,100,400,100,400,100};

        if(minutes == 0){   //ogni ora
            Log.i(CLASS_NAME, "Vibrate 3 times");
            vibrate.vibrate(thrice, -1);
        }

        if(minutes % 15 == 0){  //ogni 15 minuti
            Log.i(CLASS_NAME, "Vibrate 2 times");
            vibrate.vibrate(twice, -1);
        }

        if(minutes % 1 == 0){   //ogni 1 minuti
            Log.i(CLASS_NAME, "Vibrate once");
            vibrate.vibrate(once, -1);
        }

    }
    lastSeconds=seconds; 
}

class UpdateTimer implements Runnable { //creo questa classe per rendere visibile il timer che va avanti. Aggiorno il display ogni 200 millisecondi

     Activity activity;

        public UpdateTimer(Activity activity) {
            this.activity = activity;
        }
    @Override
    public void run() { //questo metodo definito dalla classe Runnable è chiamato solo una volta quando il nuovo thread viene creato
        //Log.d(CLASS_NAME, "run");
        Settings settings = ((BikeNavigator) getApplication()).getSettings();
        setTimeDisplay();

        if (timerRunning && settings.isVibrateOn(activity)) {   //se il tempo sta scorrendo, chiamo il metodo che fa vibrare il dispositivo
            vibrateCheck();
        }

        if (handler!= null){    //controllare che sia diverso da null serve per verificare che l'handler esista e che start sia stato cliccato
            handler.postDelayed(this,UPDATE_EVERY);
        }

    }
}

}

You have to add this to the activity declaration in the manifest:

android:configChanges="orientation|screenSize"

so it looks like

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

The matter is that the system destroys the activity when a change in the configuration occurs. See ConfigurationChanges.

The current proper way to save state during a runtime configuration change is to use retained data fragments .

In this case, you would store the timerRunning , startedAt , lastStopped , and lastSeconds . Restore them onCreate , and set the counter text based on them.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM