[Android]Activity的生命週期

來源:互聯網
上載者:User

標籤:

想要學好安卓開發,就必須理解安卓軟體的生命週期,明白一個活動的建立、啟動、停止、暫停、重啟和銷毀的過程,知道各個階段會調用什麼函數進行處理不同的情況,這裡我們就來說說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>

這個布局中我們就非常簡單地使用了一個 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,然後加入了兩個按鈕,一個用於啟動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 的七個回調方法中分別列印了一句話,這樣就可以通過觀察日誌的方式來更直觀地理解活動的生命週期。

現在運行程式,效果:

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 中添加如下代碼就可以將臨時資料進行儲存:

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

資料是已經儲存下來了,那麼我們應該在哪裡進行恢複呢?細心的你也許早就發現,我們一直使用的 onCreate()方法其實也有一個 Bundle 類型的參數。這個參數在一般情況下都是null,但是當活動被系統回收之前有通過 onSaveInstanceState()方法來儲存資料的話,這個參數就會帶有之前所儲存的全部資料,我們只需要再通過相應的取值方法將資料取出即可。

修改 MainActivity 的 onCreate()方法,如下所示:

@Overrideprotected 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中一一取出資料。

[Android]Activity的生命週期

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.