一、Activity簡(jiǎn)述
1、概念引入

Activity作為Android的四大組件之一,Activity在Android系統(tǒng)中是以界面的形式進(jìn)行體現(xiàn)。其中Activity實(shí)現(xiàn)了如Window.Callback, KeyEvent.Callback等接口用于與用戶進(jìn)行交互。
2、源碼釋義
An activity is a single, focused thing that the user can do.
一個(gè)界面是開(kāi)發(fā)者可以操作的一個(gè)重點(diǎn)的獨(dú)立事項(xiàng)。
Almost all activities interact with the user, so the Activity class takes care of creating a window for you in which you can place your UI with setContentView(View).
幾乎所有界面都可以與用戶交互,所以Activity類(lèi)負(fù)責(zé)為用戶創(chuàng)建一個(gè)窗口,你可以在其中使用setContentView(View)放置UI。
While activities are often presented to the user as full-screen windows, they can also be used in other ways: as floating windows (via a theme with windowIsFloating set) or embedded inside of another activity (using ActivityGroup).
雖然界面通常以全屏窗口的形式呈現(xiàn)給用戶,但它們也可以以其他方式使用:作為浮動(dòng)窗口(通過(guò)具有windowIsFloating集合的主題)或嵌入另一個(gè)活動(dòng)(使用ActivityGroup)內(nèi)部。
There are two methods almost all subclasses of Activity will implement:
幾乎所有的Activity子類(lèi)都會(huì)實(shí)現(xiàn)兩種方法:
onCreate(Bundle) is where you initialize your activity. Most importantly, here you will usually call setContentView(int) with a layout resource defining your UI, and using findViewById(int) to retrieve the widgets in that UI that you need to interact with programmatically.
onCreate(Bundle)是初始化界面的地方。最重要的是,在這里你通常會(huì)調(diào)用setContentView(int)和一個(gè)定義你的UI的布局資源,并且使用findViewById(int)來(lái)檢索你需要以編程方式進(jìn)行交互的那個(gè)UI中控件。
onPause() is where you deal with the user leaving your activity.Most importantly, any changes made by the user should at this point be committed (usually to the ContentProvider holding the data).
onPause()是你對(duì)于用戶離開(kāi)界面的處理。最重要的是,此時(shí)用戶所做的任何更改都應(yīng)該提交(通常發(fā)送給持有數(shù)據(jù)的ContentProvider。
To be of use with Context.startActivity(), all activity classes must have a corresponding declaration in their package's AndroidManifest.xml.
要使用Context.startActivity(),所有界面類(lèi)必須在其包下的AndroidManifest.xml中具有相應(yīng)的聲明。
二、Activity的生命周期
1、Activity的生命周期圖
2、生命周期詳解
2.1、OnCreate()方法
Called when the activity is first created. This is where you should do all of your normal static set up:
create views, bind data to lists, etc.
在第一次創(chuàng)建活動(dòng)時(shí)調(diào)用。 這是你應(yīng)該完成所有常規(guī)靜態(tài)設(shè)置的位置:創(chuàng)建視圖,將數(shù)據(jù)綁定到列表等。
This method also provides you with a Bundle containing the activity's previously frozen state, if there was one.Always followed by onStart().
此方法還會(huì)為你提供一個(gè)包含Activity先前保存頁(yè)面狀態(tài)的Bundle對(duì)象(如果有的話)。總是跟著onStart()方法。
2.2、OnRestart()方法
Called after your activity has been stopped, prior to it being started again.
在你的界面停止后調(diào)用,然后再次啟動(dòng)。
Always followed by onStart()
總是跟著onStart()
2.3、OnStart()方法
Called when the activity is becoming visible to the user.
當(dāng)界面對(duì)用戶變得可見(jiàn)時(shí)調(diào)用。
Followed by onResume() if the activity comes to the foreground, or onStop() if it becomes hidden.
如果活動(dòng)進(jìn)入前臺(tái),則跟隨執(zhí)行onResume()方法,如果隱藏,則執(zhí)行onStop()。
此時(shí),Activity實(shí)際上就已經(jīng)可見(jiàn)了,但是還沒(méi)有出現(xiàn)在前臺(tái),無(wú)法和用戶進(jìn)行交互。
2.4、onResume()方法
Called when the activity will start interacting with the user.
當(dāng)界面對(duì)用戶變得可見(jiàn)時(shí)調(diào)用。
At this point your activity is at the top of the activity stack, with user input going to it.Always followed by onPause().
如果界面進(jìn)入前臺(tái),則跟隨onResume()方法,如果隱藏,則執(zhí)行onStop()方法。
此時(shí),Activity已經(jīng)可見(jiàn),并且出現(xiàn)在前臺(tái)并開(kāi)始活動(dòng)。要注意的是onStart()被執(zhí)行時(shí)Activity顯示在后臺(tái),只有當(dāng)OnResume()被執(zhí)行時(shí)Activity才顯示到前臺(tái)。
2.5、onPause()方法
Called when the system is about to start resuming a previous activity. This is typically used to commit unsaved changes to persistent data, stop animations and other things that may be consuming CPU, etc. Implementations of this method must be very quick because the next activity will not be resumed until this method returns.
onPause()方法在系統(tǒng)即將開(kāi)始顯示之前的界面時(shí)調(diào)用。 這通常用于將未保存的更改進(jìn)行保存,并保存為持久化數(shù)據(jù),并且會(huì)執(zhí)行例如停止動(dòng)畫(huà)和其他可能消耗CPU的內(nèi)容等。此方法的實(shí)現(xiàn)必須非??焖伲?yàn)樵诖朔椒ǚ祷刂?,下一個(gè)界面不會(huì)顯示。
Followed by either onResume() if the activity returns back to the front, or onStop() if it becomes invisible to the user.
如果界面返回到前臺(tái),則跟隨onResume();如果對(duì)用戶不可見(jiàn),則使用onStop()。
- onPause()方法執(zhí)行后,Activity界面切換為后臺(tái)程序
2.6、onStop()方法
Called when the activity is no longer visible to the user, because another activity has been resumed and is covering this one. This may happen either because a new activity is being started, an existing one is being brought in front of this one, or this one is being destroyed.
onStop()方法會(huì)在當(dāng)界面對(duì)用戶不再可見(jiàn)時(shí)調(diào)用,因?yàn)榱硪豁?xiàng)界面正在顯示并要去覆蓋這個(gè)界面。 這可能是因?yàn)橐豁?xiàng)新的界面正在開(kāi)始,其他的界面會(huì)被顯示為前臺(tái)界面,或者這個(gè)界面正在被摧毀。
Followed by either onRestart() if this activity is coming back to interact with the user, or onDestroy() if this activity is going away.
如果這個(gè)界面快速的回顯與用戶交互緊接著onRestart()會(huì)被執(zhí)行,否則,這個(gè)界面回銷(xiāo)毀onDestroy()方法會(huì)被執(zhí)行。
2.6、onDestroy()方法
The final call you receive before your activity is destroyed. This can happen either because the activity is finishing (someone called finish() on it, or because the system is temporarily destroying this instance of the activity to save space. You can distinguish between these two scenarios with the isFinishing() method.)
在你的界面被銷(xiāo)毀前被最后調(diào)用的方法。 這可能是因?yàn)榻缑嬲诮Y(jié)束(有人在其中調(diào)用finish()方法因?yàn)橄到y(tǒng)會(huì)暫時(shí)銷(xiāo)毀此Activity界面的實(shí)例以節(jié)省空間,你可以使用isFinishing()方法區(qū)分內(nèi)存空間是否節(jié)省資源的這兩種情況)。
3、異常情況下生命周期詳解
3.1、橫豎屏切換時(shí)Activity的生命周期
此時(shí)的生命周期跟清單文件里的配置有關(guān)系。
①不設(shè)置 Activity 的 android:configChanges 時(shí),橫豎屏切換會(huì)重新調(diào)用各個(gè)生命周期,銷(xiāo)毀當(dāng)前 activity,然后重新加載,跟系統(tǒng)配置有關(guān)。
②onSaveInstanceState()方法會(huì)在當(dāng)前頁(yè)面銷(xiāo)毀前被調(diào)用存儲(chǔ)數(shù)據(jù),onRestoreInstanceState()方法會(huì)被執(zhí)行去取出保存的Bundle對(duì)象中的內(nèi)容,進(jìn)行一次橫豎屏切換時(shí)Activity所執(zhí)行的生命周期方法以及在onSaveInstanceState與onRestoreInstanceState打印相應(yīng)日志,如下圖所示:

