[Android]Activity的生命週期

weixin_34037977發表於2017-08-15

想要學好安卓開發,就必須理解安卓軟體的生命週期。明確一個活動的建立、啟動、停止、暫停、重新啟動和銷燬的過程,知道各個階段會呼叫什麼函式進行處理不同的情況。這裡我們就來說說Activity的生命週期。

1. 活動棧

Android 中的活動是層疊的,我們每啟動一個新的活動,就會覆蓋在原活動之上。然後點選 Back 鍵會銷燬最上面的活動,以下的一個活動就會又一次顯示出來。

事實上 Android 是使用任務來管理活動的。一個任務就是一組存放在棧裡的活動的集合。這個棧也被稱作活動棧 。

棧是一種後進先出的資料結構。在預設情況下。每當我們啟動了一個新的活動。它會在返回棧中入棧,並處於棧頂的位置。而每當我們按下 Back 鍵或呼叫 finish()方法去銷燬一個活動時,處於棧頂的活動會出棧。這時前一個入棧的活動就會又一次處於棧頂的位置。

系統總是會顯示處於棧頂的活動給使用者。

2. 活動的狀態

每一個活動在其生命週期中最多可能會有四種狀態。

  1. 執行狀態
    當一個活動位於活動棧的棧頂時。這時活動就處於執行狀態。系統最不願意回收的就是處於執行狀態的活動,由於這會帶來非常差的使用者體驗。

  2. 暫停狀態
    當一個活動不再處於棧頂位置,但仍然可見時,這時活動就進入了暫停狀態。

    你可能會認為既然活動已經不在棧頂了,還怎麼會可見呢?這是由於並非每一個活動都會佔滿整個螢幕的。比方對話方塊形式的活動僅僅會佔用螢幕中間的部分割槽域,你非常快就會在後面看到這樣的活動。處於暫停狀態的活動仍然是全然存活著的。系統也不願意去回收這樣的活動(由於它還是可見的。回收可見的東西都會在使用者體驗方面有不好的影響) 。僅僅有在記憶體極低的情況下。系統才會去考慮回收這樣的活動。

  3. 停止狀態
    當一個活動不再處於棧頂位置。而且全然不可見的時候,就進入了停止狀態。

    系統仍然會為這樣的活動儲存對應的狀態和成員變數。可是這並非全然可靠的,當其它地方須要記憶體時,處於停止狀態的活動有可能會被系統回收。

  4. 銷燬狀態
    當一個活動從活動棧中移除後就變成了銷燬狀態。系統會最傾向於回收處於這樣的狀態的活動,從而保證手機的記憶體充足。

3. 活動的生存期

Activity 類中定義了七個回撥方法。覆蓋了活動生命週期的每一個環節,以下我來一一介紹下這七個方法。
1. onCreate()
這種方法你已經看到過非常多次了,每一個活動中我們都重寫了這種方法,它會在活動第一次被建立的時候呼叫。

你應該在這種方法中完畢活動的初始化操作,比方說載入佈局、繫結事件等。


2. onStart()
這種方法在活動由不可見變為可見的時候呼叫。
3. onResume()
這種方法在活動準備好和使用者進行互動的時候呼叫。此時的活動一定位於活動棧的棧頂,而且處於執行狀態。
4. onPause()
這種方法在系統準備去啟動或者恢復還有一個活動的時候呼叫。我們一般會在這種方法中將一些消耗 CPU 的資源釋放掉,以及儲存一些重要資料,但這種方法的執行速度一定要快,不然會影響到新的棧頂活動的使用。
5. onStop()
這種方法在活動全然不可見的時候呼叫。

它和 onPause()方法的主要差別在於,假設啟動的新活動是一個對話方塊式的活動。那麼 onPause()方法會得到執行,而 onStop()方法並不會執行。


6. onDestroy()
這種方法在活動被銷燬之前呼叫,之後活動的狀態將變為銷燬狀態。
7. onRestart()
這種方法在活動由停止狀態變為執行狀態之前呼叫,也就是活動被又一次啟動了。

以上七個方法中除了 onRestart()方法。其它都是兩兩相對的。從而又能夠將活動分為三種生存期。
1. 完整生存期
活動在 onCreate()方法和 onDestroy()方法之間所經歷的,就是完整生存期。普通情況下。一個活動會在 onCreate()方法中完畢各種初始化操作,而在 onDestroy()方法中完畢釋放記憶體的操作。


