官术网_书友最值得收藏!

1.1 Activity的生命周期全面分析

本節將Activity的生命周期分為兩部分內容,一部分是典型情況下的生命周期,另一部分是異常情況下的生命周期。所謂典型情況下的生命周期,是指在有用戶參與的情況下,Activity所經過的生命周期的改變;而異常情況下的生命周期是指Activity被系統回收或者由于當前設備的Configuration發生改變從而導致Activity被銷毀重建,異常情況下的生命周期的關注點和典型情況下略有不同。

1.1.1 典型情況下的生命周期分析

在正常情況下,Activity會經歷如下生命周期。

(1)onCreate:表示Activity正在被創建,這是生命周期的第一個方法。在這個方法中,我們可以做一些初始化工作,比如調用setContentView去加載界面布局資源、初始化Activity所需數據等。

(2)onRestart:表示Activity正在重新啟動。一般情況下,當當前Activity從不可見重新變為可見狀態時,onRestart就會被調用。這種情形一般是用戶行為所導致的,比如用戶按Home鍵切換到桌面或者用戶打開了一個新的Activity,這時當前的Activity就會暫停,也就是onPause和onStop被執行了,接著用戶又回到了這個Activity,就會出現這種情況。

(3)onStart:表示Activity正在被啟動,即將開始,這時Activity已經可見了,但是還沒有出現在前臺,還無法和用戶交互。這個時候其實可以理解為Activity已經顯示出來了,但是我們還看不到。

(4)onResume:表示Activity已經可見了,并且出現在前臺并開始活動。要注意這個和onStart的對比,onStart和onResume都表示Activity已經可見,但是onStart的時候Activity還在后臺,onResume的時候Activity才顯示到前臺。

(5)onPause:表示Activity正在停止,正常情況下,緊接著onStop就會被調用。在特殊情況下,如果這個時候快速地再回到當前Activity,那么onResume會被調用。筆者的理解是,這種情況屬于極端情況,用戶操作很難重現這一場景。此時可以做一些存儲數據、停止動畫等工作,但是注意不能太耗時,因為這會影響到新Activity的顯示,onPause必須先執行完,新Activity的onResume才會執行。

(6)onStop:表示Activity即將停止,可以做一些稍微重量級的回收工作,同樣不能太耗時。

(7)onDestroy:表示Activity即將被銷毀,這是Activity生命周期中的最后一個回調,在這里,我們可以做一些回收工作和最終的資源釋放。

正常情況下,Activity的常用生命周期就只有上面7個,圖1-1更詳細地描述了Activity各種生命周期的切換過程。

圖1-1 Activity生命周期的切換過程

針對圖1-1,這里再附加一下具體說明,分如下幾種情況。

(1)針對一個特定的Activity,第一次啟動,回調如下:onCreate -> onStart -> onResume。

(2)當用戶打開新的Activity或者切換到桌面的時候,回調如下:onPause -> onStop。這里有一種特殊情況,如果新Activity采用了透明主題,那么當前Activity不會回調onStop。

(3)當用戶再次回到原Activity時,回調如下:onRestart -> onStart -> onResume。

(4)當用戶按back鍵回退時,回調如下:onPause -> onStop -> onDestroy。

(5)當Activity被系統回收后再次打開,生命周期方法回調過程和(1)一樣,注意只是生命周期方法一樣,不代表所有過程都一樣,這個問題在下一節會詳細說明。

(6)從整個生命周期來說,onCreate和onDestroy是配對的,分別標識著Activity的創建和銷毀,并且只可能有一次調用。從Activity是否可見來說,onStart和onStop是配對的,隨著用戶的操作或者設備屏幕的點亮和熄滅,這兩個方法可能被調用多次;從Activity是否在前臺來說,onResume和onPause是配對的,隨著用戶操作或者設備屏幕的點亮和熄滅,這兩個方法可能被調用多次。

這里提出2個問題,不知道大家是否清楚。

