掌握活動的生命周期對任何 Android 開發者來說都非常重要,當你深入理解活動的生命 周期之后,就可以寫出更加連貫流暢的程序,並在如何合理管理應用資源方面,你會發揮的 游刃有余。你的應用程序將會擁有更好的用戶體驗。
2.4.1 返回棧
經過前面幾節的學習,我相信你已經發現了這一點,Android 中的活動是可以層疊的。 我們每啟動一個新的活動,就會覆蓋在原活動之上,然后點擊 Back 鍵會銷毀最上面的活動, 下面的一個活動就會重新顯示出來。
其實 Android 是使用任務(Task)來管理活動的,一個任務就是一組存放在棧里的活動 的集合,這個棧也被稱作返回棧(Back Stack)。棧是一種后進先出的數據結構,在默認情況 下,每當我們啟動了一個新的活動,它會在返回棧中入棧,並處於棧頂的位置。而每當我們 按下 Back 鍵或調用 finish()方法去銷毀一個活動時,處於棧頂的活動會出棧,這時前一個入 棧的活動就會重新處於棧頂的位置。系統總是會顯示處於棧頂的活動給用戶。
示意圖 2.19 展示了返回棧是如何管理活動入棧出棧操作的。

圖 2.19
2.4.2 活動狀態
每個活動在其生命周期中最多可能會有四種狀態。
1. 運行狀態
當一個活動位於返回棧的棧頂時,這時活動就處於運行狀態。系統最不願意回收的
就是處於運行狀態的活動,因為這會帶來非常差的用戶體驗。
2. 暫停狀態 當一個活動不再處於棧頂位置,但仍然可見時,這時活動就進入了暫停狀態。你可
能會覺得既然活動已經不在棧頂了,還怎么會可見呢?這是因為並不是每一個活動都會 占滿整個屏幕的,比如對話框形式的活動只會占用屏幕中間的部分區域,你很快就會在 后面看到這種活動。處於暫停狀態的活動仍然是完全存活着的,系統也不願意去回收這 種活動(因為它還是可見的,回收可見的東西都會在用戶體驗方面有不好的影響),只 有在內存極低的情況下,系統才會去考慮回收這種活動。
3. 停止狀態 當一個活動不再處於棧頂位置,並且完全不可見的時候,就進入了停止狀態。系統
仍然會為這種活動保存相應的狀態和成員變量,但是這並不是完全可靠的,當其他地方 需要內存時,處於停止狀態的活動有可能會被系統回收。
4. 銷毀狀態 當一個活動從返回棧中移除后就變成了銷毀狀態。系統會最傾向於回收處於這種狀
態的活動,從而保證手機的內存充足。
2.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()方法之間所經歷的,就是前台生存期。在前台 生存期內,活動總是處於運行狀態的,此時的活動是可以和用戶進行相互的,我們平時 看到和接觸最多的也這個狀態下的活動。
為了幫助你能夠更好的理解,Android 官方提供了一張活動生命周期的示意圖,如圖 2.20
所示。

圖 2.20
2.4.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>
這個布局中我們就非常簡單地使用了一個 TextView,用於顯示一行文字,在下一章中你 將會學到更多關於 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>標 簽中添加如下代碼:
<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" android:orientation="vertical" >
<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, 然后加入了兩個按鈕,一個用於啟動 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()方法中,我們分別為兩個按鈕注冊了點擊事件,點擊第一個按鈕會啟動 NormalActivity,點擊第二個按鈕會啟動 DialogActivity。然后在 Activity 的七個回調方法中 分別打印了一句話,這樣就可以通過觀察日志的方式來更直觀地理解活動的生命周期。
現在運行程序,效果如圖 2.21 所示。

圖 2.21
這時觀察 LogCat 中的打印日志,如圖 2.22 所示。

圖 2.22
可以看到,當 MainActivity 第一次被創建時會依次執行 onCreate()、onStart()和 onResume()
方法。然后點擊第一個按鈕,啟動 NormalActivity,如圖 2.23 所示。

圖 2.23
此時的打印信息如圖 2.24 所示。

圖 2.24
由於 NormalActivity 已經把 MainActivity 完全遮擋住,因此 onPause()和 onStop()方法都
會得到執行。然后按下 Back 鍵返回 MainActivity,打印信息如圖 2.25 所示。

圖 2.25
由於之前 MainActivity 已經進入了停止狀態,所以 onRestart()方法會得到執行,之后又 會依次執行 onStart()和 onResume()方法。注意此時 onCreate()方法不會執行,因為 MainActivity 並沒有重新創建。
然后再點擊第二個按鈕,啟動 DialogActivity,如圖 2.26 所示。
圖 2.26
此時觀察打印信息,如圖 2.27 所示。

圖 2.27
可以看到,只有 onPause() 方法得到了執行,onStop() 方法並沒有執行,這是因為
DialogActivity 並沒有完全遮擋住 MainActivity,此時 MainActivity 只是進入了暫停狀態,並 沒有進入停止狀態。相應地,按下 Back 鍵返回 MainActivity 也應該只有 onResume()方法會 得到執行,如圖 2.28 所示。

圖 2.28
最后在 MainActivity 按下 Back 鍵退出程序,打印信息如圖 2.29 所示。

圖 2.29
依次會執行 onPause()、onStop()和 onDestroy()方法,最終銷毀 MainActivity。 這樣活動完整的生命周期你已經體驗了一遍,是不是理解得更加深刻了?
2.4.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中一一取出數據。
