[干貨] Android 深入淺出 Activity 生命周期(二)

Android 深入淺出 Activity 生命周期(一)
Android 深入淺出 Activity 生命周期(二)
編寫:kesenhoo
原文:http://developer.android.com/training/basics/activity-lifecycle/index.html


停止與重啟Activity

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

  • 用戶打開最近使用app的菜單并從我們的app切換到另外一個app,這個時候我們的app是被停止的。如果用戶通過手機主界面的啟動程序圖標或者最近使用程序的窗口回到我們的app,那么我們的activity會重啟。
  • 用戶在我們的app里面執行啟動一個新activity的操作,當前activity會在第二個activity被創建后stop。如果用戶點擊back按鈕,第一個activtiy會被重啟。
  • 用戶在使用我們的app時接收到一個來電通話.

Activity類提供了<a >onStop()</a>與<a >onRestart()</a>方法來允許在activity停止與重啟時進行調用。不同于暫停狀態的部分阻塞UI,停止狀態是UI不再可見并且用戶的焦點轉移到另一個activity中.

Note: 因為系統在activity停止時會在內存中保存Activity的實例,所以有時不需要實現onStop(),onRestart()甚至是onStart()方法. 因為大多數的activity相對比較簡單,activity會自己停止與重啟,我們只需要使用onPause()來停止正在運行的動作并斷開系統資源鏈接。

basic-lifecycle-stopped

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

停止activity

當activity調用onStop()方法, activity不再可見,并且應該釋放那些不再需要的所有資源。一旦activity停止了,系統會在需要內存空間時摧毀它的實例(和棧結構有關,通常back操作會導致前一個activity被銷毀)。極端情況下,系統會直接殺死我們的app進程,并不執行activity的<a >onDestroy()</a>回調方法, 因此我們需要使用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通過調用<a >finish()</a>來發出停止信號。系統也有可能會在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的生命周期里面存在一個額外的回調函數,你必須重寫這個函數。該回調函數并沒有在前面課程的圖片示例中顯示。這個方法是<a >onSaveInstanceState()</a> ,當用戶離開Activity時,系統會調用它。當系統調用這個函數時,系統會在Activity被異常Destory時傳遞 Bundle 對象,這樣我們就可以增加額外的信息到Bundle中并保存到系統中。若系統在Activity被Destory之后想重新創建這個Activity實例時,之前的Bundle對象會(系統)被傳遞到你我們activity的<a >onRestoreInstanceState()</a>方法與 onCreate() 方法中。

basic-lifecycle-savestate

Figure 5. 當系統開始停止Activity時,只有在Activity實例會需要重新創建的情況下才會調用到<a >onSaveInstanceState()</a> (1) ,在這個方法里面可以指定額外的狀態數據到Bunde中。如果這個Activity被destroyed然后這個實例又需要被重新創建時,系統會傳遞在 (1) 中的狀態數據到 onCreate() (2) 與 <a >onRestoreInstanceState()</a>(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);
}

Caution: 必須要調用 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);
}

Caution: 與上面保存一樣,總是需要調用onRestoreInstanceState()方法的父類實現,這樣默認的父類實現才能保存視圖狀態的信息。更多關于運行時狀態改變引起的recreate我們的activity。請參考Handling Runtime Changes.

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,505評論 6 533
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,556評論 3 418
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,463評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,009評論 1 312
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,778評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,218評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,281評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,436評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,969評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,795評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,993評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,537評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,229評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,659評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,917評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,687評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,990評論 2 374

推薦閱讀更多精彩內容