問題1:onStart和onResume、onPause和onStop從描述上來看差不多,對我們來說有什么實質的不同呢?

問題2:假設當前Activity為A,如果這時用戶打開一個新Activity B,那么B的onResume和A的onPause哪個先執行呢?

先說第一個問題,從實際使用過程來說,onStart和onResume、onPause和onStop看起來的確差不多,甚至我們可以只保留其中一對,比如只保留onStart和onStop。既然如此,那為什么Android系統還要提供看起來重復的接口呢?根據上面的分析,我們知道,這兩個配對的回調分別表示不同的意義,onStart和onStop是從Activity是否可見這個角度來回調的,而onResume和onPause是從Activity是否位于前臺這個角度來回調的,除了這種區別,在實際使用中沒有其他明顯區別。

第二個問題可以從Android源碼里得到解釋。關于Activity的工作原理在本書后續章節會進行介紹,這里我們先大概了解即可。從Activity的啟動過程來看,我們來看一下系統源碼。Activity的啟動過程的源碼相當復雜,涉及Instrumentation、ActivityThread和ActivityManagerServic(e下面簡稱AMS)。這里不詳細分析這一過程,簡單理解,啟動Activity的請求會由Instrumentation來處理,然后它通過Binder向AMS發請求,AMS內部維護著一個ActivityStack并負責棧內的Activity的狀態同步,AMS通過ActivityThread去同步Activity的狀態從而完成生命周期方法的調用。在ActivityStack中的resumeTopActivity-InnerLocked方法中,有這么一段代碼:

        // We need to start pausing the current activity so the top one
        // can be resumed...
        boolean   dontWaitForPause   =   (next.info.flags&ActivityInfo.FLAG_RESUME_
        WHILE_PAUSING) ! = 0;
        boolean   pausing   =   mStackSupervisor.pauseBackStacks(userLeaving,   true,
        dontWaitForPause);
        if (mResumedActivity ! = null) {
            pausing |= startPausingLocked(userLeaving, false, true, dontWait-
            ForPause);
            if (DEBUG_STATES) Slog.d(TAG, "resumeTopActivityLocked: Pausing " +
            mResumedActivity);
        }

從上述代碼可以看出,在新Activity啟動之前,桟頂的Activity需要先onPause后,新Activity才能啟動。最終,在ActivityStackSupervisor中的realStartActivityLocked方法會調用如下代碼。

        app.thread.scheduleLaunchActivity(new Intent(r.intent), r.appToken,
                System.identityHashCode(r), r.info, new Configuration(mService.
                mConfiguration),
                r.compat, r.task.voiceInteractor, app.repProcState, r.icicle,
                r.persistentState,
                results, newIntents, ! andResume, mService.isNextTransition-
                Forward(),
                profilerInfo);

我們知道,這個app.thread的類型是IApplicationThread,而IApplicationThread的具體實現是ActivityThread中的ApplicationThread。所以,這段代碼實際上調到了ActivityThread的中,即ApplicationThread的scheduleLaunchActivity方法,而scheduleLaunchActivity方法最終會完成新Activity的onCreate、onStart、onResume的調用過程。因此,可以得出結論,是舊Activity先onPause,然后新Activity再啟動。

至于ApplicationThread的scheduleLaunchActivity方法為什么會完成新Activity的onCreate、onStart、onResume的調用過程,請看下面的代碼。scheduleLaunchActivity最終會調用如下方法,而如下方法的確會完成onCreate、onStart、onResume的調用過程。

