Android 基礎 -- Activity 生命週期實踐總結
Activity / Fragment 的生命週期是每個 Android 開發者最最基礎的知識點。所以特別有必要自己整理一番。總看別人部落格和書上的死知識,還不如自己動手實踐,然後輸出要印象深刻,理解透徹。
Activity 生命週期
正常情況下的生命週期分析
- 針對一個特定的 Activity ,第一次啟動,回撥如下:
onCreate
—->onStart
—->onResume
Log 日誌
D/KLog: (MainActivity.java:19) onCreate
D/KLog: (MainActivity.java:44) onStart
D/KLog: (MainActivity.java:62) onResume
- 切換回到桌面的時候,回撥如下:
onPause
—->onStop
Log 日誌
D/KLog: (MainActivity.java:50) onPause
D/KLog: (MainActivity.java:68) onStop
Back 鍵退出的話,最後會
onDestroy
啟動一個新的 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)
- 這個時候我們 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)
- 如果我在 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 只會執行onCreate
和onDestroy
。
- 接下來我們啟動一個特殊的 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
的原始碼中的 onSaveInstanceState
和 onRestoreInstanceState
方法。
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 時有兩種情況:
- 若系統中存在相同 taskAffinity 值的任務棧 (tacks1 )時,會把 task1 從後臺調到前臺,若例項存在則幹掉其上面的所有 Activity 並呼叫 onNewInstance 方法重用,沒有該例項則新建一個。
- 否則,新建一個任務棧,並以此 Activity 作為 root 。
- SingleInstance 單例項模式
這是一種加強的 singleTask 模式,它除了具有 singleTask 模式的所有特性以外,還加強了一點,就是具有此模式的 Activity 只能單獨地位於任務棧。
<div class="tip">
好了,關於生命週期和啟動模式實踐+知識點整理已經完成啦,
非常推薦大家下載原始碼自己執行看看 Log 日誌,檢視原始碼:Github
,這樣可以對這篇文章知識更加深刻。
</div>
參考文件
- Android 開發藝術探索 第一章
- 管理Activity生命週期
- Print the current back stack in the log
相關文章
- Activity生命週期總結
- activity的生命週期(總結)
- Android Activity生命週期Android
- [Android]Activity的生命週期Android
- 文章之間的基本總結:Activity生命週期
- Activity生命週期
- Android 元件系列-----Activity生命週期Android元件
- Android Activity生命週期詳解Android
- Activity生命週期onDestroy
- Android全面解析之Activity生命週期Android
- android基礎學習-android篇day17-Activity的生命週期(轉)Android
- React 基礎_生命週期React
- View生命週期與Activity生命週期的關係View
- Android Activity生命週期的一點感悟Android
- Android 之 Activity 生命週期淺析(一)Android
- 初識Android之Activity的生命週期Android
- vue生命週期總結Vue
- React生命週期總結React
- fragment生命週期(總結)Fragment
- Android四大元件——Activity——Activity的生命週期Android元件
- Angular生命週期實踐Angular
- Activity 知識梳理(1) Activity生命週期
- 擼擼Android的羊毛(二)----Activity生命週期Android
- Android 之 Activity 生命週期的淺析(二)Android
- 深入學習Activity生命週期
- View和Activity的生命週期View
- Activity簡介及生命週期
- Android | Activity和Fragment最全生命週期+發現大牛AndroidFragment
- Android入門教程之Activity(生命週期,啟動...)Android
- Android學習筆記04——Activity的生命週期Android筆記
- Unity基礎-指令碼生命週期Unity指令碼
- Servlet基礎教程之生命週期Servlet
- vue使用總結-生命週期篇Vue
- vue總結「三」--元件生命週期Vue元件
- android官方Api 理解Activity生命週期的回撥機制(適合有基礎的人看)AndroidAPI
- Activity生命週期與啟動模式模式
- Activity生命週期深入理解2
- 關於activity的生命週期1