2. 可見生存期
活動在 onStart()方法和 onStop()方法之間所經歷的。就是可見生存期。在可見生存期內,活動對於使用者總是可見的,即便有可能無法和使用者進行互動。我們能夠通過這兩個方法。合理地管理那些對使用者可見的資源。比方在 onStart()方法中對資源進行載入,而在 onStop()方法中對資源進行釋放, 從而保證處於停止狀態的活動不會佔用過多記憶體。
3. 前臺生存期
活動在 onResume()方法和 onPause()方法之間所經歷的,就是前臺生存期。在前臺生存期內。活動總是處於執行狀態的,此時的活動是能夠和使用者進行相互的,我們平時看到和接觸最多的也這個狀態下的活動。
這裡寫圖片描寫敘述

4. 活動生命期例項

講了這麼多理論知識,也是時候該實戰一下了,以下我們將通過一個例項,讓你能夠更加直觀地體驗活動的生命週期。

這次我們不準備在 ActivityTest 這個專案的基礎上改動了,而是新建一個專案。

因此,首先關閉 ActivityTest 專案。然後新建一個ActivityLifeCycleTest 專案。

新建專案的過程你應該已經非常清楚了,不須要我再進行贅述,這次我們同意 ADT 幫我們自己主動建立活動。這樣能夠省去不少工作,建立的活動名和佈局名都使用預設值。這樣主活動就建立完畢了,我們還須要分別再建立兩個子活動,NormalActivity 和DialogActivity,以下一步步來實現。

新建 normal_layout.xml 檔案,程式碼例如以下所看到的:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >

    <TextView
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:text="This is a normal activity"
    />
</LinearLayout>

這個佈局中我們就非常easy地使用了一個 TextView,然後相同的方法,我們再新建一個 dialog_layout.xml 檔案,程式碼例如以下所看到的:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >

    <TextView
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:text="This is a dialog activity"
    />
</LinearLayout>

兩個佈局檔案的程式碼差點兒沒有差別,僅僅是顯示的文字不同而已。然後新建 NormalActivity 繼承自 Activity,程式碼例如以下所看到的:

public class NormalActivity extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        setContentView(R.layout.normal_layout);
    }
}

我們在 NormalActivity 中載入了 normal_layout 這個佈局。相同的方法。再新建 DialogActivity 繼承自 Activity,程式碼例如以下所看到的:

public class DialogActivity extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        setContentView(R.layout.dialog_layout);
    }
}

我們在 DialogActivity 中載入了 dialog_layout 這個佈局。事實上從名字上你就能夠看出。這兩個活動一個是普通的活動,一個是對話方塊式的活動。可是如今無論怎麼看,這兩個活動的程式碼都差點兒都是一模一樣的,在哪裡有體現出將活動設成對話方塊式的呢?彆著急,以下我們立即開始設定。在AndroidManifest.xml 的標籤中加入例如以下程式碼:

<activity android:name=".NormalActivity" >
</activity>

<activity android:name=".DialogActivity" android:theme="@android:style/
Theme.Dialog" >
</activity>

這裡分別為兩個活動進行註冊。可是 DialogActivity 的註冊程式碼有些不同,它使用了一個 android:theme 屬性,這是用於給當前活動指定主題的。Android 系統內建有非常多主題能夠選擇。當然我們也能夠定製自己的主題。而這裡@android:style/Theme.Dialog 則毫無疑問是讓DialogActivity 使用對話方塊式的主題。

接下來我們改動 activity_main.xml,又一次定製我們主活動的佈局:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"

    <Button
    android:id="@+id/start_normal_activity"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:text="Start NormalActivity" />

    <Button
    android:id="@+id/start_dialog_activity"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:text="Start DialogActivity" />
</LinearLayout>

自己主動生成的佈局程式碼有些複雜, 這裡我們全然替換掉。 仍然還是使用最熟悉的 LinearLayout。然後加入了兩個button,一個用於啟動NormalActivity,一個用於啟動 DialogActivity。

最後改動 MainActivity 中的程式碼,例如以下所看到的:

public class MainActivity extends Activity {
    public static final String TAG = "MainActivity";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Log.d(TAG, "onCreate");

        requestWindowFeature(Window.FEATURE_NO_TITLE);
        setContentView(R.layout.activity_main);
        Button startNormalActivity = (Button) findViewById(R.id.start_normal_activity);
        Button startDialogActivity = (Button) findViewById(R.id.start_dialog_activity);