源碼:ActivityThread# handleLaunchActivity

        private  void  handleLaunchActivity(ActivityClientRecord  r,  Intent  custom-
        Intent) {
            // If we are getting ready to gc after going to the background, well
            // we are back active so skip it.
            unscheduleGcIdler();
            mSomeActivitiesChanged = true;

            if (r.profilerInfo ! = null) {
                mProfiler.setProfiler(r.profilerInfo);
                mProfiler.startProfiling();
            }

            // Make sure we are running with the most recent config.
            handleConfigurationChanged(null, null);

            if (localLOGV) Slog.v(
                TAG, "Handling launch of " + r);

            //這里新Activity被創建出來,其onCreate和onStart會被調用
        Activity a = performLaunchActivity(r, customIntent);

            if (a ! = null) {
                r.createdConfig = new Configuration(mConfiguration);
                Bundle oldState = r.state;
                //這里新Activity的onResume會被調用
                handleResumeActivity(r.token, false, r.isForward,
                        !r.activity.mFinished && ! r.startsNotResumed);
            //省略
        }

從上面的分析可以看出,當新啟動一個Activity的時候,舊Activity的onPause會先執行,然后才會啟動新的Activity。到底是不是這樣呢?我們寫個例子驗證一下,如下是2個Activity的代碼,在MainActivity中單擊按鈕可以跳轉到SecondActivity,同時為了分析我們的問題,在生命周期方法中打印出了日志,通過日志我們就能看出它們的調用順序。

代碼:MainActivity.java

        public class MainActivity extends Activity {

            private static final String TAG = "MainActivity";
        //省略
        @Override
        protected void onPause() {
            super.onPause();
            Log.d(TAG, "onPause");
        }

        @Override
        protected void onStop() {
            super.onStop();
            Log.d(TAG, "onStop");
        }
    }

代碼:SecondActivity.java

    public class SecondActivity extends Activity {
        private static final String TAG = "SecondActivity";

        @Override
        protected void onCreate(Bundle savedInstanceState) {
          super.onCreate(savedInstanceState);
          setContentView(R.layout.activity_second);
          Log.d(TAG, "onCreate");
        }

        @Override
        protected void onStart() {
          super.onStart();
          Log.d(TAG, "onStart");
        }

        @Override
        protected void onResume() {
          super.onResume();
          Log.d(TAG, "onResume");
        }
    }

我們來看一下log,是不是和我們上面分析的一樣,如圖1-2所示。

圖1-2 Activity生命周期方法的回調順序

通過圖1-2可以發現,舊Activity的onPause先調用,然后新Activity才啟動,這也證實了我們上面的分析過程。也許有人會問,你只是分析了Android5.0的源碼,你怎么知道所有版本的源碼都是相同邏輯呢?關于這個問題,我們的確不大可能把所有版本的源碼都分析一遍,但是作為Android運行過程的基本機制,隨著版本的更新并不會有大的調整,因為Android系統也需要兼容性,不能說在不同版本上同一個運行機制有著截然不同的表現。關于這一點我們需要把握一個度,就是對于Android運行的基本機制在不同Android版本上具有延續性。從另一個角度來說,Android官方文檔對onPause的解釋有這么一句:不能在onPause中做重量級的操作,因為必須onPause執行完成以后新Activity才能Resume,從這一點也能間接證明我們的結論。通過分析這個問題,我們知道onPause和onStop都不能執行耗時的操作,尤其是onPause,這也意味著,我們應當盡量在onStop中做操作,從而使得新Activity盡快顯示出來并切換到前臺。

1.1.2 異常情況下的生命周期分析

上一節我們分析了典型情況下Activity的生命周期,本節我們接著分析Activity在異常情況下的生命周期。我們知道,Activity除了受用戶操作所導致的正常的生命周期方法調度,還有一些異常情況,比如當資源相關的系統配置發生改變以及系統內存不足時,Activity就可能被殺死。下面我們具體分析這兩種情況。

1.情況1:資源相關的系統配置發生改變導致Activity被殺死并重新創建

理解這個問題,我們首先要對系統的資源加載機制有一定了解,這里不詳細分析系統的資源加載機制,只是簡單說明一下。拿最簡單的圖片來說,當我們把一張圖片放在drawable目錄后,就可以通過Resources去獲取這張圖片。同時為了兼容不同的設備,我們可能還需要在其他一些目錄放置不同的圖片,比如drawable-mdpi、drawable-hdpi、drawable-land等。這樣,當應用程序啟動時,系統就會根據當前設備的情況去加載合適的Resources資源,比如說橫屏手機和豎屏手機會拿到兩張不同的圖片(設定了landscape或者portrait狀態下的圖片)。比如說當前Activity處于豎屏狀態,如果突然旋轉屏幕,由于系統配置發生了改變,在默認情況下,Activity就會被銷毀并且重新創建,當然我們也可以阻止系統重新創建我們的Activity。在默認情況下,如果我們的Activity不做特殊處理,那么當系統配置發生改變后,Activity就會被銷毀并重新創建,其生命周期如圖1-3所示。

圖1-3 異常情況下Activity的重建過程

當系統配置發生改變后,Activity會被銷毀,其onPause、onStop、onDestroy均會被調用,同時由于Activity是在異常情況下終止的,系統會調用onSaveInstanceState來保存當前Activity的狀態。這個方法的調用時機是在onStop之前,它和onPause沒有既定的時序關系,它既可能在onPause之前調用,也可能在onPause之后調用。需要強調的一點是,這個方法只會出現在Activity被異常終止的情況下,正常情況下系統不會回調這個方法。當Activity被重新創建后,系統會調用onRestoreInstanceState,并且把Activity銷毀時onSaveInstanceState方法所保存的Bundle對象作為參數同時傳遞給onRestoreInstanceState和onCreate方法。因此,我們可以通過onRestoreInstanceState和onCreate方法來判斷Activity是否被重建了,如果被重建了,那么我們就可以取出之前保存的數據并恢復,從時序上來說,onRestoreInstanceState的調用時機在onStart之后。

同時,我們要知道,在onSaveInstanceState和onRestoreInstanceState方法中,系統自動為我們做了一定的恢復工作。當Activity在異常情況下需要重新創建時,系統會默認為我們保存當前Activity的視圖結構,并且在Activity重啟后為我們恢復這些數據,比如文本框中用戶輸入的數據、ListView滾動的位置等,這些View相關的狀態系統都能夠默認為我們恢復。具體針對某一個特定的View系統能為我們恢復哪些數據,我們可以查看View的源碼。和Activity一樣,每個View都有onSaveInstanceState和onRestoreInstanceState這兩個方法,看一下它們的具體實現,就能知道系統能夠自動為每個View恢復哪些數據。

關于保存和恢復View層次結構,系統的工作流程是這樣的:首先Activity被意外終止時,Activity會調用onSaveInstanceState去保存數據,然后Activity會委托Window去保存數據,接著Window再委托它上面的頂級容器去保存數據。頂層容器是一個ViewGroup,一般來說它很可能是DecorView。最后頂層容器再去一一通知它的子元素來保存數據,這樣整個數據保存過程就完成了??梢园l現,這是一種典型的委托思想,上層委托下層、父容器委托子元素去處理一件事情,這種思想在Android中有很多應用,比如View的繪制過程、事件分發等都是采用類似的思想。至于數據恢復過程也是類似的,這里就不再重復介紹了。接下來舉個例子,拿TextView來說,我們分析一下它到底保存了哪些數據。

源碼:TextView# onSaveInstanceState

        @Override
        public Parcelable onSaveInstanceState() {
            Parcelable superState = super.onSaveInstanceState();

            // Save state if we are forced to
            boolean save = mFreezesText;
            int start = 0;
            int end = 0;

            if (mText ! = null) {
                start = getSelectionStart();
                end = getSelectionEnd();
                if (start >= 0 || end >= 0) {
                    // Or save state if there is a selection
                    save = true;
                }
            }

            if (save) {
                SavedState ss = new SavedState(superState);
                // XXX Should also save the current scroll position!
                ss.selStart = start;
                ss.selEnd = end;

                if (mText instanceof Spanned) {
                          Spannable sp = new SpannableStringBuilder(mText);

                          if (mEditor ! = null) {
                              removeMisspelledSpans(sp);
                              sp.removeSpan(mEditor.mSuggestionRangeSpan);
                          }

                          ss.text = sp;
                      } else {
                          ss.text = mText.toString();
                      }

                      if (isFocused() && start >= 0 && end >= 0) {
                          ss.frozenWithFocus = true;
                      }

                      ss.error = getError();

                      return ss;
                  }

                  return superState;
              }

從上述源碼可以很容易看出,TextView保存了自己的文本選中狀態和文本內容,并且通過查看其onRestoreInstanceState方法的源碼,可以發現它的確恢復了這些數據,具體源碼就不再貼出了,讀者可以去看看源碼。下面我們看一個實際的例子,來對比一下Activity正常終止和異常終止的不同,同時驗證系統的數據恢復能力。為了方便,我們選擇旋轉屏幕來異常終止Activity,如圖1-4所示。

圖1-4 Activity旋轉屏幕后數據的保存和恢復

通過圖1-4可以看出,在我們選擇屏幕以后,Activity被銷毀后重新創建,我們輸入的文本“這是測試文本”被正確地還原,這說明系統的確能夠自動地做一些View層次結構方面的數據存儲和恢復。下面再用一個例子,來驗證我們自己做數據存儲和恢復的情況,代碼如下:

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

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

        @Override
        protected void onRestoreInstanceState(Bundle savedInstanceState) {
            super.onRestoreInstanceState(savedInstanceState);
            String test = savedInstanceState.getString("extra_test");
            Log.d(TAG, "[onRestoreInstanceState]restore extra_test:" + test);
        }

上面的代碼很簡單,首先我們在onSaveInstanceState中存儲一個字符串,然后當Activity被銷毀并重新創建后,我們再去獲取之前存儲的字符串。接收的位置可以選擇onRestoreInstanceState或者onCreate,二者的區別是:onRestoreInstanceState一旦被調用,其參數Bundle savedInstanceState一定是有值的,我們不用額外地判斷是否為空;但是onCreate不行,onCreate如果是正常啟動的話,其參數Bundle savedInstanceState為null,所以必須要額外判斷。這兩個方法我們選擇任意一個都可以進行數據恢復,但是官方文檔的建議是采用onRestoreInstanceState去恢復數據。下面我們看一下運行的日志,如圖1-5所示。

圖1-5 系統日志

如圖1-5所示,Activity被銷毀了以后調用了onSaveInstanceState來保存數據,重新創建以后在onCreate和onRestoreInstanceState中都能夠正確地恢復我們之前存儲的字符串。這個例子很好地證明了上面我們的分析結論。針對onSaveInstanceState方法還有一點需要說明,那就是系統只會在Activity即將被銷毀并且有機會重新顯示的情況下才會去調用它??紤]這么一種情況,當Activity正常銷毀的時候,系統不會調用onSaveInstanceState,因為被銷毀的Activity不可能再次被顯示。這句話不好理解,但是我們可以對比一下旋轉屏幕所造成的Activity異常銷毀,這個過程和正常停止Activity是不一樣的,因為旋轉屏幕后,Activity被銷毀的同時會立刻創建新的Activity實例,這個時候Activity有機會再次立刻展示,所以系統要進行數據存儲。這里可以簡單地這么理解,系統只在Activity異常終止的時候才會調用onSaveInstanceState和onRestoreInstanceState來存儲和恢復數據,其他情況不會觸發這個過程。