③對(duì)于onRestoreInstanceState()方法,源碼中的注釋如下:
This method is called after onStart when the activity is being re-initialized from a previously saved state, given here in savedInstanceState.
當(dāng)這個(gè)界面從之前保存的狀態(tài)重新初始化時(shí),這個(gè)方法在onStart之后被調(diào)用,這里在savedInstanceState中給出。
Most implementations will simply use onCreate to restore their state, but it is sometimes convenient to do it here after all of the initialization has been done or to allow subclasses to decide whether to use your default implementation. The default implementation of this method performs a restore of any view state that had previously been frozen by onSaveInstanceState.
大多數(shù)時(shí),可以直接從onCreate()方法中的Bundle對(duì)象從中獲取內(nèi)容來(lái)恢復(fù)之前的頁(yè)面狀態(tài),但有時(shí)在完成所有初始化或允許子類(lèi)決定使用默認(rèn)實(shí)現(xiàn)之后,在此處執(zhí)行重新獲取已存儲(chǔ)數(shù)據(jù)的操作很方便。 此方法的默認(rèn)實(shí)現(xiàn)會(huì)還原之前由onSaveInstanceState保存的任何視圖狀態(tài)。
④根據(jù)源碼可以得知onRestoreInstanceState 方法的默認(rèn)實(shí)現(xiàn)會(huì)還原之前由onSaveInstanceState保存的任何視圖狀態(tài)。

