1. 程式人生 > >Android Activity生命週期都該做哪些事情?

Android Activity生命週期都該做哪些事情?

Android系統根據生命週期的不同階段喚起對應的回撥函式來執行程式碼。系統存在啟動與銷燬一個activity的一套有序的回撥函式。本節來討論下不同生命週期的回撥函式裡都該做哪些事情,不該做哪些事情。

理解生命週期的回撥

在一個activity的生命週期中,系統會像金字塔模型一樣去呼叫一系列的生命週期回撥函式。Activity生命週期的每一個階段就像金字塔中的臺階。當系統建立了一個新的activity例項,每一個回撥函式會向上一階移動activity狀態。處在金字塔頂端意味著當前activity處在前臺並處於使用者可與其進行互動的狀態。

當用戶退出這個activity時,為了回收該activity,系統會呼叫其它方法來向下一階移動activity狀態。在某些情況下,activity會隱藏在金字塔下等待(例如當用戶切換到其他app),此時activity可以重新回到頂端(如果使用者回到這個activity)並恢復使用者離開時的狀態。
這裡寫圖片描述


這是一張Activity的生命週期圖,當然我們也可以從列印中觀察整個Activity生命週期呼叫的回撥方法。下面列印是從啟動一個MainActivity跳轉到另一個OtherActivity,然後點選Back,直到整個程式退出。列印如下:
這裡寫圖片描述

根據activity的複雜度,也許不需要實現所有的生命週期方法。但瞭解每一個方法的回撥時機並在其中填充相應功能,使得確保app能夠像使用者期望的那樣執行是很有必要的。如何實現一個符合使用者期待的app,我們需要注意下面幾點:

  • 使用app的時候,不會因為有來電通話或者切換到其他app而導致程式crash。
  • 使用者沒有啟用某個元件時不會消耗寶貴的系統資源。
  • 離開app並且一段時間後返回,不會丟失使用者的使用進度。
  • 裝置發生螢幕旋轉時不會crash或者丟失使用者的使用進度。

然而,其中只有三個狀態是靜態的,這三個狀態下activity可以存在一段比較長的時間。(其它幾個狀態會很快就切換掉,停留的時間比較短暫)

  • Resumed:該狀態下,activity處在前臺,使用者可以與它進行互動。(通常也被理解為”running” 狀態)
  • Paused:該狀態下,activity的部分被另外一個activity所遮蓋:另外的activity來到前臺,但是半透明的,不會覆蓋整個螢幕。被暫停的activity不再接受使用者的輸入且不再執行任何程式碼。
  • Stopped:該狀態下, activity完全被隱藏,對使用者不可見。可以認為是在後臺。當stopped, activity例項與它的所有狀態資訊(如成員變數等)都會被保留,但activity不能執行任何程式碼。
    其它狀態 (Created與Started)都是短暫的,系統快速的執行那些回撥函式並通過執行下一階段的回撥函式移動到下一個狀態。也就是說,在系統呼叫onCreate(), 之後會迅速呼叫onStart(), 之後再迅速執行onResume()。以上就是基本的activity生命週期。

一旦onCreate 操作完成,系統會迅速呼叫onStart() 與onResume()方法。我們的activity不會在Created或者Started狀態停留。技術上來說, activity在onStart()被呼叫後開始被使用者可見,但是 onResume()會迅速被執行使得activity停留在Resumed狀態,直到一些因素髮生變化才會改變這個狀態。例如接收到一個來電,使用者切換到另外一個activity,或者是裝置螢幕關閉。

暫停與恢復Activity

在正常使用app時,前端的activity有時會被其他可見的元件阻塞(obstructed),從而導致當前的activity進入Pause狀態。例如,當開啟一個半透明的activity時(例如以對話方塊的形式),之前的activity會被暫停。 只要之前的activity仍然被部分可見,這個activity就會一直處於Paused狀態。

然而,一旦之前的activity被完全阻塞並不可見時,則其會進入Stop狀態(將在下一小節討論)。