        startNormalActivity.setOnClickListener(new OnClickListener() {
        @Override
        public void onClick(View v) {
            Intent intent = new Intent(MainActivity.this,
            NormalActivity.class);
            startActivity(intent);
        }
        });

        startDialogActivity.setOnClickListener(new OnClickListener() {
        @Override
        public void onClick(View v) {
            Intent intent = new Intent(MainActivity.this,
            DialogActivity.class);
            startActivity(intent);
        }
        });
    }

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

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

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

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

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

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

在onCreate()方法中,我們分別為兩個button註冊了點選事件,點選第一個button會啟動NormalActivity,點選第二個button會啟動 DialogActivity。

然後在 Activity 的七個回撥方法中分別列印了一句話,這樣就能夠通過觀察日誌的方式來更直觀地理解活動的生命週期。

如今執行程式。效果如圖所看到的:
這裡寫圖片描寫敘述

5. 活動被銷燬了怎麼辦

前面我們已經說過,當一個活動進入到了停止狀態,是有可能被系統回收的。那麼想象以下場景,應用中有一個活動 A,使用者在活動 A 的基礎上啟動了活動 B。活動 A 就進入了停止狀態。這個時候由於系統記憶體不足。將活動 A 回收掉了,然後使用者按下 Back 鍵返回活動 A, 會出現什麼情況呢?事實上還是會正常顯示活動 A的, 僅僅只是這時並不會執行 onRestart()方法。而是會執行活動 A 的 onCreate()方法,由於活動 A 在這樣的情況下會被又一次建立一次。

這樣看上去好像一切正常,可是別忽略了一個重要問題,活動 A 中是可能存在暫時資料和狀態的。

打個比方,MainActivity 中有一個文字輸入框。如今你輸入了一段文字。然後啟動 NormalActivity,這時 MainActivity 由於系統記憶體不足被回收掉。過了一會你又點選了Back 鍵回到 MainActivity。你會發現剛剛輸入的文字所有都沒了。由於 MainActivity 被又一次建立了。

假設我們的應用出現了這樣的情況。是會嚴重影響使用者體驗的,所以必須要想想辦法解決問題。查閱文件能夠看出。Activity 中還提供了一個 onSaveInstanceState()回撥方法。這種方法會保證一定在活動被回收之前呼叫。因此我們能夠通過這種方法來解決活動被回收時暫時資料得不到儲存的問題。

onSaveInstanceState()方法會攜帶一個 Bundle 型別的引數。Bundle 提供了一系列的方法用於儲存資料。比方能夠使用 putString()方法儲存字串。使用 putInt()方法儲存整型資料。以此類推。每一個儲存方法須要傳入兩個引數,第一個引數是鍵,用於後面從 Bundle 中取值。第二個引數是真正要儲存的內容。

在 MainActivity 中加入例如以下程式碼就能夠將暫時資料進行儲存:

@Override
protected void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    String tempData = "Something you just typed";
    outState.putString("data_key", tempData);
}

資料是已經儲存下來了。那麼我們應該在哪裡進行恢復呢?細心的你或許早就發現,我們一直使用的 onCreate()方法事實上也有一個 Bundle 型別的引數。

這個引數在普通情況下都是null,可是當活動被系統回收之前有通過 onSaveInstanceState()方法來儲存資料的話,這個引數就會帶有之前所儲存的所有資料。我們僅僅須要再通過對應的取值方法將資料取出就可以。

改動 MainActivity 的 onCreate()方法,例如以下所看到的:

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    Log.d(TAG, "onCreate");
    requestWindowFeature(Window.FEATURE_NO_TITLE);
    setContentView(R.layout.activity_main);

    if (savedInstanceState != null) {
        String tempData = savedInstanceState.getString("data_key");
        Log.d(TAG, tempData);
    }
    ……
}

取出值之後再做對應的恢復操作就能夠了,比方說將文字內容又一次賦值到文字輸入框上,這裡我們僅僅是簡單地列印一下。

不知道你有沒有察覺,使用 Bundle 來儲存和取出資料是不是有些似曾相識呢?沒錯!

我們在使用 Intent 傳遞資料時也是用的相似的方法。這裡跟你提醒一點,Intent 還能夠結合Bundle 一起用於傳遞資料的,首先能夠把須要傳遞的資料都儲存在 Bundle 物件中。然後再將 Bundle 物件存放在 Intent 裡。到了目標活動之後先從 Intent 中取出 Bundle,再從 Bundle中一一取出資料。

相關文章