-
關(guān)于保存和恢復(fù)View層次結(jié)構(gòu)系統(tǒng)工作流程是這樣的:首先Activity被意外終止時(shí),Activity會(huì)調(diào)用
onSaveInstanceState去保存數(shù)據(jù),然后Activity會(huì)委托Window去保存數(shù)據(jù),接著Window會(huì)委托它上面的頂層容器去保存數(shù)據(jù)。頂層容器肯是一個(gè)ViewGroup,一般來(lái)說(shuō)是DecorView。最后頂層容器再去一一通知它的子元素來(lái)保存數(shù)據(jù),這樣整個(gè)數(shù)據(jù)保存過(guò)程就完成了。這是一種典型的委托思想,上層委托下層、父容器委托子元素去處理一件事件;在View的繪制流程、事件分發(fā)都是采用這種思想在處理。

3.2、資源內(nèi)容不足時(shí)導(dǎo)致低優(yōu)先級(jí)的Activity被殺死
Activity按優(yōu)先級(jí)從高到低可分為如下3種:
| Activity按優(yōu)先級(jí)從高到低 |
|---|
| ①前臺(tái)Activity-正在和用戶進(jìn)行交互的頁(yè)面(優(yōu)先級(jí)最高)。 |
| ②可見(jiàn)但非前臺(tái)Activity-比如Activity中彈出了一個(gè)對(duì)話框,導(dǎo)致Activity可見(jiàn),但是位于后臺(tái)無(wú)法和用戶進(jìn)行直接交互(優(yōu)先級(jí)居中)。 |
| ③后臺(tái)Activity-已經(jīng)被暫停的Activity,比如執(zhí)行了onStop()(優(yōu)先級(jí)最低)。當(dāng)系統(tǒng)內(nèi)存不足時(shí),系統(tǒng)會(huì)按照優(yōu)先級(jí)由低到高去殺死Activity所在的進(jìn)程,通過(guò)onSaveInstanceState存儲(chǔ)數(shù)據(jù),通過(guò)onRestoreInstanceState 恢復(fù)數(shù)據(jù),如果一個(gè)進(jìn)程沒(méi)有四大組件作為依附,那么這個(gè)進(jìn)程很快就會(huì)被殺死。 |
- 根據(jù)上述的分析,系統(tǒng)配置發(fā)生改變后,系統(tǒng)會(huì)重新創(chuàng)建Activity,那么是否可以不重新創(chuàng)建Activity呢?
答案是有的,那就是在清單文件中為相應(yīng)的Activity配置configChanges屬性,并添加相應(yīng)值

