天天看點

Android Activity生命周期詳解

Activity作為Android的四大元件之一,其重要性不言而喻,本文就Activity的生命周期展開詳細的描述,并就項目開發中遇到的疑難問題一一進行解決。

Activity是棧管理式的,遵循先進後出原則,後進入的居于棧頂,先進入的居于棧底,Activity有四種狀态:

1.運作狀态:Activity居于棧頂,并處于前台展示狀态;

2.暫停狀态:Activity失去焦點并仍然可見,比如前一個Activity設定的是透明主題或者Activity有彈窗顯示;

3.停止狀态:Activity不可見,比如按下home鍵或者被其他Activity遮擋;

4.銷毀狀态:Activity由于系統配置發生改變、系統記憶體不足、人為銷毀時處于此狀态。

在正常狀态下,Activity的4種運作狀态會經曆以下生命周期:

(1)onCreate:Activity正在被建立;

(2)onStart:Activity已經處于可見狀态,但還未在前台展示,無法進行互動;

(3)onResume:Activity已經可見并在前台展示,處于可互動狀态;

(4)onRestart:Activity重新啟動;

(5)onPause:Activity暫停,正常情況下,後面會接onStop,但如果在一些特殊情況下,比如迅速的回到目前頁面,那麼onStop不會調用;

(6)onStop:Activity停止;

(7)onDestroy:Activity銷毀。

具體切換過程可見下圖(官方文檔連結:http://developer.android.com/reference/android/app/Activity.html)

Android Activity生命周期詳解

針對上圖,以下給出具體的說明,大概有以下幾種情況:

(1)第一次啟動:onCreate->onStart->onResume;

(2)home鍵:onPause->onStop;

(3)打開新Activity:onPause->onStop。特殊情況,在新Activity是透明主題下,目前Activity不會執行onStop;

(4)back鍵或者被回收:onPause->onStop->onDestroy;

(5)橫豎屏切換:onPause->onStop->onDestroy,然後執行步驟(1);

(6)從生命周期來說,onCreate和onDestroy是配對使用的,标志Activity的建立與銷毀,在整個生命周期中,隻會執行一次;onStart與onStop配對,标志Activity是否可見,可多次調用;onPause和onResume配對,标志Activity是否處于前台能和使用者互動。

這裡有幾點需要注意:

(1)在Activity切換過程中,比如A切換到B,B的建立一定是在A執行了onPause之後才會執行,是以在onPause方法中不能執行重量級任務,A的onStop會在B執行完onResume展現到前台之後執行,是以可以在onStop中執行稍微耗時點的操作,但也不宜過重;

(2)onResume和onStart以及onPause和onStop在Activity是否可見以及是否展現在前台有一定差別,但是具體實際應用中沒有太大差別。

以上說的是在Activity生命周期正常情況下的調用情況,那麼在異常情況下,大緻可以分為以下倆中情況:

一、資源相關配置改變Activity重新建立

當資源配置相關發生改變時預設情況下Activity會被銷毀并重新建立,執行onPause->onStop->onDestroy,但由于是在異常情況下終止的,系統會調用onSaveInstanceState方法來儲存目前Activity的狀态,onSaveInstanceState會在onStop之前調用,與onPause的調用時機具有不确定性,可能在此之前也有可能在此之後。

如果Activity被重建了,可以在onCreate或onRestoreInstanceState方法中進行資料恢複,倆者的不同是onRestoreInstanceState是在onStart方法之後調用的,官方推薦在onRestoreInstanceState中進行資料恢複。此外,在onCreate中擷取的Bundle需要進行判空處理,否則可能出現空指針異常,而在onRestoreInstanceState中則不需要,因為如果有資料需要恢複那麼此方法中的Bundle必不為空,如果為空則不會走此方法。 

儲存資料:

@Override
    protected void onSaveInstanceState(Bundle outState) {
        outState.putString("key", "saveTest");
        super.onSaveInstanceState(outState);
        Log.d(TAG, "onSaveInstanceState:");
    }           

onCreate中恢複資料:

@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        if (savedInstanceState != null){
            Log.d(TAG, "onCreate:" + savedInstanceState.getString("key"));
        }
    }           

onRestoreInstanceState中恢複資料:

@Override
    protected void onRestoreInstanceState(Bundle savedInstanceState) {
        super.onRestoreInstanceState(savedInstanceState);
        Log.d(TAG, "onRestoreInstanceState:" + savedInstanceState.getString("key"));
    }           

二、資源記憶體不足低優先級Activity被殺死

android中Activity的優先級,可以分為以下3級:

1.前台Activity:可以互動,優先級最高;

2.可見但非前台Activity:比如有彈窗導緻Activity可見但是不可互動;

3.背景Activity。

當系統記憶體不足時,就會按照activity的優先級去殺死目标activity所在的程序,後續會通過onSaveInstanceState和onRestoreInstanceState來進行資料恢複。

       前面提及,Activity由于系統配置改變Activity銷毀後預設會重新建立,但是也可以通過修改指定Activity的configChanges的屬性來指定不需要重新建立。

常用的configChanges屬性有locale、orientation、keyboardHidden,但還需要注意screenSize和smallestScreenSize,具體屬性描述如下:

locale:裝置的本地位置發生改變,一般指切換了語言;

keyboardHidden:鍵盤的可通路性發生了改變,比如使用者調出了鍵盤;

orientation:螢幕方向發生改變,比如旋轉手機螢幕;

screenSize:配合orientation使用,當裝置旋轉時,螢幕尺寸會發生改變,這個選項比較特殊,它和編譯選項有關,當編譯選項中的minSdkVersion和targetSdkVersion都低于13時,此選項不會導緻Activity重新開機,否則會重新開機。

比如旋轉Activity時,當我們的編譯選項中minSdkVersion和targetSdkVersion有一個大于13時,如果configChanges屬性隻設定為“orientation”,則Activity仍然會重新開機,隻有設定為

“orientation|screenSize”時Activity才不會重新開機。

Activity在實際應用中還有很多需要注意的地方,在實際應用中,當應用需要退出時,會執行onPause->onStop->onDestory,然後退出到桌面,再次點選應用圖示時會重新啟動應用,比如執行啟動頁進入首頁面。但是為了更好的使用者體驗,我們需要在點選應用時直接進入上次進入的頁面,那麼有以下倆種方法:

(1)重寫Activity中的onBackPressed方法,代碼如下:

@Override
    public void onBackPressed() {
        Intent intent = new Intent(Intent.ACTION_MAIN);
        intent.addCategory(Intent.CATEGORY_HOME);
        startActivity(intent);
    }           

以上方法通過影響Activity的生命周期實作類似home鍵的效果。

(2)android系統提供的方法:

moveTaskToBack(true);           

此方法的源碼如下所示:

/**
     * Move the task containing this activity to the back of the activity
     * stack.  The activity's order within the task is unchanged.
     *
     * @param nonRoot If false then this only works if the activity is the root
     *                of a task; if true it will work for any activity in
     *                a task.
     *
     * @return If the task was moved (or it was already at the
     *         back) true is returned, else false.
     */
    public boolean moveTaskToBack(boolean nonRoot) {
        try {
            return ActivityManagerNative.getDefault().moveActivityTaskToBack(
                    mToken, nonRoot);
        } catch (RemoteException e) {
            // Empty
        }
        return false;
    }           

通過以上代碼可看出來,如果參數為true的話,此代碼一直生效,如果為false,那麼隻有目前activity在棧底才有效。此方法是将目前activity的棧移到背景,并不會影響在棧的順序。

筆者水準有限,如有錯漏,歡迎指正!