Android的生命周期
典型情況下的生命周期
如上圖所示淤年,正常情況下,Activity會經(jīng)歷以下的生命周期
1. onCreate : 與onDestroy配對寂殉,表示Activity正在被創(chuàng)建狈醉,這是生命周期的第一個方法朽砰。在這個方法中可以做一些初始化的工作(加載布局資源贴汪、初始化Activity所需要的數(shù)據(jù)等)播赁,耗時的工作在異步線程上完成嗜历。
2. onRestart : 表示Activity正在重新啟動宣渗。一般情況下抖所,在當(dāng)前Activity從不可見重新變?yōu)榭梢姷臓顟B(tài)時onRestart就會被調(diào)用。這種情形一般是由于用戶的行為所導(dǎo)致的痕囱,比如用戶按下Home鍵切換到桌面或者打開了一個新的Activity(這時當(dāng)前Activity會暫停田轧,也就是onPause和onStop被執(zhí)行),接著用戶有回到了這個Activity鞍恢,就會出現(xiàn)這種情況傻粘。
3. onStart : 與onStop配對,表示Activity正在被啟動帮掉,并且即將開始弦悉。但是這個時候要注意它與onResume的區(qū)別。兩者都表示Activity可見蟆炊,但是onStart時Activity還正在加載其他內(nèi)容稽莉,正在向我們展示,用戶還無法看到涩搓,即無法交互污秆。
4. onResume : 與onPause配對,表示Activity已經(jīng)創(chuàng)建完成昧甘,并且可以開始活動了良拼,這個時候用戶已經(jīng)可以看到界面了,并且即將與用戶交互(完成該周期之后便可以響應(yīng)用戶的交互事件了)充边。
5. onPause : 與onResume配對庸推,表示Activity正在暫停,正常情況下浇冰,onStop接著就會被調(diào)用贬媒。在特殊情況下,如果這個時候用戶快速地再回到當(dāng)前的Activity,那么onResume會被調(diào)用(極端情況)肘习。一般來說掖蛤,在這個生命周期狀態(tài)下,可以做一些存儲數(shù)據(jù)井厌、停止動畫的工作,但是不能太耗時致讥,如果是由于啟動新的Activity而喚醒的該狀態(tài)仅仆,那會影響到新Activity的顯示,原因是onPause必須執(zhí)行完垢袱,新的Activity的onResume才會執(zhí)行墓拜。
6. onStop : 與onStart配對,表示Activity即將停止请契,可以做一些稍微重量級的回收工作咳榜,同樣也不能太耗時(可以比onPause稍微好一點(diǎn))夏醉。
7. onDestory : 與onCreate配對,表示Activity即將被銷毀涌韩,這是Activity生命周期的最后一個回調(diào)畔柔,我們可以做一些回收工作和最終的資源釋放。
這里提出兩個問題:
1.onStart和onResume臣樱、onPause和onStop從描述上來看差不多靶擦,對我們來說有什么實質(zhì)上的不同呢?
2.假設(shè)當(dāng)前Activity為A雇毫,如果這時用戶打開了一個新的活動B玄捕,那么B的onResume和A的onPause誰先執(zhí)行呢?
別看妹子啦棚放,先看第一個問題枚粘,從實際的使用過程中來說,onStart和onResume飘蚯、onPause和onStop看起來確實差不多馍迄。但Android為什么要提供看似重復(fù)的接口呢?根據(jù)上面的分析我們可以知道孝冒,這兩對回調(diào)具有不同的意義柬姚,onStart和onStop是根據(jù)應(yīng)用是否可見來進(jìn)行回調(diào)的 ,onResume和onPause是根據(jù)應(yīng)用是否位于前臺來進(jìn)行回調(diào)的庄涡,除此之外量承,無其他明顯區(qū)別。
第二個問題穴店,我們可以從Android源碼里得到解釋撕捍,A的onPause執(zhí)行后B的onResume才會被調(diào)用。從另一個角度來說Android的官方文檔中對onPause有這樣一句解釋:不能在onPause里進(jìn)行重量級操作泣洞,因為必須在onPause執(zhí)行過后忧风,新的Activity才能Resume。
異常情況下的生命周期
onSaveInstanceState方法只會出現(xiàn)在 Activity被異常終止的情況下球凰,它的調(diào)用時機(jī)是在 onStop之前狮腿,它和onPause方法沒有既定的時序關(guān)系,可能在它之前呕诉,也可能在它之后缘厢。當(dāng) Activity被重新創(chuàng)建的時候, onRestoreInstanceState會被回調(diào)甩挫,它的調(diào)用時機(jī)是 onStart之后顷编。系統(tǒng)只會在 Activity即將被銷毀并且有機(jī)會重新顯示的情況下才會去調(diào)用 onSaveInstanceState方法瞎饲。當(dāng) Activity在異常情況下需要重新創(chuàng)建時连霉,系統(tǒng)會默認(rèn)為我們保存當(dāng)前 Activity的視圖結(jié)構(gòu)散庶,并且在 Activity重啟后為我們恢復(fù)這些數(shù)據(jù),比如文本框中用戶輸入的數(shù)據(jù)、listview滾動的位置等,這些 view相關(guān)的狀態(tài)系統(tǒng)都會默認(rèn)為我們恢復(fù)。具體針對某一個 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));
}
當(dāng)我們旋轉(zhuǎn)屏幕過后,可以看到如下的log:
10-23 22:50:56.032 1613-1613/com.hugo.demo.activitydemo D/MainActivity: onCreate
10-23 22:50:56.036 1613-1613/com.hugo.demo.activitydemo D/MainActivity: onStart
10-23 22:50:56.040 1613-1613/com.hugo.demo.activitydemo D/MainActivity: onResume
10-23 22:51:05.456 1613-1613/com.hugo.demo.activitydemo D/MainActivity: onPause
10-23 22:51:05.460 1613-1613/com.hugo.demo.activitydemo D/MainActivity: onSaveInstanceState
10-23 22:51:05.460 1613-1613/com.hugo.demo.activitydemo D/MainActivity: onStop
10-23 22:51:05.460 1613-1613/com.hugo.demo.activitydemo D/MainActivity: onDestroy
10-23 22:51:05.484 1613-1613/com.hugo.demo.activitydemo D/MainActivity: onCreate
10-23 22:51:05.496 1613-1613/com.hugo.demo.activitydemo D/MainActivity: onStart
10-23 22:51:05.496 1613-1613/com.hugo.demo.activitydemo D/MainActivity: [onRestoreInstanceState]: test
10-23 22:51:05.496 1613-1613/com.hugo.demo.activitydemo D/MainActivity: onResume
在onSaveInstanceState里面保存的test税灌,確實在重新創(chuàng)建活動的時候在onRestoreInstanceState被還原出來了均函。
Android開發(fā)藝術(shù)探索上有這樣一句話:
關(guān)于保存和恢復(fù) View 的層次結(jié)構(gòu),系統(tǒng)工作流程是: Activity 異常終止, Activity 調(diào)用 onSaveInstanceState 去保存數(shù)據(jù)菱涤,然后 Activity 會委托 Windows 去保存數(shù)據(jù)苞也,接著 Window 再委托它上面的頂層容器去保存數(shù)據(jù)。頂層容器是一個 ViewGroup 粘秆,一般來說它很可能是 DectorView 如迟,最后頂層容器再去通知它的子元素保存數(shù)據(jù)。(這是一種委托思想攻走,上層委托下層殷勘,父容器委托子元素去處理事情,如 View 的繪制過程昔搂,事件分發(fā)都是采用類似的思想)
Fragment的生命周期
普通的Fragment
從圖中可以看出fragment的生命周期大部分和activity一致玲销,不同的是:
- onAttach() :當(dāng)Fragment 和 Activity產(chǎn)生關(guān)聯(lián)時調(diào)用.
- onCreateView():為Fragment加載布局的時候調(diào)用
- onActivityCreated():當(dāng) Activity 的 onCreated() 方法返回后調(diào)用此方法
- onDestroyView():當(dāng) Fragment 中的視圖被移除的時候,調(diào)用這個方法摘符。
- onDetach():當(dāng) Fragment 和 Activity 分離的時候贤斜,調(diào)用這個方法。
除了上面的這些回調(diào)函數(shù)逛裤,fragment還有三個回調(diào)是值得我們注意的:
- onViewCreated():它在onCreateView()執(zhí)行之后立馬被調(diào)用,但它執(zhí)行的時候,之前保存的狀態(tài)還未被被恢復(fù)到了視圖
- onSaveInstanceState():可在此保存fragment的一些數(shù)據(jù),與activity不同的是瘩绒,它可能在onDestroy之前的任何時間調(diào)用
- onViewStateRestored():在onActivityCreated()之后,onStart()之前被調(diào)用,以前在onSaveInstanceState()里保存的數(shù)據(jù)可以在這里還原。
ViewPager 中的 Fragment
我們開發(fā)中經(jīng)常會用到 ViewPager + Fragment 組合的形式來完成特定的需求带族。本身 Fragment 生命周期就比 Activity 要復(fù)雜很多锁荔,當(dāng)它在 ViewPager 中又是怎么回調(diào)呢?
我先給 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后蝙砌,出現(xiàn)如下log:
10-23 23:17:19.332 1613-1613/com.hugo.demo.activitydemo D/ViewPagerHostActivity: onCreate
10-23 23:17:19.388 1613-1613/com.hugo.demo.activitydemo D/ViewPagerHostActivity: onStart
10-23 23:17:19.392 1613-1613/com.hugo.demo.activitydemo D/ViewPagerHostActivity: onResume
10-23 23:17:19.404 1613-1613/com.hugo.demo.activitydemo D/OneTextFragment: onAttach
10-23 23:17:19.404 1613-1613/com.hugo.demo.activitydemo D/OneTextFragment: onCreate
10-23 23:17:19.408 1613-1613/com.hugo.demo.activitydemo D/OneTextFragment: onActivityCreated
10-23 23:17:19.408 1613-1613/com.hugo.demo.activitydemo D/OneTextFragment: onStart
10-23 23:17:19.412 1613-1613/com.hugo.demo.activitydemo D/OneTextFragment: onResume
10-23 23:17:19.412 1613-1613/com.hugo.demo.activitydemo D/TwoTextFragment: onAttach
10-23 23:17:19.412 1613-1613/com.hugo.demo.activitydemo D/TwoTextFragment: onCreate
10-23 23:17:19.412 1613-1613/com.hugo.demo.activitydemo D/TwoTextFragment: onActivityCreated
10-23 23:17:19.416 1613-1613/com.hugo.demo.activitydemo D/TwoTextFragment: onStart
10-23 23:17:19.416 1613-1613/com.hugo.demo.activitydemo D/TwoTextFragment: onResume
當(dāng)活動進(jìn)入到后臺的時候:
10-23 23:19:37.536 1613-1613/com.hugo.demo.activitydemo D/ViewPagerHostActivity: onPause
10-23 23:19:38.308 1613-1613/com.hugo.demo.activitydemo D/ViewPagerHostActivity: onSaveInstanceState
10-23 23:19:38.308 1613-1613/com.hugo.demo.activitydemo D/OneTextFragment: onStop
10-23 23:19:38.312 1613-1613/com.hugo.demo.activitydemo D/TwoTextFragment: onStop
10-23 23:19:38.312 1613-1613/com.hugo.demo.activitydemo D/ViewPagerHostActivity: onStop
當(dāng)活動返回前臺的時候:
10-23 23:28:19.568 1613-1613/com.hugo.demo.activitydemo D/ViewPagerHostActivity: onRestart
10-23 23:28:19.568 1613-1613/com.hugo.demo.activitydemo D/OneTextFragment: onStart
10-23 23:28:19.568 1613-1613/com.hugo.demo.activitydemo D/TwoTextFragment: onStart
10-23 23:28:19.572 1613-1613/com.hugo.demo.activitydemo D/ViewPagerHostActivity: onStart
10-23 23:28:19.572 1613-1613/com.hugo.demo.activitydemo D/ViewPagerHostActivity: onResume
10-23 23:28:19.576 1613-1613/com.hugo.demo.activitydemo D/OneTextFragment: onResume
10-23 23:28:19.576 1613-1613/com.hugo.demo.activitydemo D/TwoTextFragment: onResume
當(dāng)我滑動一頁的時候:
10-23 23:30:26.560 30297-30297/com.hugo.demo.activitydemo D/ThreeTextFragment: onAttach
10-23 23:30:26.564 30297-30297/com.hugo.demo.activitydemo D/ThreeTextFragment: onCreate
10-23 23:30:26.568 30297-30297/com.hugo.demo.activitydemo D/ThreeTextFragment: onActivityCreated
10-23 23:30:26.568 30297-30297/com.hugo.demo.activitydemo D/ThreeTextFragment: onStart
10-23 23:30:26.572 30297-30297/com.hugo.demo.activitydemo D/ThreeTextFragment: onResume
可以看到在fragment進(jìn)行滑動切換的時候阳堕,對下一個fragment進(jìn)行了預(yù)加載。
再滑動一頁:
10-23 23:32:29.612 30297-30297/com.hugo.demo.activitydemo D/OneTextFragment: onStop
10-23 23:32:29.612 30297-30297/com.hugo.demo.activitydemo D/OneTextFragment: onDestroyView
這個時候第一個fragment已經(jīng)被銷毀了择克。
當(dāng)我們增加一行代碼:viewPager.setOffscreenPageLimit(int limit)
10-23 23:38:47.236 4139-4139/com.hugo.demo.activitydemo D/ViewPagerHostActivity: onCreate
10-23 23:38:47.272 4139-4139/com.hugo.demo.activitydemo D/ViewPagerHostActivity: onStart
10-23 23:38:47.276 4139-4139/com.hugo.demo.activitydemo D/ViewPagerHostActivity: onResume
10-23 23:38:47.280 4139-4139/com.hugo.demo.activitydemo D/OneTextFragment: onAttach
10-23 23:38:47.284 4139-4139/com.hugo.demo.activitydemo D/OneTextFragment: onCreate
10-23 23:38:47.284 4139-4139/com.hugo.demo.activitydemo D/OneTextFragment: onActivityCreated
10-23 23:38:47.288 4139-4139/com.hugo.demo.activitydemo D/OneTextFragment: onStart
10-23 23:38:47.288 4139-4139/com.hugo.demo.activitydemo D/OneTextFragment: onResume
10-23 23:38:47.292 4139-4139/com.hugo.demo.activitydemo D/TwoTextFragment: onAttach
10-23 23:38:47.292 4139-4139/com.hugo.demo.activitydemo D/TwoTextFragment: onCreate
10-23 23:38:47.292 4139-4139/com.hugo.demo.activitydemo D/TwoTextFragment: onActivityCreated
10-23 23:38:47.296 4139-4139/com.hugo.demo.activitydemo D/ThreeTextFragment: onAttach
10-23 23:38:47.296 4139-4139/com.hugo.demo.activitydemo D/ThreeTextFragment: onCreate
10-23 23:38:47.300 4139-4139/com.hugo.demo.activitydemo D/ThreeTextFragment: onActivityCreated
10-23 23:38:47.300 4139-4139/com.hugo.demo.activitydemo D/TwoTextFragment: onStart
10-23 23:38:47.304 4139-4139/com.hugo.demo.activitydemo D/TwoTextFragment: onResume
10-23 23:38:47.304 4139-4139/com.hugo.demo.activitydemo D/ThreeTextFragment: onStart
10-23 23:38:47.308 4139-4139/com.hugo.demo.activitydemo D/ThreeTextFragment: onResume
可以看到除了本來要顯示的那個fragment外嘱丢,還有其的2個fragment也被創(chuàng)建了出來。所以說我們通過viewPager.setOffscreenPageLimit(int limit)可以設(shè)置緩存fragment的個數(shù)祠饺。
Android的啟動模式
Activity 的四種啟動模式
- standard:標(biāo)準(zhǔn)模式,每次啟動一個Activity都會產(chǎn)生一個新的實例汁政,不論這個實例是否存在道偷。
- singleTop: 棧頂復(fù)用模式缀旁,在這種情況下,如果新的Activity已經(jīng)位于任務(wù)棧的棧頂勺鸦,那么此Activity不會被重新創(chuàng)建并巍,同時它的onNewIntent方法會被調(diào)用,通過此方法我們可以取出當(dāng)前請求的信息换途。
- singleTask:棧內(nèi)復(fù)用模式懊渡,這是一種單例模式,在這種模式下军拟,只要有一個activity在一個棧中存在剃执,那么多次啟動這個activity都不會創(chuàng)建新的實例,和singleTop一樣懈息,系統(tǒng)也會回調(diào)其onNewIntent方法肾档。注意:由于singleTask默認(rèn)具有clearTop的效果,所以會導(dǎo)致所有在該活動上的其他activity全部出棧辫继。如現(xiàn)在棧內(nèi)的情況是ABCD怒见,如果B以singleTask模式啟動,啟動B后姑宽,B上面的所有活動都會出棧遣耍,最后棧內(nèi)的情況是AB。
- singleInstance:單實例模式炮车,這是一種加強(qiáng)的singleTask實例舵变,它除了具有singleTask模式的所有特性外,還加強(qiáng)了一點(diǎn)示血,那就是具有此種模式的activity只能單獨(dú)的位于一個任務(wù)棧中棋傍。
這些啟動模式可以在功能清單AndroidManifest.xml 中設(shè)置launchMode屬性。
實際操作
-
singleTop:我們在清單里設(shè)置oneActivity的launchMode為singleTop难审,然后代碼里設(shè)置活動的啟動順序為one-->one,反復(fù)點(diǎn)擊多次瘫拣,查看活動棧里的情況如下。
執(zhí)行adb命令:adb shell dumpsys activity activitys
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}
我們可以看到活動棧中只有一個one告喊,說明oneActivity并沒有被重新創(chuàng)建
-
singleTask:如果我們將oneActivity的launchMode設(shè)置為singleTask麸拄,然后在代碼里設(shè)置活動的啟動順序為one->Two->one。
one -> Two 我們記錄下當(dāng)前activity的棧
我們記錄下當(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}
two->one 記錄下當(dāng)前的 Activity 棧:
Running activities (most recent first):
Run #1: ActivityRecord{39e11719 u0 com.hugo.demo.activitydemo/.dLaunchChapter.OneActivity t632}
可以看見黔姜,oneActivity上面的TwoActivity被出棧了拢切,而oneActivity和上次內(nèi)存信息相同,說明確實是復(fù)用了秆吵,沒有創(chuàng)建新的實例淮椰。
TaskAffinity-任務(wù)相關(guān)性和allowTaskReparenting
這個參數(shù)標(biāo)識了一個Activity所需要的任務(wù)棧的名字,默認(rèn)情況下,所有Activity所需的任務(wù)棧的名字為應(yīng)用的包名主穗。當(dāng)然泻拦,我們可以為每個activity單獨(dú)指定TaskAffinity屬性,這個屬性必須和包名不一致忽媒,否則相當(dāng)于沒有指定争拐。TaskAffinity屬性主要和singleTask或allowTaskReparenting屬性配對使用,在其他情況下沒有意義晦雨。
舉例:當(dāng)應(yīng)用A啟動了應(yīng)用B的一個活動C架曹,然后按home鍵回到桌面,再在這個時候打開應(yīng)用B闹瞧,如果活動C的allowTaskReparenting屬性值為true的話绑雄,那么當(dāng)B被啟動的時候,活動C會從應(yīng)用A的任務(wù)棧移到應(yīng)用B的任務(wù)棧中夹抗∩鳎可以這么理解,由于A啟動了C漠烧,那么C肯定是在A的任務(wù)棧中的杏愤,但C屬于B應(yīng)用,那么C的TaskAffinity的值肯定不會和A的任務(wù)棧相同(因為包名不同)已脓。所以當(dāng)B被啟動過后珊楼,B會創(chuàng)建自己的任務(wù)棧。系統(tǒng)在這個時候發(fā)現(xiàn)C所需要的任務(wù)棧已經(jīng)被創(chuàng)建了度液,所以就把C從A的任務(wù)棧移到了B的任務(wù)棧厕宗。
提問:現(xiàn)在我們有3個活動,我們將oneActivity的launchMode屬性設(shè)置為standard堕担,twoActivity和threeActivity的launchMode屬性設(shè)置為singleTask已慢,并指定他們的taskAffinity屬性為:"com.ryg.task1",注意taskAffinity屬性的值為字符串。然后做如下操作霹购。在oneActivity里面啟動twoActivity佑惠,在twoActivity里面啟動threeActivity,在threeActivity里面啟動oneActivity齐疙,最后在oneActivity里面啟動twoActivity膜楷,那現(xiàn)在按back鍵,顯示的是哪個activity贞奋?
答案是返回到oneActivity赌厅。如此時再次按back鍵,將返回到桌面轿塔。
參考文章及Demo源代碼
Android開發(fā)藝術(shù)探索第一章
Android 基礎(chǔ) -- 生命周期和啟動模式實踐總結(jié)
查看源碼:Github特愿,源代碼來自@謝三弟