activity一旦進入paused狀態,系統就會呼叫activity中的onPause()方法, 該方法中可以停止不應該在暫停過程中執行的操作,如暫停視訊播放;或者儲存那些有可能需要長期儲存的資訊。如果使用者從暫停狀態回到當前activity,系統應該恢復那些資料並執行onResume()方法。

Note: 當我們的activity收到呼叫onPause()的訊號時,那可能意味者activity將被暫停一段時間,並且使用者很可能回到我們的activity。然而,那也是使用者要離開我們的activtiy的第一個訊號。
這裡寫圖片描述

Figure 1. 當一個半透明的activity阻塞activity時,系統會呼叫onPause()方法並且這個activity會停留在Paused 狀態(1). 如果使用者在這個activity還是在Paused 狀態時回到這個activity,系統則會呼叫它的onResume() (2).

暫停Activity

當系統呼叫activity中的onPause(),從技術上講,意味著activity仍然處於部分可見的狀態.但更多時候意味著使用者正在離開這個activity,並馬上會進入Stopped state. 通常應該在onPause()回撥方法裡面做以下事情:

  • 停止動畫或者是其他正在執行的操作,那些都會導致CPU的浪費.
  • 提交在使用者離開時期待儲存的內容(例如郵件草稿).
  • 釋放系統資源,例如broadcast receivers, sensors (比如GPS), 或者是其他任何會影響到電量的資源。

例如, 如果程式使用Camera,onPause()會是一個比較好的地方去做那些釋放資源的操作。

@Override
public void onPause() {
    super.onPause();  // Always call the superclass method first

    // Release the Camera because we don't need it when paused
    // and other activities might need to use it.
    if (mCamera != null) {
        mCamera.release()
        mCamera = null;
    }
}

通常,不應該使用onPause()來儲存使用者改變的資料 (例如填入表格中的個人資訊) 到永久儲存(File或者DB)上。僅僅當確認使用者期待那些改變能夠被自動儲存的時候(例如正在撰寫郵件草稿),才把那些資料存到永久儲存 。但是,我們應該避免在onPause()時執行CPU-intensive 的工作,例如寫資料到DB,因為它會導致切換到下一個activity變得緩慢(應該把那些heavy-load的工作放到onStop()去做)。

如果activity實際上是要被Stop,那麼我們應該為了切換的順暢而減少在OnPause()方法裡面的工作量。

Note:當activity處於暫停狀態,Activity.html” target=”_blank”>Activity例項是駐留在記憶體中的,並且在activity 恢復的時候重新呼叫。我們不需要在恢復到Resumed狀態的一系列回撥方法中重新初始化元件。

恢復activity

當用戶從Paused狀態恢復activity時,系統會呼叫onResume()方法。

請注意,系統每次呼叫這個方法時,activity都處於前臺,包括第一次建立的時候。所以,應該實現onResume()來初始化那些在onPause方法裡面釋放掉的元件,並執行那些activity每次進入Resumed state都需要的初始化動作 (例如開始動畫與初始化那些只有在獲取使用者焦點時才需要的元件)

下面的onResume()的例子是與上面的onPause()例子相對應的。

@Override
public void onResume() {
    super.onResume();  // Always call the superclass method first

    // Get the Camera instance as the activity achieves full user focus
    if (mCamera == null) {
        initializeCamera(); // Local method to handle camera init
    }
}

停止與重啟Activity

恰當的停止與重啟我們的activity是很重要的,在activity生命週期中,他們能確保使用者感知到程式的存在並不會丟失他們的進度。在下面一些關鍵的場景中會涉及到停止與重啟:

  • 使用者開啟最近使用app的選單並從我們的app切換到另外一個app,這個時候我們的app是被停止的。如果使用者通過手機主介面的啟動程式圖示或者最近使用程式的視窗回到我們的app,那麼我們的activity會重啟。
  • 使用者在我們的app裡面執行啟動一個新activity的操作,當前activity會在第二個activity被建立後stop。如- - 果使用者點選back按鈕,第一個activtiy會被重啟。
  • 使用者在使用我們的app時接收到一個來電通話。