2.情況2:資源內存不足導致低優先級的Activity被殺死

這種情況我們不好模擬,但是其數據存儲和恢復過程和情況1完全一致。這里我們描述一下Activity的優先級情況。Activity按照優先級從高到低,可以分為如下三種:

(1)前臺Activity——正在和用戶交互的Activity,優先級最高。

(2)可見但非前臺Activity——比如Activity中彈出了一個對話框,導致Activity可見但是位于后臺無法和用戶直接交互。

(3)后臺Activity——已經被暫停的Activity,比如執行了onStop,優先級最低。

當系統內存不足時,系統就會按照上述優先級去殺死目標Activity所在的進程,并在后續通過onSaveInstanceState和onRestoreInstanceState來存儲和恢復數數據。如果一個進程中沒有四大組件在執行,那么這個進程將很快被系統殺死,因此,一些后臺工作不適合脫離四大組件而獨自運行在后臺中,這樣進程很容易被殺死。比較好的方法是將后臺工作放入Service中從而保證進程有一定的優先級,這樣就不會輕易地被系統殺死。

上面分析了系統的數據存儲和恢復機制,我們知道,當系統配置發生改變后,Activity會被重新創建,那么有沒有辦法不重新創建呢?答案是有的,接下來我們就來分析這個問題。系統配置中有很多內容,如果當某項內容發生改變后,我們不想系統重新創建Activity,可以給Activity指定configChanges屬性。比如不想讓Activity在屏幕旋轉的時候重新創建,就可以給configChanges屬性添加orientation這個值,如下所示。

        android:configChanges="orientation"

