1. Functional analysis
1.1 stopwatch function interface
1.2 App structure
- 1 Activity: MainActivity
- 1 Layout: activity_main.xml
2. Development view layout
2.1,activity_main.xml
<?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity"> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:gravity="center_horizontal" android:orientation="vertical"> <TextView android:id="@+id/time_view" android:layout_width="match_parent" android:layout_height="wrap_content" android:gravity="center_horizontal" android:text="0:00:00" android:textAppearance="@style/TextAppearance.AppCompat.Large" android:textSize="80sp"/> <Button android:id="@+id/button_start" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_horizontal" android:text="@string/start" android:onClick="onClickStart"/> <Button android:id="@+id/button_stop" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_horizontal" android:text="@string/stop" android:onClick="onClickStop"/> <Button android:id="@+id/button_reset" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_horizontal" android:text="@string/reset" android:onClick="onClickReset"/> </LinearLayout> </androidx.constraintlayout.widget.ConstraintLayout>
2.2,string.xml
<resources> <string name="app_name">Stopwatch</string> <string name="start">start</string> <string name="stop"> Stop</string> <string name="reset">Reset</string> <string name="time">Time</string> </resources>
3. Activity implementation
3.1. MainActivity class
public class MainActivity extends AppCompatActivity { //Seconds timed private int seconds = 0; //Status of timing private boolean running = false; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); //When the Activity starts, runTimer() is started in the onCreate() method runTimer(); } //Start timing public void onClickStart(View view){ running = true; } //Stop timing public void onClickStop(View view){ running = false; } //Reset stopwatch public void onClickReset(View view){ running = false; seconds = 0; } //Cycle timing method private void runTimer(){ final TextView timeView = findViewById(R.id.time_view); //Create handler of UI thread for message processing final Handler handler = new Handler(); handler.post(new Runnable() {//Hand in a Runnable immediately, and the task is in the run() method of Runnable @Override public void run() { int hours = seconds/3600; int minutes = (seconds%3600)/60; int secs = seconds%60; String time = String.format("%d:%02d:%02d", hours, minutes, secs); timeView.setText(time); if(running){ seconds++; } //Submit the task repeatedly every 1000ms handler.postDelayed(this,1000); } }); } }
4. Application of life cycle
4.1 problem analysis
- Question 1: when the screen is rotated, Android detects a change in the screen direction, and the timing will be reset
- Problem 2: the App is switched to the background, and the stopwatch cannot pause
4.2. Activity running process
4.2. The screen rotates and the timing does not reset
-
When the device configuration changes, such as the screen rotates, the state needs to be saved and restored when restarting
-
After running and before destroying (onDestroy()), onSaveInstanceState() will be called to save the state to the Bundle
-
The onSaveInstanceState() method needs to be overridden to save the state
-
Bundle stores key value pairs
bundle.put*("name",value)
-
Store running and seconds in the Bundle
@Override public void onSaveInstanceState(Bundle savedInstanceState){ super.onSaveInstanceState(savedInstanceState); savedInstanceState.putInt("seconds",seconds); savedInstanceState.putBoolean("running",running); }
-
-
Recover in onCreate() method, with Bundle as its parameter, and null when the process creates an Activity for the first time,
The Bundle saved later for onSaveInstanceState()-
Take the key value pair from the Bundle
bundle.get*("name");
-
Take out running and seconds from the Bundle
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Log.d("life cycle","onCreate"); setContentView(R.layout.activity_main); if(savedInstanceState!=null){ seconds = savedInstanceState.getInt("seconds"); running = savedInstanceState.getBoolean("running"); } runTimer(); }
-
-
Program running process
- The user starts the App and clicks the start button to start timing
The runTimer() method starts incrementing seconds and displays it in the text box time_ In view - Rotate the mobile phone, Android detects the change of the screen direction, and destroys the original Activity, then calls onSaveInstanceState() to save the instance variables.
- Android destroys the Activity, creates the Activity again, calls onCreate() method again, and passes in the saved Bundle as a parameter
- In the onCreate() method, retrieve the value stored in the Bundle and restore it to the state before destruction
- The runTimer() method continues timing from the seconds before rotation
- The user starts the App and clicks the start button to start timing
4.3. When the App is switched to the background, the stopwatch can pause
-
resolvent
-
Override onStop() and stop timing before disappearing
@Override protected void onStop(){ super.onStop(); Log.d("life cycle","onStop"); wasRunning = running; // Whether it is running before recording, running = false; //Set running to false to stop timing }
-
The lifecycle of the parent class must be called before overriding the lifecycle method
super.onStop();
-
Override onStart() to continue timing until it is visible
@Override //If running==true before, set running to true to continue timing protected void onStart(){ super.onStart(); Log.d("life cycle","onStop"); if(wasRunning){ running = true; } }
-
The App is switched to the background, and the Activity object still exists. You can use the instance variable to store the state
//Seconds timed private int seconds = 0; //Status of timing private boolean running = false; //A new variable used to save the running state before disappearing in onStop() private boolean wasRunning = false; @Override protected void onStop(){ super.onStop(); Log.d("life cycle","onStop"); //Log and pause wasRunning = running; running = false; } @Override protected void onStart(){ super.onStart(); Log.d("life cycle","onStop"); //Log and recover if(wasRunning){ running = true; } }
-
Save wasRunning in the Bundle before destroying the Activity,
Recover wasRunning from Bundle after re instantiation of Activity@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Log.d("life cycle","onCreate"); setContentView(R.layout.activity_main); if(savedInstanceState!=null){ seconds = savedInstanceState.getInt("seconds"); running = savedInstanceState.getBoolean("running"); wasRunning = savedInstanceState.getBoolean("wasRunning"); } runTimer(); } @Override public void onSaveInstanceState(Bundle savedInstanceState){ super.onSaveInstanceState(savedInstanceState); savedInstanceState.putInt("seconds",seconds); savedInstanceState.putBoolean("running",running); savedInstanceState.putBoolean("wasRunning",wasRunning); }
-
-
Operation process
- The user starts the App, clicks the Start button, and runTimer() starts to increment seconds and update the text box
- The user clicks the Home key, the Activity disappears, and Android calls onStop()
- The user returns the stopwatch App, the Activity is visible, and Android calls onStart()
5. MainActivity complete code
public class MainActivity extends AppCompatActivity { //Seconds timed private int seconds = 0; //Status of timing private boolean running = false; //A new variable used to save the running state before disappearing in onStop() private boolean wasRunning = false; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Log.d("life cycle","onCreate"); setContentView(R.layout.activity_main); if(savedInstanceState!=null){ seconds = savedInstanceState.getInt("seconds"); running = savedInstanceState.getBoolean("running"); wasRunning = savedInstanceState.getBoolean("wasRunning"); } //When the Activity starts, runTimer() is started in the onCreate() method runTimer(); } @Override protected void onStart(){ super.onStart(); Log.d("life cycle","onStop"); if(wasRunning){ running = true; } } @Override protected void onStop(){ super.onStop(); Log.d("life cycle","onStop"); wasRunning = running; running = false; } @Override public void onSaveInstanceState(Bundle savedInstanceState){ super.onSaveInstanceState(savedInstanceState); savedInstanceState.putInt("seconds",seconds); savedInstanceState.putBoolean("running",running); savedInstanceState.putBoolean("wasRunning",wasRunning); } protected void onDestroy(){ super.onDestroy(); Log.d("life cycle","onDestroy"); } //Start timing public void onClickStart(View view){ running = true; } //Stop timing public void onClickStop(View view){ running = false; } //Reset stopwatch public void onClickReset(View view){ running = false; seconds = 0; wasTiming = false; } //Cycle timing method private void runTimer(){ final TextView timeView = findViewById(R.id.time_view); //Create handler of UI thread for message processing final Handler handler = new Handler(); handler.post(new Runnable() {//Hand in a Runnable immediately, and the task is in the run() method of Runnable @Override public void run() { int hours = seconds/3600; int minutes = (seconds%3600)/60; int secs = seconds%60; String time = String.format("%d:%02d:%02d", hours, minutes, secs); timeView.setText(time); if(running){ seconds++; } //Submit the task repeatedly every 1000ms handler.postDelayed(this,1000); } }); } }