Activity類提供了Activity.onStop()”>onStop()與Activity.onRestart()”>onRestart()方法來允許在activity停止與重啟時進行呼叫。不同於暫停狀態的部分阻塞UI,停止狀態是UI不再可見並且使用者的焦點轉移到另一個activity中.

Note: 因為系統在activity停止時會在記憶體中儲存Activity的例項,所以有時不需要實現onStop(),onRestart()甚至是onStart()方法. 因為大多數的activity相對比較簡單,activity會自己停止與重啟,我們只需要使用onPause()來停止正在執行的動作並斷開系統資源連結。
這裡寫圖片描述

Figure 1. 上圖顯示:當用戶離開我們的activity時,系統會呼叫onStop()來停止activity (1). 這個時候如果使用者返回,系統會呼叫onRestart()(2), 之後會迅速呼叫onStart()(3)與onResume()(4). 請注意:無論什麼原因導致activity停止,系統總是會在onStop()之前呼叫onPause()方法。

停止activity

當activity呼叫onStop()方法, activity不再可見,並且應該釋放那些不再需要的所有資源。一旦activity停止了,系統會在需要記憶體空間時摧毀它的例項(和棧結構有關,通常back操作會導致前一個activity被銷燬)。極端情況下,系統會直接殺死我們的app程序,並不執行activity的onDestroy()回撥方法, 因此我們需要使用onStop()來釋放資源,從而避免記憶體洩漏。(這點需要注意)

儘管onPause()方法是在onStop()之前呼叫,我們應該使用onStop()來執行那些CPU intensive的shut-down操作,例如往資料庫寫資訊。

例如,下面是一個在onStop()的方法裡面儲存筆記草稿到persistent storage的示例:

@Override
protected void onStop() {
    super.onStop();  // Always call the superclass method first

    // Save the note's current draft, because the activity is stopping
    // and we want to be sure the current note progress isn't lost.
    ContentValues values = new ContentValues();
    values.put(NotePad.Notes.COLUMN_NAME_NOTE, getCurrentNoteText());
    values.put(NotePad.Notes.COLUMN_NAME_TITLE, getCurrentNoteTitle());

    getContentResolver().update(
            mUri,    // The URI for the note to update.
            values,  // The map of column names and new values to apply to them.
            null,    // No SELECT criteria are used.
            null     // No WHERE columns are used.
            );
}

activity已經停止後,Activity物件會儲存在記憶體中,並在activity resume時被重新呼叫。我們不需要在恢復到Resumed state狀態前重新初始化那些被儲存在記憶體中的元件。系統同樣儲存了每一個在佈局中的檢視的當前狀態,如果使用者在EditText元件中輸入了text,它會被儲存,因此不需要儲存與恢復它。

Note: 即使系統會在activity stop時停止這個activity,它仍然會儲存View物件的狀態(比如EditText中的文字) 到一個Bundle中,並且在使用者返回這個activity時恢復它們(下一小節會介紹在activity銷燬與重新建立時如何使用Bundle來儲存其他資料的狀態).

啟動與重啟activity

當activity從Stopped狀態回到前臺時,它會呼叫onRestart().系統再呼叫onStart()方法,onStart()方法會在每次activity可見時都會被呼叫。onRestart()方法則是隻在activity從stopped狀態恢復時才會被呼叫,因此我們可以使用它來執行一些特殊的恢復(restoration)工作,請注意之前是被stopped而不是destrory。

使用onRestart()來恢復activity狀態是不太常見的,因此對於這個方法如何使用沒有任何的guidelines。然而,因為onStop()方法應該做清除所有activity資源的操作,我們需要在重啟activtiy時重新例項化那些被清除的資源,同樣, 我們也需要在activity第一次建立時例項化那些資源。介於上面的原因,應該使用onStart()作為onStop()所對應方法。因為系統會在建立activity與從停止狀態重啟activity時都會呼叫onStart()。也就是說,我們在onStop裡面做了哪些清除的操作,就該在onStart裡面重新把那些清除掉的資源重新創建出來。