如果我們想指定多個值,可以用“|”連接起來,比如android:configChanges="orientation|keyboardHidden"。系統配置中所含的項目是非常多的,下面介紹每個項目的含義,如表1-1所示。

表1-1 configChanges的項目和含義

從表1-1可以知道,如果我們沒有在Activity的configChanges屬性中指定該選項的話,當配置發生改變后就會導致Activity重新創建。上面表格中的項目很多,但是我們常用的只有locale、orientation和keyboardHidden這三個選項,其他很少使用。需要注意的是screenSize和smallestScreenSize,它們兩個比較特殊,它們的行為和編譯選項有關,但和運行環境無關。下面我們再看一個demo,看看當我們指定了configChanges屬性后,Activity是否真的不會重新創建了。我們所要修改的代碼很簡單,只需要在AndroidMenifest.xml中加入Activity的聲明即可,代碼如下:

        <uses-sdk
            android:minSdkVersion="8"
            android:targetSdkVersion="19" />
        <activity
            android:name="com.ryg.chapter_1.MainActivity"
            android:configChanges="orientation|screenSize"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

        @Override
        public void onConfigurationChanged(Configuration newConfig) {
            super.onConfigurationChanged(newConfig);
            Log.d(TAG, "onConfigurationChanged, newOrientation:" + newConfig.
            orientation);
        }

