Android基础教程3

系统中的活动是被返回栈管理的。 启动新活动时,通常将其放置在当前堆栈的顶部并成为正在运行的活动,先前的活动始终在堆栈中保持在其下方,并且在新活动退出之前不会再次成为前台。 屏幕上可以看到一个或多个活动堆栈。

在这些活动,或者简单点来说这些页面不停的切换之间,它们的状态发生了一次又一次的改变,这些状态的改变我们就可以看作是Activity的生命周期变化。

这里是引用
研究生命周期变化的常见方法是通过log来进行观察,这也是我们能查到的资料绝大部分都是用打印log的方法演示其生命周期的转变。我们也这么做,毕竟我也不会其他办法。

基于课程2的代码,我们为MainActivity添加上图所示的重载方法。

在MainActivity内部,使用ctrl+o的快捷方式:
在这里插入图片描述
在上面的界面选择生命周期图中出现的方法,当然了,小伙伴们onCreate方法,我们就不用在添加了。

 /**
     * Activity被创建时该回调方法执行,一般用来做初始化操作
     *
     * @param savedInstanceState
     */
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    }
  /**
     * Activity正在重新启动,当Activity由不可见变为可见状态时,该回调方法执行
     * 
     * 一般在该Activity处于可见状态,也就是当前显示在屏幕上时,切换应用后,再次
     * 打开该应用的时候会执行该回调方法
     */
    @Override
    protected void onRestart() {
        super.onRestart();
    }

    /**
     * Activity正在启动,此时Activity已处于可见状态,
     * 此时,Activity并未在屏幕显示,所以无法和用户进行交互
     */
    @Override
    protected void onStart() {
        super.onStart();
    }

    /**
     * Activity已在前台可见,可与用户交互
     */
    @Override
    protected void onResume() {
        super.onResume();
    }

    /**
     * Activity正在停止(Paused形态)
     */
    @Override
    protected void onPause() {
        super.onPause();
    }

    /**
     * Activity即将停止或者完全被覆盖(Stopped形态),此时Activity不可见,仅在后台运行
     */
    @Override
    protected void onStop() {
        super.onStop();
    }

    /**
     * Activity正在被销毁,一般在这个回调方法中释放资源
     */
    @Override
    protected void onDestroy() {
        super.onDestroy();
    }

接下来我们就需要使用Logcat来跟踪理解Activity的生命周期变化了。

在Logcat中输出我们想要的日志内容需要借助Log类。

android.util.Log

通过 Log 类,我们可以创建日志消息,这些消息会显示在 logcat 中。哦我们呢可以使用以下日志方法:

Log.e(String, String)(错误)
Log.w(String, String)(警告)
Log.i(String, String)(信息)
Log.d(String, String)(调试)
Log.v(String, String)(详细)

除开发期间外,其他任何时候都绝不应将详细日志编译到应用中。虽然会编译调试日志,但会在运行时将其去掉,而错误、警告和信息日志会始终保留。

对于每种日志方法,第一个参数都应是唯一标记,第二个参数都应是消息。系统日志消息的标记是一个简短的字符串,指示消息所源自的系统组件(例如 ActivityManager)。标记可以是您认为有用的任何字符串,例如当前类的名称。

最常见的方法就是在我们的Activity中定义如下格式的静态变量:

private static final String TAG = "MainActivity";

好像还有一种更方便的写法,我忘记了,不知道是不是下面的样子:

private static final String TAG = MainActivity.class.getSimpleName();

其中TAG的值为当前Activity的名称。

我们在代码中使用:

@Override
    protected void onDestroy() {
        super.onDestroy();
        Log.d(TAG, "onDestroy: ");
    }

其他的方法同样添加对应的log语句。

Logcat使用:
在这里插入图片描述

这里我们简单看下常见的几种场景中生命周期的变化:

启动应用

logcat输出:
在这里插入图片描述

用户按下BACK键

在这里插入图片描述

用户按下BACK键后,再次返回应用

在这里插入图片描述

用户按下HOME键

在这里插入图片描述

用户按下HOME键后,再次返回应用

在这里插入图片描述

打开其他应用

在这里插入图片描述

另外我们需要主要一点,当用户旋转屏幕的时候,系统会销毁MainActivity实例,然后重新创建一个实例。

那么我们接下来看看怎么在我们的程序中怎么适配横屏。

新建资源目录layout-land:

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
在Project视图下我们会看到该目录已经被创建
在这里插入图片描述
那么layout-land目录有什么用呢?
在这里插入图片描述

这里的-land后缀名是配置修饰符的另一个使用例子。 Android依靠res子目录的配置修饰符定位最佳资源以匹配当前设备配置。

我们可以将layout-land理解为我们应用程序的备用资源,如果我们没有提供这两个目录,那么系统会使用默认的layout目录下的布局文件。

也就说当我们创建了layout-land目录,并且在下面创建了布局文件,那么系统在横屏模式下回优先从这里查找布局文件。

注意:两个布局文件的文件名必须相同,这样它们才能以同一个资源ID被引用

我们可以将layout目录下的activity_main.xml直接拷贝到layout-land中,然后修改下布局文件:

<?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">

    <TextView
        android:id="@+id/tv_question"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="20dp"
        android:textSize="20sp"
        android:textStyle="bold"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.1"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:id="@+id/tv_answerA"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="15dp"
        android:textSize="18sp"
        app:layout_constraintStart_toStartOf="@id/tv_question"
        app:layout_constraintTop_toBottomOf="@id/tv_question" />

    <TextView
        android:id="@+id/tv_answerB"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginLeft="50dp"
        android:textSize="18sp"
        app:layout_constraintStart_toEndOf="@id/tv_answerA"
        app:layout_constraintTop_toTopOf="@id/tv_answerA" />

    <TextView
        android:id="@+id/tv_answerC"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="15dp"
        android:textSize="18sp"
        app:layout_constraintStart_toStartOf="@id/tv_question"
        app:layout_constraintTop_toBottomOf="@id/tv_answerA" />


    <TextView
        android:id="@+id/tv_answerD"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginLeft="50dp"
        android:textSize="18sp"
        app:layout_constraintStart_toEndOf="@id/tv_answerC"
        app:layout_constraintTop_toTopOf="@id/tv_answerC" />
    
    <TextView
        android:id="@+id/tv_confirm_title"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="25dp"
        android:text="@string/question_confirm_title"
        android:textSize="18sp"
        app:layout_constraintStart_toStartOf="@id/tv_question"
        app:layout_constraintTop_toBottomOf="@id/tv_answerD" />

    <EditText
        android:id="@+id/et_confirm_answer"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:hint="@string/question_select"
        android:maxLength="4"
        android:singleLine="true"
        android:text=""
        android:textSize="18sp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toEndOf="@id/tv_confirm_title"
        app:layout_constraintTop_toTopOf="@id/tv_confirm_title" />

    <Button
        android:id="@+id/bt_previous"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="15dp"
        android:text="@string/question_previous"
        app:layout_constraintEnd_toStartOf="@id/bt_confirm"
        app:layout_constraintHorizontal_chainStyle="spread"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@id/et_confirm_answer" />

    <Button
        android:id="@+id/bt_confirm"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/question_ok"
        app:layout_constraintEnd_toStartOf="@id/bt_next"
        app:layout_constraintStart_toEndOf="@id/bt_previous"
        app:layout_constraintTop_toTopOf="@id/bt_previous" />

    <Button
        android:id="@+id/bt_next"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/question_next"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toEndOf="@id/bt_confirm"
        app:layout_constraintTop_toTopOf="@id/bt_previous" />


</androidx.constraintlayout.widget.ConstraintLayout>

接下来就是验证下是否按照我们的预期正确显示,不过在模拟器上实现横屏好像比较麻烦,我这里在我的手机上试试。

确保你的手机的usb调试模式已经开启,使用数据线连接,设置成功后,Andorid Studio能够检测到活动的设备。

在这里插入图片描述
运行程序,没问题。。
在这里插入图片描述
写在测试之后,记得把自动旋转打开,我这看了半天没反应结果却是这个问题。。。。

上面提到过我们在横屏和竖屏之间切换的时候,系统会销毁原来的Activity重新创建Activity的实例,也就意味着我们的应用在切换后会返回到第一个页面,而且如果用户有什么输入之类的,这些数据也不会被系统保存。那么有什么办法能够在Activity销毁之前保存我们需要的数据呢?

onSaveInstanceState() 保存简单轻量的界面状态

Activity 开始停止时,系统会调用 onSaveInstanceState() 方法,以便 Activity 可以将状态信息保存到实例状态 Bundle 中。此方法的默认实现保存有关 Activity 视图层次结构状态的瞬态信息。
要保存 Activity 的其他实例状态信息,需要重写 onSaveInstanceState()方法,并将键值对形式的数据添加到Activity 意外销毁时所保存的 Bundle 对象中。重写onSaveInstanceState() 时,如果要默认实现保存视图层次结构的状态,则必须调用父类实现。

对于我们的程序来说,我们需要保存:mCurrentQuestion即试题索引

首先重写该方法:

	/**
     * 保存用户输入以及试题索引
     *
     * @param outState
     */
    @Override
    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        outState.putInt(QUESTION_INDEX, mCurrentQuestion);
    }

常量定义如下:

	private static final String QUESTION_INDEX = "QUESTION_INDEX";

无论系统是新建 Activity 实例还是重新创建之前的实例,都会调用 onCreate() 方法,所以在尝试读取之前,您必须检查状态 Bundle 是否为 null。如果为 null,系统将新建 Activity 实例,而不会恢复之前销毁的实例。

那么我们需要做的就是在onCreate() 方法中恢复这些数据:

 @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // 恢复数据
        if(savedInstanceState != null){
            mCurrentQuestion = savedInstanceState.getInt(QUESTION_INDEX,0);
        }
        ........

解释下:int getInt (String key, int defaultValue),第一个参数为key,第二个参数意味着没有对应的key的值时候返回的默认值。

那么接下来就该验证下是否能够正确恢复数据了:
![在这里插入图片描述](https://img-blog.csdnimg.cn/2019111311561634.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,sh
在这里插入图片描述
当用户显式关闭 Activity 时,或者在其他情况下调用 finish() 时,系统不会调用 onSaveInstanceState()。

如要保存持久化数据(例如用户首选项或数据库中的数据),您应在 Activity 位于前台时抓住合适机会。如果没有这样的时机,应在执行 onStop() 方法期间保存此类数据。

下一篇我们对该程序的界面进行升级。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章