例如:因為使用者很可能在回到這個activity之前已經過了很長一段時間,所以onStart()方法是一個比較好的地方來驗證某些必須的系統特性是否可用。

@Override
protected void onStart() {
    super.onStart();  // Always call the superclass method first

    // The activity is either being restarted or started for the first time
    // so this is where we should make sure that GPS is enabled
    LocationManager locationManager =
            (LocationManager) getSystemService(Context.LOCATION_SERVICE);
    boolean gpsEnabled = locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER);

    if (!gpsEnabled) {
        // Create a dialog here that requests the user to enable GPS, and use an intent
        // with the android.provider.Settings.ACTION_LOCATION_SOURCE_SETTINGS action
        // to take the user to the Settings screen to enable GPS when they click "OK"
    }
}

@Override
protected void onRestart() {
    super.onRestart();  // Always call the superclass method first

    // Activity being restarted from stopped state
}

當系統Destory我們的activity,它會為activity呼叫onDestroy()方法。因為我們會在onStop方法裡面做釋放資源的操作,那麼onDestory方法則是我們最後去清除那些可能導致記憶體洩漏的地方。因此需要確保那些執行緒都被destroyed並且所有的操作都被停止。

重新建立Activity

有幾個場景中,Activity是由於正常的程式行為而被Destory的。例如當用戶點選返回按鈕或者是Activity通過呼叫Activity.html#finish()”>finish()來發出停止訊號。系統也有可能會在Activity處於stop狀態且長時間不被使用,或者是在前臺activity需要更多系統資源的時關閉後臺程序,以圖獲取更多的記憶體。

當Activity是因為使用者點選Back按鈕或者是activity通過呼叫finish()結束自己時,系統就丟失了對Activity例項的引用,因為這一行為意味著不再需要這個activity了。然而,如果因為系統資源緊張而導致Activity的Destory, 系統會在使用者回到這個Activity時有這個Activity存在過的記錄,系統會使用那些儲存的記錄資料(描述了當Activity被Destory時的狀態)來重新建立一個新的Activity例項。那些被系統用來恢復之前狀態而儲存的資料被叫做 “instance state” ,它是一些存放在Bundle物件中的key-value pairs。(請注意這裡的描述,這對理解onSaveInstanceState執行的時刻很重要)

Caution: 你的Activity會在每次旋轉螢幕時被destroyed與recreated。當螢幕改變方向時,系統會Destory與Recreate前臺的activity,因為螢幕配置被改變,你的Activity可能需要載入另一些替代的資源(例如layout).
預設情況下, 系統使用 Bundle 例項來儲存每一個View(檢視)物件中的資訊(例如輸入EditText 中的文字內容)。因此,如果Activity被destroyed與recreated, 則layout的狀態資訊會自動恢復到之前的狀態。然而,activity也許存在更多你想要恢復的狀態資訊,例如記錄使用者Progress的成員變數(member variables)。

Note: 為了使Android系統能夠恢復Activity中的View的狀態,每個View都必須有一個唯一ID,由android:id定義。
為了可以儲存額外更多的資料到saved instance state。在Activity的生命週期裡面存在一個額外的回撥函式,你必須重寫這個函式。該回調函式並沒有在前面課程的圖片示例中顯示。這個方法是Activity.onSaveInstanceState(android.os.Bundle)>onSaveInstanceState() ,當用戶離開Activity時,系統會呼叫它。當系統呼叫這個函式時,系統會在Activity被異常Destory時傳遞 Bundle 物件,這樣我們就可以增加額外的資訊到Bundle中並儲存到系統中。若系統在Activity被Destory之後想重新建立這個Activity例項時,之前的Bundle物件會(系統)被傳遞到你我們activity的Activity.onRestoreInstanceState(android.os.Bundle)>onRestoreInstanceState()方法與 onCreate() 方法中。
這裡寫圖片描述
Figure 2. 當系統開始停止Activity時,只有在Activity例項會需要重新建立的情況下才會呼叫到Activity.onSaveInstanceState(android.os.Bundle)>onSaveInstanceState() (1) ,在這個方法裡面可以指定額外的狀態資料到Bunde中。如果這個Activity被destroyed然後這個例項又需要被重新建立時,系統會傳遞在 (1) 中的狀態資料到 onCreate() (2) 與 Activity.onRestoreInstanceState(android.os.Bundle)>onRestoreInstanceState()(3).

