Activity / Fragment 的生命周期是每個(gè) Android 開發(fā)者最最基礎(chǔ)的知識(shí)點(diǎn)供嚎。所以特別有必要自己整理一番。總看別人博客和書上的死知識(shí)槽驶,還不如自己動(dòng)手實(shí)踐审洞,然后輸出要印象深刻,理解透徹痕支。
Activity 生命周期
正常情況下的生命周期分析
- 針對一個(gè)特定的 Activity 颁虐,第一次啟動(dòng),回調(diào)如下:
onCreate
—->onStart
—->onResume
Log 日志
D/KLog: (MainActivity.java:19) onCreate
D/KLog: (MainActivity.java:44) onStart
D/KLog: (MainActivity.java:62) onResume
- 切換回到桌面的時(shí)候卧须,回調(diào)如下:
onPause
—->onStop
Log 日志
D/KLog: (MainActivity.java:50) onPause
D/KLog: (MainActivity.java:68) onStop
Back 鍵退出的話另绩,最后會(huì)
onDestroy
啟動(dòng)一個(gè)新的 Activity , 我們看看兩個(gè) 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)
- 這個(gè)時(shí)候我們 Back 回到第一個(gè) Activity 時(shí)發(fā)生的回調(diào):
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
回調(diào)中直接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
我們發(fā)現(xiàn) B Activity 只會(huì)執(zhí)行onCreate
和onDestroy
。
- 接下來我們啟動(dòng)一個(gè)特殊的 Activity (半透明或者對話框樣式)到關(guān)閉它:
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
在正常使用應(yīng)用的過程中花嘶,前臺(tái) Activity 有時(shí)會(huì)被其他導(dǎo)致 Activity 暫停的可視組件阻擋笋籽。 例如,當(dāng)半透明 Activity 打開時(shí)(比如對話框樣式中的 Activity )椭员,上一個(gè) Activity 會(huì)暫停车海。 只要 Activity 仍然部分可見但目前又未處于焦點(diǎn)之中,它會(huì)一直暫停拆撼。
<div class="tip">
問題:如果是啟動(dòng)一個(gè)普通的 Dialog 容劳,Activity 的會(huì)執(zhí)行 onPause 嗎?
</div>
答案是不會(huì)的闸度,我們可以這樣理解竭贩,普通的 dialog 是依附在本 Activity 的,相當(dāng)于是一個(gè)整體莺禁,所以它本身的焦點(diǎn)也是屬于 Activity 的留量。故不會(huì)調(diào)用 onPause 。
異常狀態(tài)下的生命周期
onSaveInstanceState
方法只會(huì)出現(xiàn)在 Activity
被異常終止的情況下哟冬,它的調(diào)用時(shí)機(jī)是在 onStop
之前楼熄,它和 onPause
方法沒有既定的時(shí)序關(guān)系,可能在它之前浩峡,也可能在它之后可岂。當(dāng) Activity
被重新創(chuàng)建的時(shí)候, onRestoreInstanceState
會(huì)被回調(diào)翰灾,它的調(diào)用時(shí)機(jī)是 onStart
之后缕粹。
系統(tǒng)只會(huì)在 Activity
即將被銷毀并且有機(jī)會(huì)重新顯示的情況下才會(huì)去調(diào)用 onSaveInstanceState
方法稚茅。
當(dāng) Activity
在異常情況下需要重新創(chuàng)建時(shí),系統(tǒng)會(huì)默認(rèn)為我們保存當(dāng)前 Activity
的視圖結(jié)構(gòu)平斩,并且在 Activity
重啟后為我們恢復(fù)這些數(shù)據(jù)亚享,比如文本框中用戶輸入的數(shù)據(jù)、listview
滾動(dòng)的位置等绘面,這些 view
相關(guān)的狀態(tài)系統(tǒng)都會(huì)默認(rèn)為我們恢復(fù)欺税。具體針對某一個(gè) view
系統(tǒng)能為我們恢復(fù)哪些數(shù)據(jù)可以查看 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));
}
為了方便我們旋轉(zhuǎn)下屏幕來異常終止 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 開發(fā)者藝術(shù)探索 一書:
關(guān)于保存和恢復(fù) View 的層次結(jié)構(gòu)揭璃,系統(tǒng)工作流程是: Activity 異常終止, Activity 調(diào)用 onSaveInstanceState 去保存數(shù)據(jù)晚凿,然后 Activity 會(huì)委托 Windows 去保存數(shù)據(jù),接著 Window 再委托它上面的頂層容器去保存數(shù)據(jù)塘辅。頂層容器是一個(gè) ViewGroup 晃虫,一般來說它很可能是 DectorView ,最后頂層容器再去通知它的子元素保存數(shù)據(jù)扣墩。(這是一種委托思想哲银,上層委托下層,父容器委托子元素去處理事情呻惕,如 View 的繪制過程荆责,事件分發(fā)都是采用類似的思想)
Fragment
普通的 Fragment
從圖可以看出,F(xiàn)ragment 生命周期大部分的狀態(tài)與 Activity 相似亚脆,特殊的是
-
onAttach()
—— 當(dāng) Fragment 被加入到 Activity 時(shí)調(diào)用(在這個(gè)方法中可以獲得所在的 Activity ). -
onCreateView()
—— 當(dāng) Activity 要得到 Fragment 的 Layout 時(shí)做院,調(diào)用此方法,F(xiàn)ragment 在其中創(chuàng)建自己的 Layout (界面)濒持。 -
onActivityCreated()
—— 當(dāng) Activity 的 onCreated() 方法返回后調(diào)用此方法 -
onDestroyView()
—— 當(dāng) Fragment 中的視圖被移除的時(shí)候键耕,調(diào)用這個(gè)方法。 -
onDetach()
—— 當(dāng) Fragment 和 Activity 分離的時(shí)候柑营,調(diào)用這個(gè)方法屈雄。
ViewPager 中的 Fragment
我們開發(fā)中經(jīng)常會(huì)用到 ViewPager + Fragment 組合的形式來完成特定的需求。本身 Fragment 生命周期就比 Activity 要復(fù)雜很多官套,當(dāng)它在 ViewPager 中又是怎么回調(diào)呢酒奶?
我先給 ViewPager 加入三個(gè) 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));
啟動(dòng)這個(gè) 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
我們發(fā)現(xiàn)啟動(dòng)后,有兩個(gè) Fragment(one,two) 被創(chuàng)建奶赔,為什么會(huì)創(chuàng)建兩個(gè)惋嚎?這個(gè)問題我們留著后面說。
當(dāng) Activity 進(jìn)入后臺(tái):
D/ViewPagerHostActivity: onPause
D/ViewPagerHostActivity: onSaveInstanceState
D/TwoTextFragment: onStop
D/OneTextFragment: onStop
D/ViewPagerHostActivity: onStop
當(dāng) Activity 返回前臺(tái):
D/ViewPagerHostActivity: onRestart
D/TwoTextFragment: onStart
D/OneTextFragment: onStart
D/ViewPagerHostActivity: onStart
D/ViewPagerHostActivity: onResume
D/TwoTextFragment: onResume
D/OneTextFragment: onResume
當(dāng) 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òng)一頁:
D/ThreeTextFragment: onAttach
D/ThreeTextFragment: onCreate
D/ThreeTextFragment: onActivityCreated
D/ThreeTextFragment: onStart
D/ThreeTextFragment: onResume
當(dāng)前顯示的頁面是 TwoTextFragment 然后 ThreeTextFragment 也已經(jīng)創(chuàng)建好了站刑。
再滑動(dòng)一頁:
D/OneTextFragment: onStop
D/OneTextFragment: onDestroyView
當(dāng)前顯示的頁面是 ThreeTextFragment 另伍,我們發(fā)現(xiàn) OneTextFragment 已經(jīng)銷毀。
我們可以得到默認(rèn)狀態(tài)下的 ViewPager 會(huì)緩存 1 個(gè) Fragment绞旅,相當(dāng)于有兩個(gè) Fragment 是創(chuàng)建狀態(tài)摆尝。
當(dāng)我們增加一行代碼:
viewPager.setOffscreenPageLimit(2);
當(dāng) Activity 創(chuàng)建時(shí)的生命周期:
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
三個(gè) Fragment 都創(chuàng)建好了愕宋,并且左右切換不會(huì)走任何生命周期(雖然是廢話)。
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 的緩存的默認(rèn)值和最小值是 1。
啟動(dòng)模式
Activity 的四種啟動(dòng)模式
Standard:標(biāo)準(zhǔn)模式囤捻,一調(diào)用 startActivity() 方法就會(huì)產(chǎn)生一個(gè)新的實(shí)例臼朗。
SingleTop: 來了 intent, 每次都創(chuàng)建新的實(shí)例,僅一個(gè)例外:當(dāng)棧頂?shù)腶ctivity 恰恰就是該activity的實(shí)例(即需要?jiǎng)?chuàng)建的實(shí)例)時(shí)蝎土,不再創(chuàng)建新實(shí)例视哑。這解決了棧頂復(fù)用問題
SingleTask: 來了 intent 后,檢查棧中是否存在該 activity的實(shí)例誊涯,如果存在就把 intent 發(fā)送給它挡毅,否則就創(chuàng)建一個(gè)新的該activity的實(shí)例,放入一個(gè)新的 task 棧的棧底暴构」虺剩肯定位于一個(gè) task 的棧底,而且棧中只能有它一個(gè)該 activity 實(shí)例取逾,但允許其他 activity 加入該棧耗绿。解決了在一個(gè) task 中共享一個(gè) activity。
SingleInstance: 這個(gè)跟 SingleTask 基本上是一樣砾隅,只有一個(gè)區(qū)別:在這個(gè)模式下的Activity實(shí)例所處的task中误阻,只能有這個(gè)activity實(shí)例,不能有其他的實(shí)例晴埂。一旦該模式的activity的實(shí)例已經(jīng)存在于某個(gè)棧中究反,任何應(yīng)用在激活該activity時(shí)都會(huì)重用該棧中的實(shí)例,解決了多個(gè)task共享一個(gè) activity儒洛。
這些啟動(dòng)模式可以在功能清單文件 AndroidManifest.xml 中進(jìn)行設(shè)置精耐,中的 launchMode 屬性。
具體實(shí)踐
- SingleTop 棧頂復(fù)用模式
<activity android:name=".dLaunchChapter.OneActivity"
android:launchMode="singleTop"/>
我們在清單里先給 OneActivity 啟動(dòng)模式設(shè)置為 singleTop 晶丘,然后代碼啟動(dòng)活動(dòng)的順序?yàn)?One --> One
黍氮,反復(fù)點(diǎn)擊多次,然后我們看看棧內(nèi)情況浅浮。
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}
該啟動(dòng)模式下并且 OneActivity 在棧頂所以不會(huì)創(chuàng)建新的實(shí)例沫浆,其生命周期調(diào)用 onPause —-> onNewIntent —-> onResume
- SingleTask 棧內(nèi)復(fù)用模式
修改 OneActivity 的啟動(dòng)模式為 SingleTask ,然后我們代碼啟動(dòng)的順序?yàn)?One —-> Two —-> One
滚秩,接了下看看棧內(nèi)情況:
One —-> Two
我們記錄下當(dāng)前的 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}
接下來我們執(zhí)行 Two —-> One
专执,當(dāng)前 Activity 棧信息:
Running activities (most recent first):
Run #1: ActivityRecord{39e11719 u0 com.hugo.demo.activitydemo/.dLaunchChapter.OneActivity t632}
當(dāng) TwoActivity 啟動(dòng) OneActivity(SingleTask) 的時(shí)候,堆棧信息里只剩下了 OneActivity 并且和第一次內(nèi)存信息 39e11719 相同郁油,所以確實(shí)是復(fù)用了沒有新建實(shí)例本股,接下來我們看看 Log 日志攀痊,再驗(yàn)證下我們的猜想,看看具體走了哪些生命周期:
D/TwoActivity: onPause
D/OneActivity: onNewIntent
D/OneActivity: onRestart
D/OneActivity: onStart
D/OneActivity: onResume
D/TwoActivity: onStop
D/TwoActivity: onDestroy
果然此時(shí) OneActivity 沒有重新創(chuàng)建拄显,并且系統(tǒng)把它切換到了棧頂并調(diào)用 onNewIntent 方法苟径,同時(shí)我們發(fā)現(xiàn), SingleTask 默認(rèn)具有 clearTop 效果躬审,導(dǎo)致 TwoActivity 出棧棘街。
<div class="tip">
我們代碼指定 OneActivity 的棧,效果還是一樣的嗎承边?
</div>
帶著問題我們修改下代碼遭殉,增加一行:
<activity
android:name=".dLaunchChapter.OneActivity"
android:launchMode="singleTask"
android:taskAffinity="com.hugo.demo.singleTask"/>
然后我們按照剛剛順序 One —-> Two —-> One
啟動(dòng) Activity ,現(xiàn)在的棧內(nèi)信息還會(huì)更上次一樣嗎博助?
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}
我們發(fā)現(xiàn)险污,雖然是復(fù)用了 OneActivity 而且移到了棧頂,但是并沒有銷毀 TwoActivity 富岳。
原因在于 singleTask 模式受 taskAffinity 影響蛔糯,TwoActivity 和 OneActivity 所在的 Activity 棧不同。
總結(jié)窖式,啟動(dòng)一個(gè) lauchMode 為 singleTask 的 Activity 時(shí)有兩種情況:
- 若系統(tǒng)中存在相同 taskAffinity 值的任務(wù)棧 (tacks1 )時(shí),會(huì)把 task1 從后臺(tái)調(diào)到前臺(tái)渤闷,若實(shí)例存在則干掉其上面的所有 Activity 并調(diào)用 onNewInstance 方法重用,沒有該實(shí)例則新建一個(gè)脖镀。
- 否則飒箭,新建一個(gè)任務(wù)棧,并以此 Activity 作為 root 蜒灰。
- SingleInstance 單實(shí)例模式
這是一種加強(qiáng)的 singleTask 模式弦蹂,它除了具有 singleTask 模式的所有特性以外,還加強(qiáng)了一點(diǎn)强窖,就是具有此模式的 Activity 只能單獨(dú)地位于任務(wù)棧凸椿。
<div class="tip">
好了,關(guān)于生命周期和啟動(dòng)模式實(shí)踐+知識(shí)點(diǎn)整理已經(jīng)完成啦翅溺,
非常推薦大家下載源碼自己運(yùn)行看看 Log 日志脑漫,查看源碼:Github
,這樣可以對這篇文章知識(shí)更加深刻咙崎。
</div>
參考文檔
- Android 開發(fā)藝術(shù)探索 第一章
- 管理Activity生命周期
- Print the current back stack in the log