Activity生命周期里的「隱秘」

Activity作為Android四大組件的之首,可能是與開發者最「親密」的組件,因此介紹Activity生命周期的相關文章也多如牛毛。然而正是這種經常接觸會讓開發者產生一種錯覺,對Activity生命周期這種基礎性的東西已經了如執掌了,可是你確定真的了解Activity嗎?不如我們先來看幾個問題:

  • 打開一個Activity,然後點擊返回鍵,生命周期以怎樣的順序被回調?
  • 通常生命周期中的onPause()後會緊接著進入onStop(),什麼情況下Activity會保持在onPause()狀態?
  • 從Activity A打開一個Activity B時,這兩個Activity生命周期的調用時序你可以寫出來嗎?
  • 在onCreate()中調用finish(),生命周期回調是怎樣的?
  • onPostCreate()這個回調有什麼應用場景,發生在哪個生命周期階段?
  • 有哪些異常的生命周期狀態?

如果你不能確定一些問題的答案,那麼本文可能會對你進一步了解Activity生命周期有所幫助。深入掌握Activity,了解一些它生命周期里的「隱秘」可以讓我們在開發中有意識地避開一些可能導致bug或性能問題。

從一張圖開始

這是Android Developer官方文檔中經典的Activity生命周期圖解,可能很多人都看見過這張看似簡單的圖。從這張圖中,我們可以得到什麼信息呢?

典型的生命周期

最直白的,我們可以從圖中解讀出,打開一個Activity典型生命周期的回調順序為:onCreate()->onStart()->onResume();Activity被銷毀時的回調順序為:onPaused()->onStop()->onDestory()。

讀到這裡,不知道你會不會有一個疑問,假如一個Activity從創建到銷毀,是不是一定會得到這些全部生命周期的回調?

並不一定,在兩種情況下,Activity就不會得到完整的生命周期回調:

  • 在onCreate()中調用Activity的finish()方法,這時候會直接進入onDestory(),而不會產生其他中間過程的回調,即onCreate()->onDestory()。這個我們可以應用到類似跳轉功能的Activity中,在onCreate()進行邏輯處理後,打開目標Activity,然後finish()。這種情況下,用戶不會看到這個Activity,這個Activity充當一個分發者的角色。
  • 某個生命周期發生了crash,如在onCreate()中就發生了crash,就不會有下面生命周期的回調了。

onPause()

這個生命周期值得注意,圖片在它的下面標記著「Partially Visible」,翻譯過來字面上的意思是部分可見。如果只這樣理解的話,從Activity中彈出了一個對話框會遮擋住一部分Activity,那會不會讓Activity進入onPause()狀態呢?

可能你會根據經驗猜測,彈出對話框不會觸發Activity的生命周期,很棒,經實際的測試確實也是如此。這說明「部分可見」並不滿足所有的情況,那麼圖中的"Partially Visible"有怎樣的限制呢,仔細閱讀官方文檔後,發現下面有一句補充說明:「the activity is partially obscured by another activity」。翻譯過來就是,只有當一個Activity被另一個Activity部分遮擋時,會觸發onPause()回調。那麼什麼情況會產生「部分遮擋」的狀態呢?有兩種情況會形成部分遮擋的狀態:

  • 遮蓋在它上面的Activity為非全屏狀態
  • 遮蓋在它上面的Activity有透明顏色的主題

值得一提地是,在Android N上出現了一種新的觸髮狀態:多窗口模式下,這時一個Activity如果不是和用戶最近交互的,即使沒有被部分遮蓋,依然會處於onPause()狀態

從Activity A 打開Activity B

下圖為由Activity A創建並打開Activity B,兩個Activity生命周期的回調時序圖。

可以看到,Activity A的onPause()可以理解,但是為什麼它的onStop()生命周期會出現在Activity B的onResume()之後呢?

我們不妨通過上面那張經典生命周期圖來理解:創建一個新的Activity,自然需要將其焦點由A轉移到B,這時A就要暫停,開始執行B的代碼;一個Activity的形成需要經歷一個從無到部分出現再到全部出現的過程,因此B最初會部分遮擋A,直到B的onResume()執行完畢,B獲得了和用戶交互的能力,並且完全遮擋在A的上面,此時A就會徹底失去焦點,進入onStop()狀態。

