Android 基礎 -- Activity 生命週期實踐總結

weixin_33860722發表於2016-07-30

Activity / Fragment 的生命週期是每個 Android 開發者最最基礎的知識點。所以特別有必要自己整理一番。總看別人部落格和書上的死知識,還不如自己動手實踐,然後輸出要印象深刻,理解透徹。

Activity 生命週期

正常情況下的生命週期分析

217525-a677a66007219fa3.jpg
  1. 針對一個特定的 Activity ,第一次啟動,回撥如下:onCreate —-> onStart —-> onResume

Log 日誌
D/KLog: (MainActivity.java:19) onCreate
D/KLog: (MainActivity.java:44) onStart
D/KLog: (MainActivity.java:62) onResume

  1. 切換回到桌面的時候,回撥如下:onPause —-> onStop

Log 日誌
D/KLog: (MainActivity.java:50) onPause
D/KLog: (MainActivity.java:68) onStop

  1. Back 鍵退出的話,最後會 onDestroy

  2. 啟動一個新的 Activity , 我們看看兩個 Activity 的生命週期:

Log 日誌
D/KLog: (MainActivity.java:50) onPause
D/KLog: (OtherActivity.java:25) onCreate
D/KLog: (OtherActivity.java:31) onStart
D/KLog: (OtherActivity.java:49) onResume
D/KLog: (MainActivity.java:68) onStop
可以得到順序是:onPause(A) —-> onCreate(B) —-> onStart(B) —-> onResume(B) —-> onStop(A)

  1. 這個時候我們 Back 回到第一個 Activity 時發生的回撥:

Log 日誌
D/KLog: (OtherActivity.java:37) onPause
D/KLog: (MainActivity.java:56) onRestart
D/KLog: (MainActivity.java:44) onStart
D/KLog: (MainActivity.java:62) onResume
D/KLog: (OtherActivity.java:55) onStop
D/KLog: (OtherActivity.java:61) onDestroy
可以得到順序是: onPause(B) —-> onRestart(A) —-> onStart(A) —-> onResume(A) —-> onStop(B) —-> onDestroy(B)

  1. 如果我在 B Activity 中的 onCreate 回撥中直接 finish()

Log 日誌
D/KLog: (MainActivity.java:50) onPause
D/KLog: (OtherActivity.java:25) onCreate
D/KLog: (MainActivity.java:62) onResume
D/KLog: (OtherActivity.java:62) onDestroy
我們發現 B Activity 只會執行 onCreateonDestroy

  1. 接下來我們啟動一個特殊的 Activity (半透明或者對話方塊樣式)到關閉它:

Log 日誌
D/MainActivity: onPause
D/DialogActivity: onCreate
D/DialogActivity: onStart
D/DialogActivity: onResume
D/DialogActivity: onPause
D/MainActivity: onResume
D/DialogActivity: onStop
D/DialogActivity: onDestroy

在正常使用應用的過程中,前臺 Activity 有時會被其他導致 Activity 暫停的可視元件阻擋。 例如,當半透明 Activity 開啟時(比如對話方塊樣式中的 Activity ),上一個 Activity 會暫停。 只要 Activity 仍然部分可見但目前又未處於焦點之中,它會一直暫停。

<div class="tip">
問題:如果是啟動一個普通的 Dialog ,Activity 的會執行 onPause 嗎?
</div>

答案是不會的,我們可以這樣理解,普通的 dialog 是依附在本 Activity 的,相當於是一個整體,所以它本身的焦點也是屬於 Activity 的。故不會呼叫 onPause 。

異常狀態下的生命週期

onSaveInstanceState 方法只會出現在 Activity 被異常終止的情況下,它的呼叫時機是在 onStop 之前,它和 onPause 方法沒有既定的時序關係,可能在它之前,也可能在它之後。當 Activity 被重新建立的時候, onRestoreInstanceState 會被回撥,它的呼叫時機是 onStart 之後。
系統只會在 Activity 即將被銷燬並且有機會重新顯示的情況下才會去呼叫 onSaveInstanceState 方法。
Activity 在異常情況下需要重新建立時,系統會預設為我們儲存當前 Activity 的檢視結構,並且在 Activity 重啟後為我們恢復這些資料,比如文字框中使用者輸入的資料、listview 滾動的位置等,這些 view 相關的狀態系統都會預設為我們恢復。具體針對某一個 view 系統能為我們恢復哪些資料可以檢視 view 的原始碼中的 onSaveInstanceStateonRestoreInstanceState 方法。

