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)
針對上圖,以下給出具體的說明,大概有以下幾種情況:
(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的棧移到背景,并不會影響在棧的順序。
筆者水準有限,如有錯漏,歡迎指正!