Android復(fù)習(xí)(六)核心組件—>Activity 任何和返回棧、進程和應(yīng)用生命周期、Parcelable和Bundle
了解任務(wù)和返回堆棧
任務(wù)是用戶在執(zhí)行某項工作時與之互動的一系列 Activity 的集合。這些 Activity 按照每個 Activity 打開的順序排列在一個返回堆棧中。例如,電子郵件應(yīng)用可能有一個 Activity 來顯示新郵件列表。當用戶選擇一封郵件時,系統(tǒng)會打開一個新的 Activity 來顯示該郵件。這個新的 Activity 會添加到返回堆棧中。如果用戶按返回按鈕,這個新的 Activity 即會完成并從堆棧中退出。通過以下視頻可以大致了解返回堆棧的工作原理。
大多數(shù)任務(wù)都從設(shè)備主屏幕上啟動。當用戶輕觸應(yīng)用啟動器中的圖標(或主屏幕上的快捷方式)時,該應(yīng)用的任務(wù)就會轉(zhuǎn)到前臺運行。如果該應(yīng)用沒有任務(wù)存在(應(yīng)用最近沒有使用過),則會創(chuàng)建一個新的任務(wù),并且該應(yīng)用的“主”Activity 將會作為堆棧的根 Activity 打開。
在當前 Activity 啟動另一個 Activity 時,新的 Activity 將被推送到堆棧頂部并獲得焦點。上一個 Activity 仍保留在堆棧中,但會停止。當 Activity 停止時,系統(tǒng)會保留其界面的當前狀態(tài)。當用戶按返回按鈕時,當前 Activity 會從堆棧頂部退出(該 Activity 銷毀),上一個 Activity 會恢復(fù)(界面會恢復(fù)到上一個狀態(tài))。堆棧中的 Activity 永遠不會重新排列,只會被送入和退出,在當前 Activity 啟動時被送入堆棧,在用戶使用返回按鈕離開時從堆棧中退出。因此,返回堆棧按照“后進先出”的對象結(jié)構(gòu)運作。圖 1 借助一個時間軸直觀地顯示了這種行為。該時間軸顯示了 Activity 之間的進展以及每個時間點的當前返回堆棧。
圖 1. 有關(guān)任務(wù)中的每個新 Activity 如何添加到返回堆棧的圖示。當用戶按返回按鈕時,當前 Activity 會銷毀,上一個 Activity 將恢復(fù)。
如果用戶繼續(xù)按返回,則堆棧中的 Activity 會逐個退出,以顯示前一個 Activity,直到用戶返回到主屏幕(或任務(wù)開始時運行的 Activity)。移除堆棧中的所有 Activity 后,該任務(wù)將不復(fù)存在。
圖 2. 兩個任務(wù):任務(wù) B 在前臺接收用戶互動,任務(wù) A 在后臺等待恢復(fù)。
任務(wù)是一個整體單元,當用戶開始一個新任務(wù)或通過主屏幕按鈕進入主屏幕時,任務(wù)可移至“后臺”。在后臺時,任務(wù)中的所有 Activity 都會停止,但任務(wù)的返回堆棧會保持不變,當其他任務(wù)啟動時,當前任務(wù)只是失去了焦點,如圖 2 所示。這樣一來,任務(wù)就可以返回到“前臺”,以便用戶可以從他們離開的地方繼續(xù)操作。舉例來說,假設(shè)當前任務(wù)(任務(wù) A)的堆棧中有 3 個 Activity,當前 Activity 下有 2 個 Activity。用戶按主屏幕按鈕,然后從應(yīng)用啟動器中啟動新應(yīng)用。主屏幕出現(xiàn)后,任務(wù) A 轉(zhuǎn)到后臺。當新應(yīng)用啟動時,系統(tǒng)會啟動該應(yīng)用的任務(wù)(任務(wù) B),該任務(wù)具有自己的 Activity 堆棧。與該應(yīng)用互動后,用戶再次返回到主屏幕并選擇最初啟動任務(wù) A 的應(yīng)用。現(xiàn)在,任務(wù) A 進入前臺,其堆棧中的所有三個 Activity 都完好如初,堆棧頂部的 Activity 恢復(fù)運行。此時,用戶仍可通過以下方式切換到任務(wù) B:轉(zhuǎn)到主屏幕并選擇啟動該任務(wù)的應(yīng)用圖標(或者從最近使用的應(yīng)用屏幕中選擇該應(yīng)用的任務(wù))。這就是在 Android 上進行多任務(wù)處理的一個例子。
注意:多個任務(wù)可以同時在后臺進行。但是,如果用戶同時運行很多后臺任務(wù),系統(tǒng)可能會為了恢復(fù)內(nèi)存而開始銷毀后臺 Activity,導(dǎo)致 Activity 狀態(tài)丟失。
圖 3. 單個 Activity 會被多次實例化。
由于返回堆棧中的 Activity 不會被重新排列,如果您的應(yīng)用允許用戶從多個 Activity 啟動特定的 Activity,系統(tǒng)便會創(chuàng)建該 Activity 的新實例并將其推送到堆棧中(而不是將該 Activity 的某個先前的實例移至堆棧頂部)。這樣一來,應(yīng)用中的一個 Activity 就可能被多次實例化(甚至是從其他任務(wù)對其進行實例化),如圖 3 所示。因此,如果用戶使用返回按鈕向后導(dǎo)航,Activity 的每個實例將按照它們被打開的順序顯示出來(每個實例都有自己的界面狀態(tài))。不過,如果您不希望某個 Activity 被實例化多次,可以修改此行為。有關(guān)如何實現(xiàn)此操作,將在后面的管理任務(wù)部分中討論。
Activity 和任務(wù)的默認行為總結(jié)如下:
- 當 Activity A 啟動 Activity B 時,Activity A 會停止,但系統(tǒng)會保留其狀態(tài)(例如滾動位置和輸入到表單中的文本)。如果用戶在 Activity B 中按返回按鈕,系統(tǒng)會恢復(fù) Activity A 及其狀態(tài)。
- 當用戶通過按主屏幕按鈕離開任務(wù)時,當前 Activity 會停止,其任務(wù)會轉(zhuǎn)到后臺。系統(tǒng)會保留任務(wù)中每個 Activity 的狀態(tài)。如果用戶稍后通過點按該任務(wù)的啟動器圖標來恢復(fù)該任務(wù),該任務(wù)會進入前臺并恢復(fù)堆棧頂部的 Activity。
- 如果用戶按返回按鈕,當前 Activity 將從堆棧中退出并銷毀。堆棧中的上一個 Activity 將恢復(fù)。Activity 被銷毀后,系統(tǒng)不會保留該 Activity 的狀態(tài)。
- Activity 可以多次實例化,甚至是從其他任務(wù)對其進行實例化。
導(dǎo)航設(shè)計
要詳細了解 Android 上的應(yīng)用導(dǎo)航如何運作,請參閱 Android 設(shè)計中的導(dǎo)航指南。
管理任務(wù)
如上文所述,Android 管理任務(wù)和返回堆棧的方式是將所有接連啟動的 Activity 放到同一任務(wù)和一個“后進先出”堆棧中,這對于大多數(shù)應(yīng)用都很有效,而且您不必擔心 Activity 如何與任務(wù)相關(guān)聯(lián),或者它們?nèi)绾未嬖谟诜祷囟褩V小2贿^,您可能需要決定是否要打破正常行為。或許您希望應(yīng)用中的某個 Activity 在啟動時開啟一個新的任務(wù)(而不是被放入當前的任務(wù)中),或者當您啟動某個 Activity 時,您希望調(diào)用它的一個現(xiàn)有實例(而不是在返回堆棧頂部創(chuàng)建一個新實例),或者您希望在用戶離開任務(wù)時清除返回堆棧中除根 Activity 以外的所有 Activity。
您可以借助 <activity> 清單元素中的屬性以及您傳遞給 startActivity() 的 intent 中的標記來實現(xiàn)上述目的。
在這方面,您可以使用的主要 <activity> 屬性包括:
taskAffinitylaunchModeallowTaskReparentingclearTaskOnLaunchalwaysRetainTaskStatefinishOnTaskLaunch
您可以使用的主要 intent 標記包括:
在下面幾節(jié)中,您將了解到如何使用這些清單屬性和 intent 標記來定義 Activity 與任務(wù)之間的關(guān)聯(lián)方式,以及它們在返回堆棧中的行為。
另外,下面還分別介紹了如何在最近使用的應(yīng)用屏幕中表示和管理任務(wù)與 Activity。有關(guān)詳情,請參閱“最近使用的應(yīng)用”屏幕。通常,您應(yīng)允許系統(tǒng)定義任務(wù)和 Activity 在最近使用的應(yīng)用屏幕中的表示方式,您無需修改此行為。
注意:大多數(shù)應(yīng)用不應(yīng)打破 Activity 和任務(wù)的默認行為。如果您確定需要讓 Activity 改變默認行為,請謹慎操作,并且務(wù)必要測試該 Activity 在以下情況下的可用性:啟動期間以及您通過返回按鈕從其他 Activity 和任務(wù)返回該 Activity 時。務(wù)必要測試是否存在可能與用戶預(yù)期的行為沖突的導(dǎo)航行為。
定義啟動模式
您可以通過啟動模式定義 Activity 的新實例如何與當前任務(wù)關(guān)聯(lián)。您可以通過兩種方式定義不同的啟動模式:
- 使用清單文件
當您在清單文件中聲明 Activity 時,您可以指定該 Activity 在啟動時如何與任務(wù)關(guān)聯(lián)。
- 使用 Intent 標記
當您調(diào)用
startActivity()時,可以在Intent中添加一個標記,用于聲明新 Activity 如何(或是否)與當前任務(wù)相關(guān)聯(lián)。
因此,如果 Activity A 啟動 Activity B,Activity B 可在其清單中定義如何與當前任務(wù)相關(guān)聯(lián)(如果關(guān)聯(lián)的話),Activity A 也可以請求 Activity B 應(yīng)該如何與當前任務(wù)關(guān)聯(lián)。如果兩個 Activity 都定義了 Activity B 應(yīng)如何與任務(wù)關(guān)聯(lián),將優(yōu)先遵循 Activity A 的請求(在 intent 中定義),而不是 Activity B 的請求(在清單中定義)。
注意:有些啟動模式可通過清單文件定義,但不能通過 intent 標記定義,同樣,有些啟動模式可通過 intent 標記定義,卻不能在清單中定義。
使用清單文件
在清單文件中聲明 Activity 時,可以使用 <activity> 元素的 launchMode 屬性指定 Activity 應(yīng)該如何與任務(wù)關(guān)聯(lián)。
launchMode 屬性說明了 Activity 應(yīng)如何啟動到任務(wù)中。您可以為 launchMode 屬性指定 4 種不同的啟動模式:
"standard"(默認模式)- 默認值。系統(tǒng)在啟動該 Activity 的任務(wù)中創(chuàng)建 Activity 的新實例,并將 intent 傳送給該實例。Activity 可以多次實例化,每個實例可以屬于不同的任務(wù),一個任務(wù)可以擁有多個實例。
"singleTop"- 如果當前任務(wù)的頂部已存在 Activity 的實例,則系統(tǒng)會通過調(diào)用其
onNewIntent()方法來將 intent 轉(zhuǎn)送給該實例,而不是創(chuàng)建 Activity 的新實例。Activity 可以多次實例化,每個實例可以屬于不同的任務(wù),一個任務(wù)可以擁有多個實例(但前提是返回堆棧頂部的 Activity 不是該 Activity 的現(xiàn)有實例)。例如,假設(shè)任務(wù)的返回堆棧包含根 Activity A 以及 Activity B、C 和位于頂部的 D(堆棧為 A-B-C-D;D 位于頂部)。收到以 D 類型 Activity 為目標的 intent。如果 D 采用默認的
"standard"啟動模式,則會啟動該類的新實例,并且堆棧將變?yōu)?A-B-C-D-D。但是,如果 D 的啟動模式為"singleTop",則 D 的現(xiàn)有實例會通過onNewIntent()接收 intent,因為它位于堆棧頂部,堆棧仍為 A-B-C-D。但是,如果收到以 B 類型 Activity 為目標的 intent,則會在堆棧中添加 B 的新實例,即使其啟動模式為"singleTop"也是如此。注意:創(chuàng)建 Activity 的新實例后,用戶可以按返回按鈕返回到上一個 Activity。但是,當由 Activity 的現(xiàn)有實例處理新 intent 時,用戶將無法通過按返回按鈕返回到
onNewIntent()收到新 intent 之前的 Activity 狀態(tài)。 "singleTask"- 系統(tǒng)會創(chuàng)建新任務(wù),并實例化新任務(wù)的根 Activity。但是,如果另外的任務(wù)中已存在該 Activity 的實例,則系統(tǒng)會通過調(diào)用其
onNewIntent()方法將 intent 轉(zhuǎn)送到該現(xiàn)有實例,而不是創(chuàng)建新實例。Activity 一次只能有一個實例存在。注意:雖然 Activity 在新任務(wù)中啟動,但用戶按返回按鈕仍會返回到上一個 Activity。
"singleInstance"- 與
"singleTask"相似,唯一不同的是系統(tǒng)不會將任何其他 Activity 啟動到包含該實例的任務(wù)中。該 Activity 始終是其任務(wù)唯一的成員;由該 Activity 啟動的任何 Activity 都會在其他的任務(wù)中打開。
再舉個例子,Android 瀏覽器應(yīng)用在 <activity> 元素中指定 singleTask 啟動模式,由此聲明網(wǎng)絡(luò)瀏覽器 Activity 應(yīng)始終在它自己的任務(wù)中打開。這意味著,如果您的應(yīng)用發(fā)出打開 Android 瀏覽器的 intent,系統(tǒng)不會將其 Activity 置于您的應(yīng)用所在的任務(wù)中,而是會為瀏覽器啟動一個新任務(wù),如果瀏覽器已經(jīng)有任務(wù)在后臺運行,則會將該任務(wù)轉(zhuǎn)到前臺來處理新 intent。
無論 Activity 是在新任務(wù)中啟動的,還是在和啟動它的 Activity 相同的任務(wù)中啟動,用戶按返回按鈕都會回到上一個 Activity。但是,如果您啟動了指定 singleTask 啟動模式的 Activity,而后臺任務(wù)中已存在該 Activity 的實例,則系統(tǒng)會將該后臺任務(wù)整個轉(zhuǎn)到前臺運行。此時,返回堆棧包含了轉(zhuǎn)到前臺的任務(wù)中的所有 Activity,這些 Activity 都位于堆棧的頂部。圖 4 展示了具體的情景。
圖 4. 采用“singleTask”啟動模式的 Activity 添加到返回堆棧的過程圖示。如果 Activity 已經(jīng)存在于某個具有自己的返回堆棧的后臺任務(wù)中,那么整個返回堆棧也會轉(zhuǎn)到前臺,覆蓋當前任務(wù)。
要詳細了解如何在清單文件中設(shè)置啟動模式,請參閱 <activity> 元素的說明文檔,里面詳細介紹了 launchMode 屬性和可接受的值。
注意:您通過 launchMode 屬性為 Activity 指定的行為,可被啟動 Activity 的 intent 所包含的標記替換。下一節(jié)將對此進行介紹。
使用 Intent 標記
啟動 Activity 時,您可以在傳送給 startActivity() 的 intent 中添加相應(yīng)的標記來修改 Activity 與其任務(wù)的默認關(guān)聯(lián)。您可以使用以下標記來修改默認行為:
FLAG_ACTIVITY_NEW_TASK- 在新任務(wù)中啟動 Activity。如果您現(xiàn)在啟動的 Activity 已經(jīng)有任務(wù)在運行,則系統(tǒng)會將該任務(wù)轉(zhuǎn)到前臺并恢復(fù)其最后的狀態(tài),而 Activity 將在
onNewIntent()中收到新的 intent。這與上一節(jié)中介紹的
"singleTask"launchMode值產(chǎn)生的行為相同。 FLAG_ACTIVITY_SINGLE_TOP- 如果要啟動的 Activity 是當前 Activity(即位于返回堆棧頂部的 Activity),則現(xiàn)有實例會收到對
onNewIntent()的調(diào)用,而不會創(chuàng)建 Activity 的新實例。這與上一節(jié)中介紹的
"singleTop"launchMode值產(chǎn)生的行為相同。 FLAG_ACTIVITY_CLEAR_TOP- 如果要啟動的 Activity 已經(jīng)在當前任務(wù)中運行,則不會啟動該 Activity 的新實例,而是會銷毀位于它之上的所有其他 Activity,并通過
onNewIntent()將此 intent 傳送給它的已恢復(fù)實例(現(xiàn)在位于堆棧頂部)。launchMode屬性沒有可產(chǎn)生此行為的值。FLAG_ACTIVITY_CLEAR_TOP最常與FLAG_ACTIVITY_NEW_TASK結(jié)合使用。將這兩個標記結(jié)合使用,可以查找其他任務(wù)中的現(xiàn)有 Activity,并將其置于能夠響應(yīng) intent 的位置。注意:如果指定 Activity 的啟動模式為
"standard",系統(tǒng)也會將其從堆棧中移除,并在它的位置啟動一個新實例來處理傳入的 intent。這是因為當啟動模式為"standard"時,始終會為新 intent 創(chuàng)建新的實例。
處理親和性
“親和性”表示 Activity 傾向于屬于哪個任務(wù)。默認情況下,同一應(yīng)用中的所有 Activity 彼此具有親和性。因此,在默認情況下,同一應(yīng)用中的所有 Activity 都傾向于位于同一任務(wù)。不過,您可以修改 Activity 的默認親和性。在不同應(yīng)用中定義的 Activity 可以具有相同的親和性,或者在同一應(yīng)用中定義的 Activity 也可以被指定不同的任務(wù)親和性。
您可以使用 <activity> 元素的 taskAffinity 屬性修改任何給定 Activity 的親和性。
taskAffinity 屬性采用字符串值,該值必須不同于 <manifest> 元素中聲明的默認軟件包名稱,因為系統(tǒng)使用該名稱來標識應(yīng)用的默認任務(wù)親和性。
親和性可在兩種情況下發(fā)揮作用:
- 當啟動 Activity 的 intent 包含
FLAG_ACTIVITY_NEW_TASK標記時。默認情況下,新 Activity 會啟動到調(diào)用
startActivity()的 Activity 的任務(wù)中。它會被推送到調(diào)用方 Activity 所在的返回堆棧中。但是,如果傳遞給startActivity()的 intent 包含FLAG_ACTIVITY_NEW_TASK標記,則系統(tǒng)會尋找其他任務(wù)來容納新 Activity。通常會是一個新任務(wù),但也可能不是。如果已存在與新 Activity 具有相同親和性的現(xiàn)有任務(wù),則會將 Activity 啟動到該任務(wù)中。如果不存在,則會啟動一個新任務(wù)。如果此標記導(dǎo)致 Activity 啟動一個新任務(wù),而用戶按下主屏幕按鈕離開該任務(wù),則必須為用戶提供某種方式來返回到該任務(wù)。有些實體(例如通知管理器)總是在外部任務(wù)中啟動 Activity,而不在它們自己的任務(wù)中啟動,因此它們總是將
FLAG_ACTIVITY_NEW_TASK添加到傳遞給startActivity()的 intent 中。如果您的 Activity 可由外部實體調(diào)用,而該實體可能使用此標記,請注意用戶可以通過一種獨立的方式返回到所啟動的任務(wù),例如使用啟動器圖標(任務(wù)的根 Activity 具有一個CATEGORY_LAUNCHERintent 過濾器;請參閱下面的啟動任務(wù)部分)。 - 當 Activity 的
allowTaskReparenting屬性設(shè)為"true"時。在這種情況下,一旦和 Activity 有親和性的任務(wù)進入前臺運行,Activity 就可從其啟動的任務(wù)轉(zhuǎn)移到該任務(wù)。
舉例來說,假設(shè)一款旅行應(yīng)用中定義了一個報告特定城市天氣狀況的 Activity。該 Activity 與同一應(yīng)用中的其他 Activity 具有相同的親和性(默認應(yīng)用親和性),并通過此屬性支持重新歸屬。當您的某個 Activity 啟動該天氣預(yù)報 Activity 時,該天氣預(yù)報 Activity 最初會和您的 Activity 同屬于一個任務(wù)。不過,當旅行應(yīng)用的任務(wù)進入前臺運行時,該天氣預(yù)報 Activity 就會被重新分配給該任務(wù)并顯示在其中。
提示:如果一個 APK 文件中包含了就用戶角度而言的多個“應(yīng)用”,您可能需要使用 taskAffinity 屬性為每個“應(yīng)用”所關(guān)聯(lián)的 Activity 指定不同的親和性。
清除返回堆棧
如果用戶離開任務(wù)較長時間,系統(tǒng)會清除任務(wù)中除根 Activity 以外的所有 Activity。當用戶再次返回到該任務(wù)時,只有根 Activity 會恢復(fù)。系統(tǒng)之所以采取這種行為方式是因為,經(jīng)過一段時間后,用戶可能已經(jīng)放棄了之前執(zhí)行的操作,現(xiàn)在返回任務(wù)是為了開始某項新的操作。
您可以使用一些 Activity 屬性來修改此行為:
alwaysRetainTaskState- 如果在任務(wù)的根 Activity 中將該屬性設(shè)為
"true",則不會發(fā)生上述默認行為。即使經(jīng)過很長一段時間后,任務(wù)仍會在其堆棧中保留所有 Activity。 clearTaskOnLaunch- 如果在任務(wù)的根 Activity 中將該屬性設(shè)為
"true",那么只要用戶離開任務(wù)再返回,堆棧就會被清除到只剩根 Activity。也就是說,它與alwaysRetainTaskState正好相反。用戶始終會返回到任務(wù)的初始狀態(tài),即便只是短暫離開任務(wù)也是如此。 finishOnTaskLaunch- 該屬性與
clearTaskOnLaunch類似,但它只會作用于單個 Activity 而非整個任務(wù)。它還可導(dǎo)致任何 Activity 消失,包括根 Activity。如果將該屬性設(shè)為"true",則 Activity 僅在當前會話中歸屬于任務(wù)。如果用戶離開任務(wù)再返回,則該任務(wù)將不再存在。
啟動任務(wù)
您可以設(shè)置一個 Activity 作為任務(wù)的入口點,方法是為該 Activity 提供一個 intent 過濾器,并將 "android.intent.action.MAIN" 作為指定操作,將 "android.intent.category.LAUNCHER" 作為指定類別。例如:
<activity ... >
<intent-filter ... >
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
...
</activity>
這種 intent 過濾器可在應(yīng)用啟動器中顯示 Activity 的圖標和標簽,讓用戶可以啟動 Activity 并在啟動后隨時返回到該 Activity 創(chuàng)建的任務(wù)。
第二個作用非常重要:用戶必須能夠離開任務(wù),之后再使用此 Activity 啟動器返回到該任務(wù)。因此,只有當 Activity 具有 ACTION_MAIN 和 CATEGORY_LAUNCHER 過濾器時,才應(yīng)使用 "singleTask" 和 "singleInstance" 這兩種啟動模式,它們會將 Activity 標記為始終啟動任務(wù)。比如,可以想象一下,如果缺少該過濾器會發(fā)生什么情況:intent 會啟動 "singleTask" Activity,隨之啟動新任務(wù),用戶花了一些時間在該任務(wù)上。然后,用戶按主屏幕按鈕。此時,該任務(wù)會轉(zhuǎn)到后臺,不再可見。現(xiàn)在,用戶無法返回到該任務(wù),因為它未顯示在應(yīng)用啟動器中。
對于那些您不希望用戶能夠返回到 Activity 的情況,請將 <activity> 元素的 finishOnTaskLaunch 設(shè)置為 "true"(請參閱清除返回堆棧)。
如需詳細了解如何在概覽屏幕中顯示和管理任務(wù)及 Activity,請參閱“最近使用的應(yīng)用”屏幕。
進程和應(yīng)用生命周期
在大多數(shù)情況下,每個 Android 應(yīng)用都在各自的 Linux 進程中運行。當需要運行應(yīng)用的一些代碼時,系統(tǒng)會為應(yīng)用創(chuàng)建此進程,并使其保持運行,直到不再需要它且系統(tǒng)需要回收其內(nèi)存以供其他應(yīng)用使用。
應(yīng)用進程的生命周期并不由應(yīng)用本身直接控制,而是由系統(tǒng)綜合多種因素來確定的,比如系統(tǒng)所知道的正在運行的應(yīng)用部分、這些內(nèi)容對用戶的重要程度,以及系統(tǒng)中可用的總內(nèi)存量。這是 Android 非常獨特的一個基本功能。
應(yīng)用開發(fā)者必須了解不同的應(yīng)用組件(特別是 Activity、Service 和 BroadcastReceiver)對應(yīng)用進程的生命周期有何影響。這些組件使用不當會導(dǎo)致系統(tǒng)在應(yīng)用進程正執(zhí)行重要任務(wù)時將它終止。
進程生命周期錯誤的一個常見示例是當 BroadcastReceiver 在其 BroadcastReceiver.onReceive() 方法中接收到一個 Intent 時,它會啟動一個線程,然后從該函數(shù)返回。一旦返回,則系統(tǒng)會認為 BroadcastReceiver 不再處于活動狀態(tài),因此不再需要其托管進程(除非其中有其他應(yīng)用組件處于活動狀態(tài))。因此,系統(tǒng)可能會隨時終止進程以回收內(nèi)存,這樣會終止在進程中運行的衍生線程。要解決這個問題,通常可以從 BroadcastReceiver 調(diào)度 JobService,這樣系統(tǒng)就知道進程中還有處于活動狀態(tài)的任務(wù)正在進行中。
為了確定在內(nèi)存不足時應(yīng)該終止哪些進程,Android 會根據(jù)每個進程中運行的組件以及這些組件的狀態(tài),將它們放入“重要性層次結(jié)構(gòu)”。這些進程類型包括(按重要性排序):
- A 前臺進程是用戶目前執(zhí)行操作所需的進程。在不同的情況下,進程可能會因為其所包含的各種應(yīng)用組件而被視為前臺進程。如果以下任一條件成立,則進程會被認為位于前臺:
- 它正在用戶的互動屏幕上運行一個
Activity(其onResume()方法已被調(diào)用)。 - 它有一個
BroadcastReceiver目前正在運行(其BroadcastReceiver.onReceive()方法正在執(zhí)行)。 - 它有一個
Service目前正在執(zhí)行其某個回調(diào)(Service.onCreate()、Service.onStart()或Service.onDestroy())中的代碼。
- 它正在用戶的互動屏幕上運行一個
系統(tǒng)中只有少數(shù)此類進程,而且除非內(nèi)存過低,導(dǎo)致連這些進程都無法繼續(xù)運行,才會在最后一步終止這些進程。通常,此時設(shè)備已達到內(nèi)存分頁狀態(tài),因此必須執(zhí)行此操作才能使用戶界面保持響應(yīng)。
- 可見進程正在進行用戶當前知曉的任務(wù),因此終止該進程會對用戶體驗造成明顯的負面影響。在以下條件下,進程將被視為可見:
- 它正在運行的
Activity在屏幕上對用戶可見,但不在前臺(其onPause()方法已被調(diào)用)。舉例來說,如果前臺 Activity 顯示為一個對話框,而這個對話框允許在其后面看到上一個 Activity,則可能會出現(xiàn)這種情況。 - 它有一個
Service正在通過Service.startForeground()(要求系統(tǒng)將該服務(wù)視為用戶知曉或基本上對用戶可見的服務(wù))作為前臺服務(wù)運行。 - 系統(tǒng)正在使用其托管的服務(wù)實現(xiàn)用戶知曉的特定功能,例如動態(tài)壁紙、輸入法服務(wù)等。
相比前臺進程,系統(tǒng)中運行的這些進程數(shù)量較不受限制,但仍相對受控。這些進程被認為非常重要,除非系統(tǒng)為了使所有前臺進程保持運行而需要終止它們,否則不會這么做。
- 它正在運行的
- 服務(wù)流程包含一個已使用
startService()方法啟動的Service。雖然用戶無法直接看到這些進程,但它們通常正在執(zhí)行用戶關(guān)心的任務(wù)(例如后臺網(wǎng)絡(luò)數(shù)據(jù)上傳或下載),因此系統(tǒng)會始終使此類進程保持運行,除非沒有足夠的內(nèi)存來保留所有前臺和可見進程。已經(jīng)運行了很長時間(例如 30 分鐘或更長時間)的服務(wù)的重要性可能會降位,以使其進程降至下文所述的緩存 LRU 列表。這有助于避免超長時間運行的服務(wù)因內(nèi)存泄露或其他問題占用大量內(nèi)存,進而妨礙系統(tǒng)有效利用緩存進程。
- 緩存進程是目前不需要的進程,因此,如果其他地方需要內(nèi)存,系統(tǒng)可以根據(jù)需要自由地終止該進程。在正常運行的系統(tǒng)中,這些是內(nèi)存管理中涉及的唯一進程:運行良好的系統(tǒng)將始終有多個緩存進程可用(為了更高效地切換應(yīng)用),并根據(jù)需要定期終止最早的進程。只有在非常危急(且具有不良影響)的情況下,系統(tǒng)中的所有緩存進程才會被終止,此時系統(tǒng)必須開始終止服務(wù)進程。
這些進程通常包含用戶當前不可見的一個或多個
Activity實例(onStop()方法已被調(diào)用并返回)。只要它們正確實現(xiàn)其 Activity 生命周期(詳情請見Activity),那么當系統(tǒng)終止此類流程時,就不會影響用戶返回該應(yīng)用時的體驗,因為當關(guān)聯(lián)的 Activity 在新的進程中重新創(chuàng)建時,它可以恢復(fù)之前保存的狀態(tài)。這些進程保存在偽 LRU 列表中,列表中的最后一個進程是為了回收內(nèi)存而終止的第一個進程。此列表的確切排序政策是平臺的實現(xiàn)細節(jié),但它通常會先嘗試保留更多有用的進程(比如托管用戶的主屏幕應(yīng)用、用戶最后看到的 Activity 的進程等),再保留其他類型的進程。還可以針對終止進程應(yīng)用其他政策:比如對允許的進程數(shù)量的硬限制,對進程可持續(xù)保持緩存狀態(tài)的時間長短的限制等。
在決定如何對進程進行分類時,系統(tǒng)會參考進程中當前活動的所有組件中最重要的級別。請參閱 Activity、Service 和 BroadcastReceiver 文檔,詳細了解這些組件各自對進程的整體生命周期有何影響。每個類的文檔都詳細介紹了它們對應(yīng)用的整個生命周期有何影響。
進程的優(yōu)先級也可能因從屬于進程的其他依賴項而提升。例如,如果進程 A 已通過 Context.BIND_AUTO_CREATE 標記綁定到 Service,或在使用進程 B 中的 ContentProvider,則進程 B 的分類始終至少和進程 A 一樣重要。
Parcelable 和 Bundle
Parcelable 和 Bundle 對象可跨進程邊界使用,例如與 IPC/Binder 事務(wù)之間,帶有 intent 的 Activity 之間等,還可以用來存儲跨配置更改的瞬時狀態(tài)。本頁介紹了使用 Parcelable 和 Bundle 對象的建議和最佳做法。
注意:Parcel 不是通用序列化機制,您絕不能將任何 Parcel 數(shù)據(jù)存儲在磁盤上或通過網(wǎng)絡(luò)發(fā)送。
在 Activity 之間發(fā)送數(shù)據(jù)
當應(yīng)用創(chuàng)建 Intent 對象以在 startActivity(android.content.Intent) 中用于啟動新的 Activity 時,應(yīng)用可使用 putExtra(java.lang.String, java.lang.String) 方法傳入?yún)?shù)。
以下示例代碼段演示了如何執(zhí)行此操作。
val intent = Intent(this, MyActivity::class.java).apply {
putExtra("media_id", "a1b2c3")
// ...
}
startActivity(intent)
操作系統(tǒng)會將 intent 的基礎(chǔ) Bundle 打包。然后,操作系統(tǒng)會創(chuàng)建新的 Activity,將數(shù)據(jù)拆包,并將 intent 傳遞給新的 Activity。
我們建議您使用 Bundle 類為 Intent 對象設(shè)置操作系統(tǒng)已知的基元。Bundle 類針對使用 parcel 進行編組和解組進行了高度優(yōu)化。
在某些情況下,您可能需要一種機制來跨 Activity 發(fā)送復(fù)合對象或復(fù)雜對象。在這種情況下,自定義類應(yīng)實現(xiàn) Parcelable,并提供相應(yīng)的 writeToParcel(android.os.Parcel, int) 方法。它還必須提供實現(xiàn) Parcelable.Creator 接口的非空字段 CREATOR,該接口的 createFromParcel() 方法用于將 Parcel 轉(zhuǎn)回為當前對象。如需了解詳情,請參閱 Parcelable 對象的參考文檔。
通過 intent 發(fā)送數(shù)據(jù)時,應(yīng)小心地將數(shù)據(jù)大小限制為幾 KB。發(fā)送過多數(shù)據(jù)會導(dǎo)致系統(tǒng)拋出 TransactionTooLargeException 異常。
在進程之間發(fā)送數(shù)據(jù)
在進程之間發(fā)送數(shù)據(jù)與在 Activity 之間發(fā)送數(shù)據(jù)類似。不過,在進程之間發(fā)送時,我們建議您不要使用自定義 Parcelable。如果您將一個自定義 Parcelable 對象從一個應(yīng)用發(fā)送到另一個應(yīng)用,則需要確保發(fā)送和接收的應(yīng)用上都存在版本完全相同的自定義類。通常情況下,這可能是在兩個應(yīng)用中都會使用的通用庫。如果您的應(yīng)用嘗試向系統(tǒng)發(fā)送自定義 Parblelable,則可能會發(fā)生錯誤,因為系統(tǒng)無法對其不了解的類進行解組。
例如,某個應(yīng)用可能會使用 AlarmManager 類設(shè)置鬧鐘,并對鬧鐘 intent 使用自定義Parcelable。當鬧鐘響鈴時,系統(tǒng)會修改 intent 的 extra Bundle 以添加重復(fù)計數(shù)。此修改可導(dǎo)致系統(tǒng)從 extra 中剝離自定義 Parcelable。這種剝離進而會導(dǎo)致應(yīng)用在收到修改后的警報 intent 時崩潰,因為應(yīng)用預(yù)計會收到 extra 數(shù)據(jù),而它已不存在。
Binder 事務(wù)緩沖區(qū)的大小固定有限,目前為 1MB,由進程中正在處理的所有事務(wù)共享。由于此限制是進程級別而不是 Activity 級別的限制,因此這些事務(wù)包括應(yīng)用中的所有 binder 事務(wù),例如 onSaveInstanceState,startActivity 以及與系統(tǒng)的任何互動。超過大小限制時,將引發(fā) TransactionTooLargeException。
對于 savedInstanceState 的具體情況,應(yīng)將數(shù)據(jù)量保持在較小的規(guī)模,因為只要用戶可以返回到該 Activity,系統(tǒng)進程就需要保留所提供的數(shù)據(jù)(即使 Activity 的進程已終止)。我們建議您將保存的狀態(tài)保持在 50k 數(shù)據(jù)以下。
注意:在 Android 7.0(API 級別 24)或更高版本中,系統(tǒng)會在運行時拋出 TransactionTooLargeException 異常。在較低版本的 Android 中,系統(tǒng)僅在 logcat 中顯示警告。

浙公網(wǎng)安備 33010602011771號