Android 四大组件之 Activity
什么是 Activity?
Activity 是 Android 的四大組件之一,是用戶操作的可視化界面,它為用戶提供了一個完成操作指令的窗口。
當(dāng)我們創(chuàng)建完 Activity 之后,需要調(diào)用 setContentView(view) 方法來完成界面的顯示,以此來為用戶提供交互的入口。在 Android App 中只要能看見的幾乎都要依托于 Activity,所以 Activity 是在開發(fā)中使用最頻繁的一種組件。
生命周期
生命周期就是 Activity 從開始到結(jié)束所經(jīng)歷的各個狀態(tài),從一個狀態(tài)到另一個狀態(tài)的轉(zhuǎn)變,從無到有再到無,這樣一個過程中所經(jīng)歷的狀態(tài)就叫做生命周期。
Acitivity 本質(zhì)上有四種狀態(tài):
- 運(yùn)行:如果一個活動被移到了前臺(活動棧頂部)。
- 暫停:如果一個活動被另一個非全屏的活動所覆蓋(比如一個 Dialog),那么該活動就失去了焦點(diǎn),它將會暫停(但它仍然保留所有的狀態(tài)和成員信息,并且仍然是依附在 WindowsManager 上),在系統(tǒng)內(nèi)存積極缺乏的時候會將它殺死。
- 停止:如果一個活動被另一個全屏活動完全覆蓋,那么該活動處于停止?fàn)顟B(tài)(狀態(tài)和成員信息會保留,但是 Activity 已經(jīng)不再依附于 WindowManager 了)。同時,在系統(tǒng)缺乏資源的時候會將它殺死(它會比暫停狀態(tài)的活動先殺死)。
- 重啟:如果一個活動在處于停止或者暫停的狀態(tài)下,系統(tǒng)內(nèi)存缺乏時會將其結(jié)束(finish)或者殺死(kill)。這種非正常情況下,系統(tǒng)在殺死或者結(jié)束之前會調(diào)用 onSaveInstanceState() 方法來保存信息,同時,當(dāng) Activity 被移動到前臺時,重新啟動該Activity并調(diào)用 onRestoreInstanceState() 方法加載保留的信息,以保持原有的狀態(tài)。
在上面的四中常有的狀態(tài)之間,還有著其他的生命周期來作為不同狀態(tài)之間的過度,用于在不同的狀態(tài)之間進(jìn)行轉(zhuǎn)換。
正常情況下的生命周期:
-
onCreate():與 onDestroy() 配對,表示 Activity 正在被創(chuàng)建,這是生命周期的第一個方法。
在這個方法中可以做一些初始化的工作(加載布局資源、初始化 Activity 所需要的數(shù)據(jù)等),耗時的工作在異步線程上完成。
-
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)這種情況。
-
onStart():與 onStop() 配對,表示 Activity 正在被啟動,并且即將開始。
但是這個時候要注意它與 onResume() 的區(qū)別。兩者都表示 Activity 可見,但是 onStart() 時 Activity 還正在加載其他內(nèi)容,正在向我們展示,用戶還無法看到,即無法交互。
-
onResume():與 onPause() 配對,表示 Activity 已經(jīng)創(chuàng)建完成,并且可以開始活動了,這個時候用戶已經(jīng)可以看到界面了,并且即將與用戶交互(完成該周期之后便可以響應(yīng)用戶的交互事件了)。
-
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í)行。
-
onStop():與 onStart() 配對,表示 Activity 即將停止,可以做一些稍微重量級的回收工作,同樣也不能太耗時(可以比 onPause 稍微好一點(diǎn))。
-
onDestroy():與 onCreate() 配對,表示 Activity 即將被銷毀,這是 Activity 生命周期的最后一個回調(diào),我們可以做一些回收工作和最終的資源釋放(如 Service、BroadReceiver、Map 等)。
啟動模式
Activity 的啟動模式有4種,分別是 Standard、SingleTop、SingleTask、SingleInstance。可以在 AndroidMainifest.xml 文件中指定每一個 Activity 的啟動模式。
一個 Android 應(yīng)用一般都會有多個 Activity,系統(tǒng)會通過任務(wù)棧來管理這些 Activity,棧是一種后進(jìn)先出的集合,當(dāng)前的 Activity 就在棧頂,按返回鍵,棧頂 Activity 就會退出。Activity 啟動模式不同,系統(tǒng)通過任務(wù)棧管理 Activity 的方式也會不同,以下將分別介紹。
- Standard
Standard 模式是 Android 的默認(rèn)啟動模式,你不在配置文件中做任何設(shè)置,那么這個 Activity 就是 Standard 模式。這種模式下,Activity 可以有多個實(shí)例,每次啟動 Activity,無論任務(wù)棧中是否已經(jīng)有這個 Activity 的實(shí)例,系統(tǒng)都會創(chuàng)建一個新的 Activity 實(shí)例。
- SingleTop
SingleTop 模式和 Standard 模式非常相似,主要區(qū)別就是當(dāng)一個 SingleTop 模式的 Activity 已經(jīng)位于任務(wù)棧的棧頂,再去啟動它時,不會再創(chuàng)建新的實(shí)例。如果不位于棧頂,就會創(chuàng)建新的實(shí)例。
- SingleTask
SingleTask 模式的 Activity 在同一個 Task 內(nèi)只有一個實(shí)例。如果 Activity 已經(jīng)位于棧頂,系統(tǒng)不會創(chuàng)建新的 Activity 實(shí)例,和 SingleTop 模式一樣。但 Activity 已經(jīng)存在但不位于棧頂時,系統(tǒng)就會把該 Activity 移到棧頂,并把它上面的 Activity 出棧。
- SingleInstance
SingleInstance 模式也是單例的,但和 SingleTask 不同,SingleTask 只是任務(wù)棧內(nèi)單例,系統(tǒng)里是可以有多個 SingleTask Activity 實(shí)例的,而 SingleInstance Activity 在整個系統(tǒng)里只有一個實(shí)例,啟動一個SingleInstance 的 Activity 時,系統(tǒng)會創(chuàng)建一個新的任務(wù)棧,并且這個任務(wù)棧只有他一個 Activity。
SingleInstance 模式并不常用,如果我們把一個 Activity 設(shè)置為 SingleInstance 模式,你會發(fā)現(xiàn)它啟動時會慢一些,切換效果不好,影響用戶體驗(yàn)。它往往用于多個應(yīng)用之間,例如一個電視 Launcher 里的 Activity,通過遙控器某個鍵在任何情況可以啟動,這個 Activity 就可以設(shè)置為 SingleInstance 模式,當(dāng)在某應(yīng)用中按鍵啟動這個 Activity,處理完后按返回鍵,就會回到之前啟動它的應(yīng)用,不影響用戶體驗(yàn)。
任務(wù)與返回棧
一個應(yīng)用程序當(dāng)中通常會包含多個 Activity,每個 Activity 都應(yīng)該設(shè)計(jì)成可以執(zhí)行用戶特定的操作,并且能夠啟動其他 Activity。比如,電子郵件應(yīng)用可能有一個 Activity 顯示新郵件列表。用戶選擇某個郵件時,會打開一個新的 Activity 來查看該郵件。
一個 Activity 甚至可以啟動設(shè)備上其他應(yīng)用中的 Activity。比如,如果當(dāng)前應(yīng)用想要發(fā)送電子郵件,就可以發(fā)送一個 Intent 添加一些數(shù)據(jù)(如郵箱和郵件內(nèi)容等)。然后,系統(tǒng)將打開其他應(yīng)用中已經(jīng)聲明可以處理該 Intent 的 Activity,如果有多個系統(tǒng)則讓用戶選擇要使用的 Activity。即使這兩個 Activity 可能來自不同的應(yīng)用,但是 Android 仍會將 Activity 保留在相同的任務(wù)中,以維護(hù)這種無縫的用戶體驗(yàn)。
任務(wù)是指在執(zhí)行特定作業(yè)時與用戶交互的一系列 Activity。 這些 Activity 按照各自的打開順序排列在堆棧(即返回棧)中。
設(shè)備主屏幕是大多數(shù)任務(wù)的起點(diǎn)。當(dāng)用戶觸摸應(yīng)用啟動器中的圖標(biāo)(或主屏幕上的快捷方式)時,該應(yīng)用的任務(wù)將出現(xiàn)在前臺。 如果應(yīng)用不存在任務(wù)(應(yīng)用最近未曾使用),則會創(chuàng)建一個新任務(wù),并且該應(yīng)用的“主” Activity 將作為堆棧中的根 Activity 打開。
當(dāng)前 Activity 啟動另一個 Activity 時,該新 Activity 會被推送到堆棧頂部,并獲取焦點(diǎn)。前一個 Activity 仍保留在堆棧中,但是處于停止?fàn)顟B(tài)。Activity 停止時,系統(tǒng)會保持其用戶界面的當(dāng)前狀態(tài)。 用戶按「返回」按鈕時,當(dāng)前 Activity 會從堆棧頂部彈出(Activity 被銷毀),而前一個 Activity 恢復(fù)執(zhí)行(恢復(fù)其 UI 的前一狀態(tài))。
堆棧中的 Activity 永遠(yuǎn)不會重新排列,僅推入和彈出堆棧:由當(dāng)前 Activity 啟動時推入堆棧;用戶使用「返回」按鈕退出時彈出堆棧。 因此,返回棧以「后進(jìn)先出」對象結(jié)構(gòu)運(yùn)行。 如下圖:
如果用戶繼續(xù)按「返回」,堆棧中的相應(yīng) Activity 就會彈出,以顯示前一個 Activity,直到用戶返回主屏幕為止(或者,返回任務(wù)開始時正在運(yùn)行的任意 Activity)。 當(dāng)所有 Activity 均從堆棧中移除后,任務(wù)即不復(fù)存在。
任務(wù)是一個有機(jī)整體,當(dāng)用戶開始新任務(wù)或通過「主頁」按鈕轉(zhuǎn)到主屏幕時,可以移動到「后臺」。 盡管在后臺時,該任務(wù)中的所有 Activity 全部停止,但是任務(wù)的返回棧仍舊不變,也就是說,當(dāng)另一個任務(wù)發(fā)生時,該任務(wù)僅僅失去焦點(diǎn)而已,如下圖所示。然后,任務(wù)可以返回到「前臺」,用戶就能夠回到離開時的狀態(tài)。
例如,假設(shè)當(dāng)前任務(wù)(任務(wù) A)的堆棧中有三個 Activity,即當(dāng)前 Activity 下方還有兩個 Activity。 用戶先按「主頁」按鈕,然后從應(yīng)用啟動器啟動新應(yīng)用。 顯示主屏幕時,任務(wù) A 進(jìn)入后臺。新應(yīng)用啟動時,系統(tǒng)會使用自己的 Activity 堆棧為該應(yīng)用啟動一個任務(wù)(任務(wù) B)。與該應(yīng)用交互之后,用戶再次返回主屏幕并選擇最初啟動任務(wù) A 的應(yīng)用。現(xiàn)在,任務(wù) A 出現(xiàn)在前臺,其堆棧中的所有三個 Activity 保持不變,而位于堆棧頂部的 Activity 則會恢復(fù)執(zhí)行。 此時,用戶還可以通過轉(zhuǎn)到主屏幕并選擇啟動該任務(wù)的應(yīng)用圖標(biāo)(或者,通過從「屏幕預(yù)覽」擇該應(yīng)用的任務(wù))切換回任務(wù) B。這就是 Android 系統(tǒng)中的多任務(wù)的場景。
無論 Activity 是在新任務(wù)中啟動,還是在與啟動 Activity 相同的任務(wù)中啟動,用戶按「返回」按鈕始終會轉(zhuǎn)到前一個 Activity。 但是,如果啟動指定 singleTask 啟動模式的 Activity,則當(dāng)某后臺任務(wù)中存在該 Activity 的實(shí)例時,整個任務(wù)都會轉(zhuǎn)移到前臺。此時,返回棧包括上移到堆棧頂部的任務(wù)中的所有 Activity,如下圖:
保存與恢復(fù)
Activity 為我們提供了兩個回調(diào)方法 onSaveInstanceState() 和 onRestoreInstanceState() 用于當(dāng) Activity 在不是用戶主動意識關(guān)閉的情況下來進(jìn)行頁面數(shù)據(jù)的保存和恢復(fù)。
那么那些情況下 onSaveInstanceState() 會被調(diào)用呢?分別有以下幾種情況:
- 當(dāng)用戶按下 Home 鍵 App 處于后臺,此時會調(diào)用 onSaveInstanceState() 方法。
- 當(dāng)用戶按下電源鍵時,會調(diào)用 onSaveInstanceState() 方法。
- 當(dāng) Activity 進(jìn)行橫豎屏切換的時候也會調(diào)用 onSaveInstanceState() 方法。
- 從 AActivity 跳轉(zhuǎn)到 BActivity 的時候 AActivity 也會調(diào)用 onSaveInstanceState() 方法。
雖然以上四種情況會執(zhí)行 onSaveInstanceState() 方法 但是并不是都會執(zhí)行 onRestoreInstanceState() 方法,只有第三種情況會調(diào)用 onRestoreInstanceState(),因?yàn)楫?dāng) Activity 橫豎屏切換的時候會重新走一遍生命周期,所以 Activity 會被銷毀創(chuàng)建,由此會執(zhí)行 onRestoreInstanceState() 方法。
也就是說 onSaveInstanceState 和 onRestoreInstanceState 并不是一定成雙出現(xiàn)的,終于當(dāng) Activity 真正的被銷毀的時候才會執(zhí)行 onRestoreInstanceState()。
而其他情況 Activity 只是暫居后臺,并沒有被銷毀,所以系統(tǒng)不會調(diào)用 onRestoreInstanceState()。
保存數(shù)據(jù):
public void onSaveInstanceState(Bundle savedInstanceState) {// 保存數(shù)據(jù)savedInstanceState.putString("data", "這是保存的數(shù)據(jù)");super.onSaveInstanceState(savedInstanceState); } 復(fù)制代碼恢復(fù)數(shù)據(jù):
protected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);// 恢復(fù)數(shù)據(jù)if (savedInstanceState != null) {String data = savedInstanceState.getInt("data");} } public void onRestoreInstanceState(Bundle savedInstanceState) {super.onCreate(savedInstanceState);// 也可以在 onRestoreInstanceState() 方法中恢復(fù)數(shù)據(jù)if (savedInstanceState != null) {String data = savedInstanceState.getInt("data");} } 復(fù)制代碼Intent
Intent 分兩種,顯式 Intent 和隱式 Intent。如果一個 Intent 明確指定了要啟動的組件的完整類名,那么這個 Intent 就是顯式 Intent,否則就是隱式 Intent。
當(dāng)我們用一個顯式 Intent 去啟動組件時,Android 會根據(jù) Intent 對象所提供的 component name 直接找到要啟動的組件,當(dāng)我們用一個隱式的 Intent 去啟動組件時,Android 系統(tǒng)就無法直接知道要啟動的組件名稱了。
顯式 Intent
Intent intent = new Intent(this, xxx.class); startActivity(intent); 復(fù)制代碼隱式 Intent
使用隱式 Intent 之前需要在 AndroidManifest.xml 中對標(biāo)簽增加設(shè)置。
<activity android:name=".ui.activity.IntentActivity"><intent-filter><action android:name="com.jeanboy.action.TEST" /></intent-filter> </activity> 復(fù)制代碼使用隱式 Intent 跳轉(zhuǎn) Activity。
Intent intent = new Intent("com.jeanboy.action.TEST"); startActivity(intent); 復(fù)制代碼Intent Filter
如果 Intent 中的存在 category 那么所有的 category 都必須和 Activity 過濾規(guī)則中的 category 相同。才能和這個 Activity 匹配。Intent 中的 category 數(shù)量可能少于 Activity 中配置的 category 數(shù)量,但是 Intent 中的這 category 必須和 Activity 中配置的 category 相同才能匹配。
<activity android:name=".ui.activity.IntentActivity"><intent-filter><action android:name="com.jeanboy.action.TEST" /><category android:name = "android.intent.category.DEFAULT" /><category android:name="aaa.bb.cc"/></intent-filter> </activity> 復(fù)制代碼運(yùn)行以下代碼可以匹配到 IntentActivity:
Intent intent = new Intent("com.jeanboy.action.TEST"); intent.addCategory("aaa.bb.cc"); startActivity(intent); 復(fù)制代碼只通過 category 匹配是無法匹配到 IntentActivity 的,因?yàn)?category 屬性是一個執(zhí)行 Action 的附加信息。
URL Scheme
Android 中的 Scheme 是一種頁面內(nèi)跳轉(zhuǎn)協(xié)議,是一種非常好的實(shí)現(xiàn)機(jī)制。通過定義自己的 Scheme 協(xié)議,可以非常方便跳轉(zhuǎn) App 中的各個頁面。
使用場景:
- 通過小程序,利用 Scheme 協(xié)議打開原生 App。
- H5 頁面點(diǎn)擊錨點(diǎn),根據(jù)錨點(diǎn)具體跳轉(zhuǎn)路徑 App 端跳轉(zhuǎn)具體的頁面。
- App 端收到服務(wù)器端下發(fā)的 Push 通知欄消息,根據(jù)消息的點(diǎn)擊跳轉(zhuǎn)路徑跳轉(zhuǎn)相關(guān)頁面。
- App 根據(jù)URL跳轉(zhuǎn)到另外一個 App 指定頁面。
- 通過短信息中的 URL 打開原生 App。
Scheme 路徑的規(guī)則:
<scheme> :// <host> : <port> [<path>|<pathPrefix>|<pathPattern>]
設(shè)置 Scheme
在 AndroidManifest.xml 中對標(biāo)簽增加設(shè)置 Scheme。
<activityandroid:name=".ui.activity.SchemeActivity"android:screenOrientation="portrait"><!--Android 接收外部跳轉(zhuǎn)過濾器--><!--要想在別的 App 上能成功調(diào)起 App,必須添加 intent 過濾器--><intent-filter><!--協(xié)議部分配置,注意需要跟 web 配置相同--><!--協(xié)議部分,隨便設(shè)置 aa://bb:1024/from?type=jeanboy--><data android:scheme="aa"android:host="bb"android:port="1024"android:path="/from"/><!--下面這幾行也必須得設(shè)置--><category android:name="android.intent.category.DEFAULT" /><!--表示 Activity 允許通過網(wǎng)絡(luò)瀏覽器啟動,以顯示鏈接方式引用,如圖像或電子郵件--><category android:name="android.intent.category.BROWSABLE" /><action android:name="android.intent.action.VIEW" /></intent-filter> </activity> 復(fù)制代碼原生調(diào)用:
Uri uri = Uri.parse("aa://bb:1024/from?type=jeanboy"); Intent intent = new Intent(Intent.ACTION_VIEW, uri); startActivity(intent); 復(fù)制代碼網(wǎng)頁調(diào)用:
<a href="aa://bb:1024/from?type=jeanboy">打開 App</a> 復(fù)制代碼在 SchemeActivity 中可以處理 Scheme 跳轉(zhuǎn)的參數(shù):
public class SchemeActivity extends AppCompatActivity {protected void onCreate(@Nullable Bundle savedInstanceState) {super.onCreate(savedInstanceState);Uri uri = getIntent().getData();if (uri != null) {//獲取指定參數(shù)值String type = uri.getQueryParameter("type");Log.e("SchemeActivity", "type:" + type);if(type.equals("jeanboy")){ActivityUtils.startActivity(XXXActivity.class);}else if(type.equals("main")){ActivityUtils.startActivity(MainActivity.class);}}finish();} } 復(fù)制代碼如何判斷一個 Scheme 是否有效:
PackageManager packageManager = getPackageManager(); Uri uri = Uri.parse("aa://bb:1024/from?type=jeanboy"); Intent intent = new Intent(Intent.ACTION_VIEW, uri); List<ResolveInfo> activities = packageManager.queryIntentActivities(intent, 0); boolean isValid = !activities.isEmpty(); if (isValid) {startActivity(intent); } 復(fù)制代碼startActivityForResult()
如果想在 Activity 中得到新打開 Activity 關(guān)閉后返回的數(shù)據(jù),需要使用系統(tǒng)提供的 startActivityForResult() 方法打開新的 Activity,新的 Activity 關(guān)閉后會向前面的 Activity 傳回?cái)?shù)據(jù)。
public void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);//...int resultCode = 1;startActivityForResult(new Intent(this, OtherActivity.class), resultCode); }/*** 接收 OtherActivity 返回的數(shù)據(jù)*/ protected void onActivityResult(int requestCode, int resultCode, Intent data) {String result = data.getExtras().getString("result"); } 復(fù)制代碼為了得到傳回的數(shù)據(jù),必須在前面的 Activity 中重寫 onActivityResult() 方法。
Intent intent = new Intent(); // 把返回?cái)?shù)據(jù)存入 Intent intent.putExtra("result", "我是返回?cái)?shù)據(jù)"); // 設(shè)置返回?cái)?shù)據(jù) setResult(RESULT_OK, intent); // 關(guān)閉當(dāng)前 Activity finish(); 復(fù)制代碼常見面試題
-
啟動一個 Activity 的生命周期?
例如:A 啟動 B,生命周期如下
A: ==> onCreate() A: ==> onStart() A: ==> onResume() A: ==> onPause()B: ==> onCreate() B: ==> onStart() B: ==> onResume()A: ==> onStop() 復(fù)制代碼 -
下拉通知欄對生命周期的影響?
沒有影響!
-
AlertDialog(對話框)對生命周期的影響?
沒有影響!
-
Toast 對生命周期的影響?
沒有影響!
-
透明主題的 Activity 對生命周期的影響?
A: ==> onCreate() A: ==> onStart() A: ==> onResume() 如果彈出透明 Activity A: ==> onPause() 復(fù)制代碼 -
屏幕旋轉(zhuǎn)對生命周期的影響?
沒有配置 configChanges:
A: ==> onCreate() A: ==> onStart() A: ==> onResume() A: ==> onPause() A: ==> onSaveInstanceState() A: ==> onStop() A: ==> onDestroy() 屏幕旋轉(zhuǎn)后 A: ==> onCreate() A: ==> onStart() A: ==> onRestoreInstanceState() A: ==> onResume() 復(fù)制代碼配置 configChanges 后:
A: ==> onCreate() A: ==> onStart() A: ==> onResume() A: ==> onConfigurationChanged() 復(fù)制代碼
參考資料
- Android 官方文檔 - Activity 生命周期
- Android 官方文檔 - 任務(wù)和返回棧
我的 GitHub
github.com/jeanboydev
我的公眾號
歡迎關(guān)注我的公眾號,分享各種技術(shù)干貨,各種學(xué)習(xí)資料,職業(yè)發(fā)展和行業(yè)動態(tài)。
技術(shù)交流群
歡迎加入技術(shù)交流群,來一起交流學(xué)習(xí)。
轉(zhuǎn)載于:https://juejin.im/post/5c8885bbf265da2deb6ae708
總結(jié)
以上是生活随笔為你收集整理的Android 四大组件之 Activity的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: ESMap+Html5+SpringBo
- 下一篇: 谷歌已推送 Android Q Beta