(通常來說,跳轉到其他的activity或者是點選Home都會導致當前的activity執行onSaveInstanceState,因為這種情況下的activity都是有可能會被destory並且是需要儲存狀態以便後續恢復使用的,而從跳轉的activity點選back回到前一個activity,那麼跳轉前的activity是執行退棧的操作,所以這種情況下是不會執行onSaveInstanceState的,因為這個activity不可能存在需要重建的操作)

儲存Activity狀態

當我們的activity開始Stop,系統會呼叫 onSaveInstanceState() ,Activity可以用鍵值對的集合來儲存狀態資訊。這個方法會預設儲存Activity檢視的狀態資訊,如在 EditText 元件中的文字或 ListView 的滑動位置。

為了給Activity儲存額外的狀態資訊,你必須實現onSaveInstanceState() 並增加key-value pairs到 Bundle 物件中,例如:

static final String STATE_SCORE = "playerScore";
static final String STATE_LEVEL = "playerLevel";
...

@Override
public void onSaveInstanceState(Bundle savedInstanceState) {
    // Save the user's current game state
    savedInstanceState.putInt(STATE_SCORE, mCurrentScore);
    savedInstanceState.putInt(STATE_LEVEL, mCurrentLevel);

    // Always call the superclass so it can save the view hierarchy state
    super.onSaveInstanceState(savedInstanceState);
}

Note: 必須要呼叫 onSaveInstanceState() 方法的父類實現,這樣預設的父類實現才能儲存檢視狀態的資訊。

恢復Activity狀態

當Activity從Destory中重建,我們可以從系統傳遞的Activity的Bundle中恢復儲存的狀態。 onCreate() 與 onRestoreInstanceState() 回撥方法都接收到了同樣的Bundle,裡面包含了同樣的例項狀態資訊。

由於 onCreate() 方法會在第一次建立新的Activity例項與重新建立之前被Destory的例項時都被呼叫,我們必須在嘗試讀取 Bundle 物件前檢測它是否為null。如果它為null,系統則是建立一個新的Activity例項,而不是恢復之前被Destory的Activity。

下面是一個示例:演示在onCreate方法裡面恢復一些資料:

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState); // Always call the superclass first

    // Check whether we're recreating a previously destroyed instance
    if (savedInstanceState != null) {
        // Restore value of members from saved state
        mCurrentScore = savedInstanceState.getInt(STATE_SCORE);
        mCurrentLevel = savedInstanceState.getInt(STATE_LEVEL);
    } else {
        // Probably initialize members with default values for a new instance
    }
    ...
}

我們也可以選擇實現 onRestoreInstanceState() ,而不是在onCreate方法裡面恢復資料。 onRestoreInstanceState()方法會在 onStart() 方法之後執行. 系統僅僅會在存在需要恢復的狀態資訊時才會呼叫 onRestoreInstanceState() ,因此不需要檢查 Bundle 是否為null。

public void onRestoreInstanceState(Bundle savedInstanceState) {
    // Always call the superclass so it can restore the view hierarchy
    super.onRestoreInstanceState(savedInstanceState);

    // Restore state members from saved instance
    mCurrentScore = savedInstanceState.getInt(STATE_SCORE);
    mCurrentLevel = savedInstanceState.getInt(STATE_LEVEL);
}

Note: 與上面儲存一樣,總是需要呼叫onRestoreInstanceState()方法的父類實現,這樣預設的父類實現才能儲存檢視狀態的資訊。更多關於執行時狀態改變引起的recreate我們的activity。