Android应用生命周期实现简单的秒表App

1、功能分析

1.1、秒表功能界面

1.2、App结构

  • 1个Activity :MainActivity
  • 1个Layout :activity_main.xml

2、开发视图布局

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实现

3.1、MainActivity类

public class MainActivity extends AppCompatActivity {

    //计时的秒数
    private  int seconds = 0;
    //计时的状态
    private  boolean running = false;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //在Activity启动时,onCreate()方法中启动runTimer()
        runTimer();
    }

    //启动计时
    public void onClickStart(View view){
        running = true;
    }
    //停止计时
    public void onClickStop(View view){
        running = false;
    }
    //重置秒表
    public void onClickReset(View view){
        running = false;
        seconds = 0;
    }
  	//循环计时方法
    private void runTimer(){
        final TextView timeView = findViewById(R.id.time_view);
        //创建UI线程的handler,用于消息处理
        final Handler handler = new Handler();
        handler.post(new Runnable() {//立即交一个Runnable,任务在Runnable的run()方法中
            @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++;
                }            
                //每隔1000ms,重复提交该任务
                handler.postDelayed(this,1000);
            }
        });
    }
}

4、生命周期的应用

4.1、问题分析

  • 问题一:旋转屏幕,Android检测到屏幕方向变化,计时会重置
  • 问题二:App被切换至后台,秒表不能暂停

4.2、Activity运行过程

4.2、屏幕旋转,计时不重置

  • 设备配置变化时,如屏幕旋转,需保存状态,重启时恢复

  • 在运行后(running),销毁(onDestroy())前会调用 onSaveInstanceState(),保存状态到Bundle

    • 保存状态需要覆盖onSaveInstanceState()方法

    • Bundle可存储键值对

      bundle.put*(“name”,value)

    • 在Bundle中存储running和seconds

      @Override
      public void onSaveInstanceState(Bundle savedInstanceState){
          super.onSaveInstanceState(savedInstanceState);
          
          savedInstanceState.putInt("seconds",seconds);
          savedInstanceState.putBoolean("running",running);
      }
      
  • 在onCreate()方法中恢复,Bundle是其参数,进程第一次新建Activity时为null,
    之后为onSaveInstanceState()保存的Bundle

    • 从Bundle取出键值对

      bundle.get*(“name”);

    • 在Bundle中取出running和seconds

      @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();
      }
      
  • 程序运行流程

    • 用户启动App,点击start按钮,开始计时
      runTimer()方法开始递增seconds,并显示到文本框time_view中
    • 旋转手机,Android检测到屏幕方向变化,销毁原Activity前,调用onSaveInstanceState()保存实例变量
    • Android销毁Activity,再次新建该Activity再次调用onCreate()方法,将保存的Bundle作为参数传入
    • onCreate()方法中,取出Bundle存储的值并恢复到销毁前的状态
    • runTimer()方法从旋转前的seconds继续计时

4.3、App被切换至后台,秒表可以暂停

  • 解决方法

    • 覆盖onStop(),在消失前停止计时

      @Override
      protected void onStop(){
          super.onStop();
          Log.d("life cycle","onStop");
          wasRunning = running; // 记录之前是否运行,
          running = false; //将running设置为false以停止计时
      }
      
    • 覆盖生命周期方法前,必须先调用父类的生命周期

      super.onStop();

    • 覆盖onStart(),在可见前继续计时

      @Override
      //如果之前running==true,则将running设置为true,继续计时
      protected void onStart(){
          super.onStart();
          Log.d("life cycle","onStop");
          if(wasRunning){ 
              running = true;
          }
      }
      
    • App被切换至后台,Activity对象仍存在,可以使用实例变量存储状态

      //计时的秒数
      private  int seconds = 0;
      //计时的状态
      private  boolean running = false;
      //新的变量,用于在onStop()中保存消失前running的状态
      private boolean wasRunning = false;
      
      @Override
      protected void onStop(){
          super.onStop();
          Log.d("life cycle","onStop"); //日志并暂停
          wasRunning = running;
          running = false;
      }
      
      @Override
      protected void onStart(){
          super.onStart();
          Log.d("life cycle","onStop"); //日志并恢复
          if(wasRunning){
              running = true;
          }
      }
      
    • Activity销毁前在Bundle保存wasRunning,
      Activity重新实例化后从Bundle恢复wasRunning

      @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);
      }
      
  • 运行流程

    • 用户启动App,点击Start按钮,runTimer()开始递增seconds并更新文本框
    • 用户点击Home键,Activity消失,Android调用onStop()
    • 用户返回秒表App,Activity可见,Android调用onStart()

5、MainActivity完整代码

public class MainActivity extends AppCompatActivity {

    //计时的秒数
    private  int seconds = 0;
    //计时的状态
    private  boolean running = false;
    //新的变量,用于在onStop()中保存消失前running的状态
    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");
        }
        //在Activity启动时,onCreate()方法中启动runTimer()
        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");
    }

    //启动计时
    public void onClickStart(View view){
        running = true;
    }
    //停止计时
    public void onClickStop(View view){
        running = false;
    }
    //重置秒表
    public void onClickReset(View view){
        running = false;
        seconds = 0;
        wasTiming = false;
    }
    //循环计时方法
    private void runTimer(){
        final TextView timeView = findViewById(R.id.time_view);
        //创建UI线程的handler,用于消息处理
        final Handler handler = new Handler();
        handler.post(new Runnable() {//立即交一个Runnable,任务在Runnable的run()方法中
            @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++;
                }
                //每隔1000ms,重复提交该任务
                handler.postDelayed(this,1000);
            }
        });
    }
}

本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
THE END
分享
二维码
< <上一篇
下一篇>>