需要說明的是,由于編譯時筆者指定的minSdkVersion和targetSdkVersion有一個大于13,所以為了防止旋轉屏幕時Activity重啟,除了orientation,我們還要加上screenSize,原因在上面的表格里已經說明了。其他代碼還是不變,運行后看看log,如圖1-6所示。

圖1-6 系統日志

由上面的日志可見,Activity的確沒有重新創建,并且也沒有調用onSaveInstanceState和onRestoreInstanceState來存儲和恢復數據,取而代之的是系統調用了Activity的onConfigurationChanged方法,這個時候我們就可以做一些自己的特殊處理了。

主站蜘蛛池模板: 泰州市| 天门市| 勃利县| 拉孜县| 古田县| 郴州市| 怀远县| 高淳县| 综艺| 东乡族自治县| 深水埗区| 大姚县| 遂平县| 桐庐县| 阿勒泰市| 博罗县| 阿鲁科尔沁旗| 凉城县| 碌曲县| 武功县| 南宫市| 西峡县| 伊吾县| 十堰市| 毕节市| 枣强县| 金堂县| 巍山| 宁阳县| 仪陇县| 大石桥市| 宁津县| 沐川县| 贵阳市| 通化市| 自治县| 巴里| 惠州市| 南华县| 澄江县| 当雄县|