通過這個圖,我們還可以挖掘出一個值得注意的信息點:不要在onPause()中做CPU密集型操作(如文件讀寫),否則可能會影響下一步的操作(Activity B的創建和顯示)。

值得注意的非典型生命周期

onSaveInstanceState()和onRestoreInstanceState()

出現條件

這兩個生命周期只是在Activity被異常終止時成對出現(6.0以上在動態許可權申請時也會出現)。Activity的異常終止大概有以下兩種情況:

  • 系統配置發生改變
  • 內存不足,優先順序低的Activity資源被回收

其中系統配置改變是指系統需要重新載入一些資源以適應某些配置的改變,如旋轉屏幕、語言或地區改變、鍵盤改變等。當發生這種改變的時候,系統會重新創建這個Activity,在銷毀和重新創建的過程中,這兩個生命周期將會被回調。在這種情況下,系統會自動保存當前view的一些狀態,然後在重新創建時恢複數據。當然,我們也可以通過onSaveInstanceState()做一些自定義的保存。

過濾不想監聽的配置變化

如果我們不想讓一些配置變化影響我們的頁面,我們可以通過android:configChanges標籤來過濾一些我們不想監聽的配置變化。值得注意的是,這個標籤的意思並不是設置需要監聽哪些配置的改變,而是過濾不想監聽的改變。比如,「屏幕旋轉」時系統需要重新載入res中一些資源,因此默認會觸發Activity的重新創建,如果我們不想讓這種變化對我們的Activity產生影響,就可以通過設置android:configChanges="orientation | screenSize"過濾屏幕旋轉事件,因為屏幕旋轉不但涉及到屏幕朝向的改變,還會改變屏幕展示的尺寸,因此我們需要同時過濾這兩個標籤。

時序

onSaveInstance()發生在onDestory()之前,與onStop()和onPause()沒有固定的時序;如下圖:

值得一提的是,系統從異常狀態下恢復時,同樣可以從onCreate()的參數Bundle中恢複數據,但是需要判空,但是如果收到了onRestoreInstanceState()回調,那麼其參數中的bundle一定是非空的,因為只有onSaveInstanceState()中成功保存了數據,重新創建的生命周期才會收到onRestoreInstanceState()的回調。

onAttachedToWindow()

發生在onResume()和onDraw()之間,表示所在Activity的view已經被載入到window中了,但還沒還開始渲染,我們可以在這個回調中獲取正確的view和窗體大小,也可以更改窗體參數等。與之對應的是onDetachedFromWindow()。

onPostCreate()

這個回調發生在onStart()之後,onResume()之前,此時onCreate()已經執行完了,因此我們可以在其中做一些依賴於onCreate()中狀態的操作;有時還會在這個函數裡面做一些優先順序稍低的事情,比如側邊欄的初始化,因為側邊欄的特性是需要進一步的用戶交互來展現,因此我們可以減緩它初始化的優先順序。對於onPostCreate()的正確使用,可以使代碼層次化更清晰,結構更合理。

onLowMemory()

該生命周期出現在當系統內存不足的,低優先順序的Activity的資源即將被回收時收到的回調,在這個回調中,我們可以做一些數據持久化工作、保存一些關鍵狀態。在執行完這個生命周期後,系統將立即執行gc。

結語

Activity的生命周期是不論是在面試還是實際開發中都是重中之重,當然Activity生命周期只是Activity知識體系的一部分,想更深入的去了解Activity生命周期在源碼中是何時被觸發的就需要對ActivityManager、ServiceManager和Android的核心通信機制Binder進行更深一步的學習了。
推薦閱讀:

APP開發標準流程
為什麼寫這本書《Mac App開發基礎教程》
移動端的 touch 事件處理
Hey,在忙著更新 App?
翻譯 | 如何構建聊天界面

TAG:Android | 移動開發 | Android應用 |