| 防止Activity重新創(chuàng)建,android:configChanges時(shí)還需注意的點(diǎn) |
|---|
| 1. 不設(shè)置Activity的android:configChanges時(shí)。切屏?xí)忠淮握{(diào)用整個(gè)生命周期,切橫屏?xí)r會(huì)運(yùn)行一次,切豎屏?xí)r會(huì)運(yùn)行兩次 |
| 2. 設(shè)置Activity的android:configChanges="orientation"時(shí),切屏還是會(huì)又一次調(diào)用整個(gè)生命周期,切橫、豎屏?xí)r僅僅會(huì)運(yùn)行一次 |
| 3. 設(shè)置Activity的android:configChanges="orientation丨keyboardHidden"時(shí),切屏不會(huì)又一次調(diào)用整個(gè)生命周期,僅僅會(huì)運(yùn)行onConfigurationChanged方法 |
| 4. 可是。自從Android 3.2(API 13),在設(shè)置Activity的android:configChanges="orientation丨keyboardHidden"后。還是一樣會(huì)又一次調(diào)用各個(gè)生命周期的。由于screensize也開(kāi)始跟著設(shè)備的橫豎切換而改變。所以在AndroidManifest.xml里設(shè)置的MiniSdkVersion和 TargetSdkVersion屬性大于等于13的情況下,假設(shè)你想阻止程序在執(zhí)行時(shí)又一次載入Activity,除了設(shè)置"orientation"。 你還必須設(shè)置" screenSize"。 |
-
android:configChanges配置的屬性不僅局限于此,下圖為android:configChanges可配置的所有屬性內(nèi)容:
圖3.2.1.2 android:configChanges可配置的屬性
三、Activity任務(wù)與任務(wù)管理?xiàng)?/h2>
1、任務(wù)與任務(wù)棧概述
任務(wù)是指在執(zhí)行特定作業(yè)時(shí)與用戶交互的一系列 Activity。 這些 Activity 按照各自的打開(kāi)順序排列在堆棧(即任務(wù)棧)中。
- 設(shè)備主屏幕是大多數(shù)任務(wù)的起點(diǎn)。當(dāng)用戶觸摸應(yīng)用啟動(dòng)器中的圖標(biāo)(或主屏幕上的快捷方式)時(shí),該應(yīng)用的任務(wù)將出現(xiàn)在前臺(tái)。 如果應(yīng)用不存在任務(wù)(應(yīng)用最近未曾使用),則會(huì)創(chuàng)建一個(gè)新任務(wù),并且該應(yīng)用的“主”Activity 將作為堆棧中的根 Activity 打開(kāi)。
當(dāng)前 Activity 啟動(dòng)另一個(gè) Activity 時(shí),該新 Activity 會(huì)被推送到堆棧頂部,成為焦點(diǎn)所在。 前一個(gè) Activity 仍保留在堆棧中,但是處于停止?fàn)顟B(tài)。Activity 停止時(shí),系統(tǒng)會(huì)保持其用戶界面的當(dāng)前狀態(tài)。 用戶按“返回”按鈕時(shí),當(dāng)前 Activity 會(huì)從堆棧頂部彈出(Activity 被銷(xiāo)毀),而前一個(gè) Activity 恢復(fù)執(zhí)行(恢復(fù)其 UI 的前一狀態(tài))。 堆棧中的 Activity 永遠(yuǎn)不會(huì)重新排列,僅推入和彈出堆棧:由當(dāng)前 Activity 啟動(dòng)時(shí)推入堆棧;用戶使用“返回”按鈕退出時(shí)彈出堆棧。 因此,返回棧以“后進(jìn)先出”對(duì)象結(jié)構(gòu)運(yùn)行。 下圖 通過(guò)時(shí)間線顯示 Activity 之間的進(jìn)度以及每個(gè)時(shí)間點(diǎn)的當(dāng)前返回棧,直觀呈現(xiàn)了這種行為。