Demo 程式碼:

@Override
protected void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    KLog.d(getClass().getSimpleName(),"onSaveInstanceState");
    outState.putString(STATE, "test");
}

@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
    super.onRestoreInstanceState(savedInstanceState);
    KLog.d(getClass().getSimpleName(),"[onRestoreInstanceState]: " + savedInstanceState.getString(STATE));
}

為了方便我們旋轉下螢幕來異常終止 Activity :

Log 日誌
D/MainActivity: onPause
D/MainActivity: onSaveInstanceState
D/MainActivity: onStop
D/MainActivity: onDestroy
D/MainActivity: onCreate
D/MainActivity: onStart
D/MainActivity: [onRestoreInstanceState]: test
D/MainActivity: onResume

摘自 Android 開發者藝術探索 一書:

關於儲存和恢復 View 的層次結構,系統工作流程是: Activity 異常終止, Activity 呼叫 onSaveInstanceState 去儲存資料,然後 Activity 會委託 Windows 去儲存資料,接著 Window 再委託它上面的頂層容器去儲存資料。頂層容器是一個 ViewGroup ,一般來說它很可能是 DectorView ,最後頂層容器再去通知它的子元素儲存資料。(這是一種委託思想,上層委託下層,父容器委託子元素去處理事情,如 View 的繪製過程,事件分發都是採用類似的思想)

Fragment

普通的 Fragment

從圖可以看出,Fragment 生命週期大部分的狀態與 Activity 相似,特殊的是

  • onAttach() —— 當 Fragment 被加入到 Activity 時呼叫(在這個方法中可以獲得所在的 Activity ).
  • onCreateView() —— 當 Activity 要得到 Fragment 的 Layout 時,呼叫此方法,Fragment 在其中建立自己的 Layout (介面)。
  • onActivityCreated() —— 當 Activity 的 onCreated() 方法返回後呼叫此方法
  • onDestroyView() —— 當 Fragment 中的檢視被移除的時候,呼叫這個方法。
  • onDetach() —— 當 Fragment 和 Activity 分離的時候,呼叫這個方法。

ViewPager 中的 Fragment

我們開發中經常會用到 ViewPager + Fragment 組合的形式來完成特定的需求。本身 Fragment 生命週期就比 Activity 要複雜很多,當它在 ViewPager 中又是怎麼回撥呢?

我先給 ViewPager 加入三個 Fragment:

viewPager = (ViewPager) findViewById(R.id.viewpager);
fragmentList.add(new OneTextFragment());
fragmentList.add(new TwoTextFragment());
fragmentList.add(new ThreeTextFragment());
viewPager.setAdapter(new FtAdapter(getSupportFragmentManager(), fragmentList));

啟動這個 Activity 的日誌如下:

D/ViewPagerHostActivity: onCreate
D/ViewPagerHostActivity: onStart
D/ViewPagerHostActivity: onResume
D/OneTextFragment: onAttach
D/OneTextFragment: onCreate
D/TwoTextFragment: onAttach
D/TwoTextFragment: onCreate
D/TwoTextFragment: onActivityCreated
D/OneTextFragment: onActivityCreated
D/OneTextFragment: onStart
D/OneTextFragment: onResume
D/TwoTextFragment: onStart
D/TwoTextFragment: onResume

我們發現啟動後,有兩個 Fragment(one,two) 被建立,為什麼會建立兩個?這個問題我們留著後面說。

當 Activity 進入後臺:

D/ViewPagerHostActivity: onPause
D/ViewPagerHostActivity: onSaveInstanceState
D/TwoTextFragment: onStop
D/OneTextFragment: onStop
D/ViewPagerHostActivity: onStop

當 Activity 返回前臺:

D/ViewPagerHostActivity: onRestart
D/TwoTextFragment: onStart
D/OneTextFragment: onStart
D/ViewPagerHostActivity: onStart
D/ViewPagerHostActivity: onResume
D/TwoTextFragment: onResume
D/OneTextFragment: onResume

