啟動與銷毀Activity
不同于使用 main() 方法啟動應用的其他編程范例,Android 系統會通過調用對應于其生命周期中特定階段的特定回調方法在 Activity 實例中啟動代碼。 有一系列可啟動Activity的回調方法,以及一系列可分解Activity的回調方法。
本課程概述了最重要的生命周期方法,并向您展示如何處理創建Activity新實例的第一個生命周期回調。
了解生命周期回調
在Activity的生命周期中,系統會按類似于階梯金字塔的順序調用一組核心的生命周期方法。也就是說,Activity生命周期的每個階段就是金字塔上的一階。 當系統創建新Activity實例時,每個回調方法會將Activity狀態向頂端移動一階。金字塔的頂端是Activity在前臺運行并且用戶可以與其交互的時間點。
當用戶開始離開Activity時,系統會調用其他方法在金字塔中將Activity狀態下移,從而銷毀Activity。在有些情況下,Activity將只在金字塔中部分下移并等待(比如,當用戶切換到其他應用時),Activity可從該點開始移回頂端(如果用戶返回到該Activity),并在用戶停止的位置繼續。
圖 1.簡化的Activity生命周期圖示,以階梯金字塔表示。此圖示顯示,對于用于將Activity朝頂端的“繼續”狀態移動一階的每個回調,有一種將Activity下移一階的回調方法。Activity還可以從“暫?!焙汀巴V埂睜顟B回到繼續狀態。
根據Activity的復雜程度,您可能不需要實現所有生命周期方法。但是,了解每個方法并實現確保您的應用按照用戶期望的方式運行的方法非常重要。正確實現您的Activity生命周期方法可確保您的應用按照以下幾種方式良好運行,包括:
- 如果用戶在使用您的應用時接聽來電或切換到另一個應用,它不會崩潰。
- 在用戶未主動使用它時不會消耗寶貴的系統資源。
- 如果用戶離開您的應用并稍后返回,不會丟失用戶的進度。
- 當屏幕在橫向和縱向之間旋轉時,不會崩潰或丟失用戶的進度。
正如您將要在以下課程中要學習的,有Activity會在圖 1 所示不同狀態之間過渡的幾種情況。但是,這些狀態中只有三種可以是靜態。 也就是說,Activity只能在三種狀態之一下存在很長時間。
- Resumed:在這種狀態下,Activity處于前臺,且用戶可以與其交互。(有時也稱為“運行”狀態。)
- Paused:在這種狀態下,Activity被在前臺中處于半透明狀態或者未覆蓋整個屏幕的另一個Activity—部分阻擋。暫停的Activity不會接收用戶輸入并且無法執行任何代碼。
- Stopped:在這種狀態下,Activity被完全隱藏并且對用戶不可見;它被視為處于后臺。停止時,Activity實例及其諸如成員變量等所有狀態信息將保留,但它無法執行任何代碼。
其他狀態(“創建”和“開始”)是瞬態,
其它狀態 (Created與Started)都是短暫的瞬態,系統會通過調用下一個生命周期回調方法從這些狀態快速移到下一個狀態。 也就是說,在系統調用 onCreate() 之后,它會快速調用 onStart(),緊接著快速調用 onResume()。
指定程序首次啟動的Activity
當用戶從主界面點擊程序圖標時,系統會調用app中被聲明為"launcher" (or "main") activity中的onCreate()方法。這個Activity被用來當作程序的主要進入點。
我們可以在AndroidManifest.xml中定義作為主activity的activity。
這個main activity必須在manifest使用包括 MAIN
action 與 LAUNCHER
category 的<intent-filter>
標簽來聲明。例如:
<activity android:name=".MainActivity" android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
tips:當你使用Android SDK工具來創建Android工程時,工程中就包含了一個默認的聲明有這個filter的activity類。
如果程序中沒有聲明了MAIN action 或者LAUNCHER category的activity,那么在設備的主界面列表里面不會呈現app圖標。
創建一個新的實例
大多數app包括多個activity,使用戶可以執行不同的動作。不論這個activity是當用戶點擊應用圖標創建的main activtiy還是為了響應用戶行為而創建的其他activity,系統都會調用新activity實例中的onCreate()方法。
我們必須實現onCreate()方法來執行程序啟動所需要的基本邏輯。例如可以在onCreate()方法中定義UI以及實例化類成員變量。
例如:下面的onCreate()方法演示了為了建立一個activity所需要的一些基礎操作。如聲明UI元素,定義成員變量,配置UI等。(onCreate里面盡量少做事情,避免程序啟動太久都看不到界面)
TextView mTextView; // Member variable for text view in the layout
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Set the user interface layout for this Activity
// The layout file is defined in the project res/layout/main_activity.xml file
setContentView(R.layout.main_activity);
// Initialize member TextView so we can manipulate it later
mTextView = (TextView) findViewById(R.id.text_message);
// Make sure we're running on Honeycomb or higher to use ActionBar APIs
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
// For the main activity, make sure the app icon in the action bar
// does not behave as a button
ActionBar actionBar = getActionBar();
actionBar.setHomeButtonEnabled(false);
}
}
Caution:用SDK_INT來避免舊的系統調用了只在Android 2.0(API level 5)或者更新的系統可用的方法(上述if條件中的代碼)。舊的系統調用了這些方法會拋出一個運行時異常。
一旦onCreate 操作完成,系統會迅速調用onStart() 與onResume()方法。我們的activity不會在Created或者Started狀態停留。技術上來說, activity在onStart()被調用后開始被用戶可見,但是 onResume()會迅速被執行使得activity停留在Resumed狀態,直到一些因素發生變化才會改變這個狀態。例如接收到一個來電,用戶切換到另外一個activity,或者是設備屏幕關閉。
在后面的課程中,我們將看到其他方法是如何使用的,onStart() 與 onResume()在用戶從Paused或Stopped狀態中恢復的時候非常有用。
圖2. 上圖顯示了onCreate(), onStart() 和 onResume()是如何執行的。當這三個順序執行的回調函數完成后,activity會到達Resumed狀態。
銷毀Activity
activity的第一個生命周期回調函數是 onCreate(),它最后一個回調是onDestroy().當收到需要將該activity徹底移除的信號時,系統會調用這個方法。
大多數 app并不需要實現這個方法,因為局部類的references會隨著activity的銷毀而銷毀,并且我們的activity應該在onPause()與onStop()中執行清除activity資源的操作。然而,如果activity含有在onCreate調用時創建的后臺線程,或者是其他有可能導致內存泄漏的資源,則應該在OnDestroy()時進行資源清理,殺死后臺線程。
@Override
public void onDestroy() {
super.onDestroy(); // Always call the superclass
// Stop method tracing that the activity started during onCreate()
android.os.Debug.stopMethodTracing();
}
tips: 除非程序在onCreate()方法里面就調用了finish()方法,系統通常是在執行了onPause()與onStop() 之后再調用onDestroy() 。在某些情況下,例如我們的activity只是做了一個臨時的邏輯跳轉的功能,它只是用來決定跳轉到哪一個activity,這樣的話,需要在onCreate里面調用finish方法,這樣系統會直接調用onDestory,跳過生命周期中的其他方法。
暫停與恢復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的第一個信號。
當一個半透明的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實例是駐留在內存中的,并且在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類提供了onStop()與onRestart()方法來允許在activity停止與重啟時進行調用。不同于暫停狀態的部分阻塞UI,停止狀態是UI不再可見并且用戶的焦點轉移到另一個activity中.
Note: 因為系統在activity停止時會在內存中保存Activity的實例,所以有時不需要實現onStop(),onRestart()甚至是onStart()方法. 因為大多數的activity相對比較簡單,activity會自己停止與重啟,我們只需要使用onPause()來停止正在運行的動作并斷開系統資源鏈接。
上圖顯示:當用戶離開我們的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通過調用finish()來發出停止信號。系統也有可能會在Activity處于stop狀態且長時間不被使用,或者是在前臺activity需要更多系統資源的時關閉后臺進程,以圖獲取更多的內存。
當Activity是因為用戶點擊Back按鈕或者是activity通過調用finish()結束自己時,系統就丟失了對Activity實例的引用,因為這一行為意味著不再需要這個activity了。然而,如果因為系統資源緊張而導致Activity的Destory, 系統會在用戶回到這個Activity時有這個Activity存在過的記錄,系統會使用那些保存的記錄數據(描述了當Activity被Destory時的狀態)來重新創建一個新的Activity實例。那些被系統用來恢復之前狀態而保存的數據被叫做 "instance state" ,它是一些存放在Bundle對象中的key-value pairs。(請注意這里的描述,這對理解onSaveInstanceState執行的時刻很重要)
tips: 你的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的生命周期里面存在一個額外的回調函數,你必須重寫這個函數。該回調函數并沒有在前面課程的圖片示例中顯示。這個方法是onSaveInstanceState() ,當用戶離開Activity時,系統會調用它。當系統調用這個函數時,系統會在Activity被異常Destory時傳遞 Bundle 對象,這樣我們就可以增加額外的信息到Bundle中并保存到系統中。若系統在Activity被Destory之后想重新創建這個Activity實例時,之前的Bundle對象會(系統)被傳遞到你我們activity的onRestoreInstanceState()方法與 onCreate() 方法中。
當系統開始停止Activity時,只有在Activity實例會需要重新創建的情況下才會調用到onSaveInstanceState() (1) ,在這個方法里面可以指定額外的狀態數據到Bunde中。如果這個Activity被destroyed然后這個實例又需要被重新創建時,系統會傳遞在 (1) 中的狀態數據到 onCreate() (2) 與 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);
}
tips: 必須要調用 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);
}
tips: 與上面保存一樣,總是需要調用onRestoreInstanceState()方法的父類實現,這樣默認的父類實現才能保存視圖狀態的信息。更多關于運行時狀態改變引起的recreate我們的activity。