- Android開發藝術探索
- 任玉剛
- 6006字
- 2019-01-10 17:37:34
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方法,這個時候我們就可以做一些自己的特殊處理了。