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() 方法期間保存此類數據。

下一篇我們對該程序的界面進行升級。

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