當 Activity 銷燬:

D/ViewPagerHostActivity: onPause
D/TwoTextFragment: onStop
D/OneTextFragment: onStop
D/ViewPagerHostActivity: onStop
D/TwoTextFragment: onDestroyView
D/TwoTextFragment: onDestroy
D/TwoTextFragment: onDetach
D/OneTextFragment: onDestroyView
D/OneTextFragment: onDestroy
D/OneTextFragment: onDetach
D/ViewPagerHostActivity: onDestroy

滑動一頁:

D/ThreeTextFragment: onAttach
D/ThreeTextFragment: onCreate
D/ThreeTextFragment: onActivityCreated
D/ThreeTextFragment: onStart
D/ThreeTextFragment: onResume

當前顯示的頁面是 TwoTextFragment 然後 ThreeTextFragment 也已經建立好了。

再滑動一頁:

D/OneTextFragment: onStop
D/OneTextFragment: onDestroyView

當前顯示的頁面是 ThreeTextFragment ,我們發現 OneTextFragment 已經銷燬。

我們可以得到預設狀態下的 ViewPager 會快取 1 個 Fragment,相當於有兩個 Fragment 是建立狀態。
當我們增加一行程式碼:

viewPager.setOffscreenPageLimit(2);

當 Activity 建立時的生命週期:

D/ViewPagerHostActivity: onCreate
D/ViewPagerHostActivity: onStart
D/ViewPagerHostActivity: onResume
D/OneTextFragment: onAttach
D/OneTextFragment: onCreate
D/TwoTextFragment: onAttach
D/TwoTextFragment: onCreate
D/ThreeTextFragment: onAttach
D/ThreeTextFragment: onCreate
D/TwoTextFragment: onActivityCreated
D/OneTextFragment: onActivityCreated
D/OneTextFragment: onStart
D/OneTextFragment: onResume
D/TwoTextFragment: onStart
D/TwoTextFragment: onResume
D/ThreeTextFragment: onStart
D/ThreeTextFragment: onResume

三個 Fragment 都建立好了,並且左右切換不會走任何生命週期(雖然是廢話)。

setOffscreenPageLimit 原始碼:

public void setOffscreenPageLimit(int limit) {
    if (limit < DEFAULT_OFFSCREEN_PAGES) {
        Log.w(TAG, "Requested offscreen page limit " + limit + " too small; defaulting to " +
                DEFAULT_OFFSCREEN_PAGES);
        limit = DEFAULT_OFFSCREEN_PAGES;
    }
    if (limit != mOffscreenPageLimit) {
        mOffscreenPageLimit = limit;
        populate();
    }
}

通過原始碼我們可以知道,ViewPager 的快取的預設值和最小值是 1

啟動模式

Activity 的四種啟動模式

  • Standard:標準模式,一呼叫 startActivity() 方法就會產生一個新的例項。

  • SingleTop: 來了 intent, 每次都建立新的例項,僅一個例外:當棧頂的activity 恰恰就是該activity的例項(即需要建立的例項)時,不再建立新例項。這解決了棧頂複用問題

  • SingleTask: 來了 intent 後,檢查棧中是否存在該 activity的例項,如果存在就把 intent 傳送給它,否則就建立一個新的該activity的例項,放入一個新的 task 棧的棧底。肯定位於一個 task 的棧底,而且棧中只能有它一個該 activity 例項,但允許其他 activity 加入該棧。解決了在一個 task 中共享一個 activity。

  • SingleInstance: 這個跟 SingleTask 基本上是一樣,只有一個區別:在這個模式下的Activity例項所處的task中,只能有這個activity例項,不能有其他的例項。一旦該模式的activity的例項已經存在於某個棧中,任何應用在啟用該activity時都會重用該棧中的例項,解決了多個task共享一個 activity。

這些啟動模式可以在功能清單檔案 AndroidManifest.xml 中進行設定,中的 launchMode 屬性。

具體實踐

  • SingleTop 棧頂複用模式
<activity android:name=".dLaunchChapter.OneActivity"
    android:launchMode="singleTop"/>

我們在清單裡先給 OneActivity 啟動模式設定為 singleTop ,然後程式碼啟動活動的順序為 One --> One,反覆點選多次,然後我們看看棧內情況。