2、任務(wù)棧關(guān)聯(lián)
關(guān)聯(lián)指 Activity 優(yōu)先屬于哪個(gè)任務(wù)。默認(rèn)情況下,同一應(yīng)用中的所有 Activity 彼此關(guān)聯(lián)。 因此,默認(rèn)情況下,同一應(yīng)用中的所有 Activity 優(yōu)先位于相同任務(wù)中。 不過(guò),可以修改 Activity 的默認(rèn)關(guān)聯(lián)。 在不同應(yīng)用中定義的 Activity 可以共享關(guān)聯(lián),或者可為在同一應(yīng)用中定義的 Activity 分配不同的任務(wù)關(guān)聯(lián)。
- 可以使用activity元素的 taskAffinity屬性修改任何給定 Activity 的關(guān)聯(lián)。
taskAffinity 屬性取字符串值,該值必須不同于在AndroidManifest.xml文件中聲明的默認(rèn)軟件包名稱(chēng),因?yàn)橄到y(tǒng)使用該名稱(chēng)標(biāo)識(shí)應(yīng)用的默認(rèn)任務(wù)關(guān)聯(lián)。
在兩種情況下,關(guān)聯(lián)會(huì)起作用:
啟動(dòng) Activity 的 Intent 包含 FLAG_ACTIVITY_NEW_TASK標(biāo)志。
-
Activity 將其allowTaskReparenting 屬性設(shè)置為 true。
在這種情況下,Activity 可以從其啟動(dòng)的任務(wù)移動(dòng)到與其具有關(guān)聯(lián)的任務(wù)(如果該任務(wù)出現(xiàn)在前臺(tái))。
3、清理任務(wù)棧
如果用戶長(zhǎng)時(shí)間離開(kāi)應(yīng)用,則系統(tǒng)會(huì)清除應(yīng)用下所有除 Activity 的所有 Activity 。 當(dāng)用戶再次返回到任務(wù)時(shí),僅恢復(fù)根 Activity。系統(tǒng)這樣做的原因是,經(jīng)過(guò)很長(zhǎng)一段時(shí)間后,用戶可能已經(jīng)放棄之前執(zhí)行的操作,返回到任務(wù)是要開(kāi)始執(zhí)行新的操作。
可以使用下列幾個(gè) Activity 屬性修改此行為:
alwaysRetainTaskState
如果在任務(wù)的根 Activity 中將此屬性設(shè)置為 true`,則不會(huì)發(fā)生剛才所述的默認(rèn)行為。即使在很長(zhǎng)一段時(shí)間后,任務(wù)仍將所有 Activity 保留在其堆棧中。clearTaskOnLaunch
如果在任務(wù)的根 Activity 中將此屬性設(shè)置為 "true",則每當(dāng)用戶離開(kāi)任務(wù)然后返回時(shí),系統(tǒng)都會(huì)將堆棧清除到只剩下根 Activity。 換而言之,它與 alwaysRetainTaskState 正好相反。 即使只離開(kāi)任務(wù)片刻時(shí)間,用戶也始終會(huì)返回到任務(wù)的初始狀態(tài)。finishOnTaskLaunch
此屬性類(lèi)似于 clearTaskOnLaunch,但它對(duì)單個(gè) Activity 起作用,而非整個(gè)任務(wù)。 此外,它還有可能會(huì)導(dǎo)致任何 Activity 停止,包括根 Activity。 設(shè)置為 true 時(shí),Activity 仍是任務(wù)的一部分,但是僅限于當(dāng)前會(huì)話。如果用戶離開(kāi)然后返回任務(wù),則任務(wù)將不復(fù)存在。
四、Activity的啟動(dòng)模式
在AndroidManifest.xml文件中為Activity配置相應(yīng)的launchMode屬性可以設(shè)置Activity的啟動(dòng)模式,共如下四種模式:
- standard
- singleTask
- singleTop
- singleInstance
接下來(lái)對(duì)四種啟動(dòng)模式進(jìn)行詳細(xì)分析。
1、standard模式
標(biāo)準(zhǔn)啟動(dòng)模式:
該模式下每次啟動(dòng)Activity都會(huì)重新創(chuàng)建Activity實(shí)例,在這種模式下誰(shuí)啟動(dòng)了這個(gè)Actvitiy,那么這個(gè)Activity與被啟動(dòng)的Activity位于啟動(dòng)它的Activity的棧中。
但是在代碼中采用ApplicationContext去開(kāi)啟Activity時(shí)(例如執(zhí)行如下代碼時(shí)):
getApplication().startActivity(new Intent(HomeActivity.this,MainActivity.class));
會(huì)報(bào)如下所示的錯(cuò)誤:

- 分析:非Activity類(lèi)型的Context(如ApplicationContext)并沒(méi)有任務(wù)棧。
- 解決方案:為待啟動(dòng)的Activity指定FLAG_ACTIVITY_NEW_TASK標(biāo)記,那么通過(guò)這種方式啟動(dòng)的Activity就會(huì)新開(kāi)啟一個(gè)任務(wù)棧。
2、singleTop模式
棧頂復(fù)用模式:
該模式下如果Activity已經(jīng)位于棧頂,那么該Activity不會(huì)重新創(chuàng)建,同時(shí)它的OnNewIntent方法會(huì)被調(diào)用,通過(guò)方法的參數(shù)可以取出其中的信息,并且在這種模式如果這個(gè)Actvitiy不位于棧頂,那么這個(gè)Activity依然會(huì)被重新創(chuàng)建。

3、singleTask模式
棧內(nèi)復(fù)用模式:
只要Activity實(shí)例在一個(gè)棧中存在,那么再次啟動(dòng)該Activity時(shí)不會(huì)重新創(chuàng)建實(shí)例,并且系統(tǒng)也會(huì)回調(diào)其OnNewIntent方法,并且該模式下有清除棧頂實(shí)例的效果,即會(huì)將與singleTask模式下的該Activity同一個(gè)棧中的棧頂?shù)乃蠥ctivity實(shí)例全部出棧。
4、singleInstance模式
單實(shí)例模式:
單實(shí)例模式具備singleTask模式的所有的特性,此外具有此模式的Activity只能單獨(dú)位于一個(gè)任務(wù)棧中。
在這里,我們以HomeActivity、MainActivity、DetailActivity三個(gè)Activity演示singleInstance模式下的頁(yè)面跳轉(zhuǎn),其中MainActivity為singleInstance模式,其他兩個(gè)頁(yè)面為standard模式,如下圖演示效果:

在每個(gè)Activity中我們調(diào)用getTaskId(),如下圖打印其棧Id,我們可以查看到standard模式的HomeActivity與DetailActivity都位于同一個(gè)棧中,而singleInstance模式下的MainActivity單獨(dú)位于一個(gè)棧中,綜上所述,系統(tǒng)會(huì)為singleInstance模式下的Activity創(chuàng)建一個(gè)新的任務(wù)棧。

五、Activity的啟動(dòng)方式
Activity的啟動(dòng)方式可以分為顯示啟動(dòng)和隱式啟動(dòng),當(dāng)兩者共存時(shí)使用的是顯示啟動(dòng),而隱式啟動(dòng)不需要指定組件信息。兩者使用其中一種即可。那么在之后的文章中深入探究Intent以及IntentFilter在顯示、隱式開(kāi)啟Activity及其他組件中的作用。
1、顯示啟動(dòng)
顯示啟動(dòng)需要intent指定被啟動(dòng)對(duì)象的組件信息,包括包名和類(lèi)名。顯示啟動(dòng)

2、隱式啟動(dòng)
隱式啟動(dòng)調(diào)用需要Intent能夠匹配目標(biāo)組件的IntentFilter中所設(shè)置的過(guò)濾信息,如果不匹配將無(wú)法啟動(dòng)目標(biāo)Activity。IntentFilter中過(guò)濾的信息有action、category、data。
- 使用隱式Intent的時(shí)候,系統(tǒng)通過(guò)將Intent對(duì)象中的IntentFilter與組件在AndroidManifest.xml或者代碼中動(dòng)態(tài)聲明的IntentFilter進(jìn)行比較,從而找到要啟動(dòng)的相應(yīng)組件。如果組件的IntentFilter與Intent中的IntentFilter正好匹配,系統(tǒng)就會(huì)啟動(dòng)該組件,并把Intent傳遞給它。如果有多個(gè)組件同時(shí)匹配到了,系統(tǒng)則會(huì)彈出一個(gè)選擇框,讓用戶選擇使用哪個(gè)應(yīng)用去處理這個(gè)Intent,比如有時(shí)候點(diǎn)擊一個(gè)網(wǎng)頁(yè)鏈接,會(huì)彈出多個(gè)應(yīng)用,讓用戶選擇用哪個(gè)瀏覽器去打開(kāi)該鏈接,就是這種情況。
隱式啟動(dòng)Activity的代碼如下所示:
Intent intent = new Intent(Intent.ACTION_DIAL);
Uri data = Uri.parse("tel:" + "183xxxxxxxx");
intent.setData(data);
startActivity(intent);