adb 命令 :dumpsys activity | grep -i run

root@vbox86p:/ # dumpsys activity | grep -i run
    Running activities (most recent first):
        Run #1: ActivityRecord{23e3b5b u0 com.hugo.demo.activitydemo/.dLaunchChapter.OneActivity t595}
        Run #0: ActivityRecord{1a2c6f3 u0 com.hugo.demo.activitydemo/.LaunchActivity t595}

該啟動模式下並且 OneActivity 在棧頂所以不會建立新的例項,其生命週期呼叫 onPause —-> onNewIntent —-> onResume

  • SingleTask 棧內複用模式

修改 OneActivity 的啟動模式為 SingleTask ,然後我們程式碼啟動的順序為 One —-> Two —-> One,接了下看看棧內情況:

One —-> Two 我們記錄下當前的 Activity 棧:

Running activities (most recent first):
        Run #2: ActivityRecord{1e8701b7 u0 com.hugo.demo.activitydemo/.dLaunchChapter.TwoActivity t632}
        Run #1: ActivityRecord{39e11719 u0 com.hugo.demo.activitydemo/.dLaunchChapter.OneActivity t632}

接下來我們執行 Two —-> One ,當前 Activity 棧資訊:

Running activities (most recent first):
       Run #1: ActivityRecord{39e11719 u0 com.hugo.demo.activitydemo/.dLaunchChapter.OneActivity t632}

當 TwoActivity 啟動 OneActivity(SingleTask) 的時候,堆疊資訊裡只剩下了 OneActivity 並且和第一次記憶體資訊 39e11719 相同,所以確實是複用了沒有新建例項,接下來我們看看 Log 日誌,再驗證下我們的猜想,看看具體走了哪些生命週期:

D/TwoActivity: onPause
D/OneActivity: onNewIntent
D/OneActivity: onRestart
D/OneActivity: onStart
D/OneActivity: onResume
D/TwoActivity: onStop
D/TwoActivity: onDestroy

果然此時 OneActivity 沒有重新建立,並且系統把它切換到了棧頂並呼叫 onNewIntent 方法,同時我們發現, SingleTask 預設具有 clearTop 效果,導致 TwoActivity 出棧。

<div class="tip">
我們程式碼指定 OneActivity 的棧,效果還是一樣的嗎?
</div>

帶著問題我們修改下程式碼,增加一行:

<activity
    android:name=".dLaunchChapter.OneActivity"
    android:launchMode="singleTask"
    android:taskAffinity="com.hugo.demo.singleTask"/>

然後我們按照剛剛順序 One —-> Two —-> One 啟動 Activity ,現在的棧內資訊還會更上次一樣嗎?

Running activities (most recent first):
        Run #2: ActivityRecord{1bc18519 u0 com.hugo.demo.activitydemo/.dLaunchChapter.OneActivity t636}
        Run #1: ActivityRecord{36e5e368 u0 com.hugo.demo.activitydemo/.dLaunchChapter.TwoActivity t635}

我們發現,雖然是複用了 OneActivity 而且移到了棧頂,但是並沒有銷燬 TwoActivity 。

原因在於 singleTask 模式受 taskAffinity 影響,TwoActivity 和 OneActivity 所在的 Activity 棧不同。

總結,啟動一個 lauchMode 為 singleTask 的 Activity 時有兩種情況:

  1. 若系統中存在相同 taskAffinity 值的任務棧 (tacks1 )時,會把 task1 從後臺調到前臺,若例項存在則幹掉其上面的所有 Activity 並呼叫 onNewInstance 方法重用,沒有該例項則新建一個。
  2. 否則,新建一個任務棧,並以此 Activity 作為 root 。
  • SingleInstance 單例項模式

這是一種加強的 singleTask 模式,它除了具有 singleTask 模式的所有特性以外,還加強了一點,就是具有此模式的 Activity 只能單獨地位於任務棧。

<div class="tip">
好了,關於生命週期和啟動模式實踐+知識點整理已經完成啦,
非常推薦大家下載原始碼自己執行看看 Log 日誌,檢視原始碼:Github
,這樣可以對這篇文章知識更加深刻。
</div>

參考文件

相關文章