activity 防止多次打开_Android开发Activity任务和返回栈
任務是用戶在執行某項工作時與之互動的一系列 Activity 的集合。這些 Activity 按照每個 Activity 打開的順序排列在一個返回堆棧中。例如,電子郵件應用可能有一個 Activity 來顯示新郵件列表。當用戶選擇一封郵件時,系統會打開一個新的 Activity 來顯示該郵件。這個新的 Activity 會添加到返回堆棧中。如果用戶按返回按鈕,這個新的 Activity 即會完成并從堆棧中退出。
Android 7.0(API 級別 24)及更高版本支持多窗口環境,當應用在這種環境中同時運行時,系統會單獨管理每個窗口的任務;而每個窗口可能包含多項任務。在 Chromebook 上運行的 Android 應用也是如此:系統按窗口管理任務或任務組。
大多數任務都從設備主屏幕上啟動。當用戶輕觸應用啟動器中的圖標(或主屏幕上的快捷方式)時,該應用的任務就會轉到前臺運行。如果該應用沒有任務存在(應用最近沒有使用過),則會創建一個新的任務,并且該應用的“主”Activity 將會作為堆棧的根 Activity 打開。
在當前 Activity 啟動另一個 Activity 時,新的 Activity 將被推送到堆棧頂部并獲得焦點。上一個 Activity 仍保留在堆棧中,但會停止。當 Activity 停止時,系統會保留其界面的當前狀態。當用戶按返回按鈕時,當前 Activity 會從堆棧頂部退出(該 Activity 銷毀),上一個 Activity 會恢復(界面會恢復到上一個狀態)。堆棧中的 Activity 永遠不會重新排列,只會被送入和退出,在當前 Activity 啟動時被送入堆棧,在用戶使用返回按鈕離開時從堆棧中退出。因此,返回堆棧按照“后進先出”的對象結構運作。圖 1 借助一個時間軸直觀地顯示了這種行為。該時間軸顯示了 Activity 之間的進展以及每個時間點的當前返回堆棧。
圖 1.?有關任務中的每個新 Activity 如何添加到返回堆棧的圖示。當用戶按返回按鈕時,當前 Activity 會銷毀,上一個 Activity 將恢復。
如果用戶繼續按返回,則堆棧中的 Activity 會逐個退出,以顯示前一個 Activity,直到用戶返回到主屏幕(或任務開始時運行的 Activity)。移除堆棧中的所有 Activity 后,該任務將不復存在。
圖 2.?兩個任務:任務 B 在前臺接收用戶互動,任務 A 在后臺等待恢復。
任務是一個整體單元,當用戶開始一個新任務或通過主屏幕按鈕進入主屏幕時,任務可移至“后臺”。在后臺時,任務中的所有 Activity 都會停止,但任務的返回堆棧會保持不變,當其他任務啟動時,當前任務只是失去了焦點,如圖 2 所示。這樣一來,任務就可以返回到“前臺”,以便用戶可以從他們離開的地方繼續操作。舉例來說,假設當前任務(任務 A)的堆棧中有 3 個 Activity,當前 Activity 下有 2 個 Activity。用戶按主屏幕按鈕,然后從應用啟動器中啟動新應用。主屏幕出現后,任務 A 轉到后臺。當新應用啟動時,系統會啟動該應用的任務(任務 B),該任務具有自己的 Activity 堆棧。與該應用互動后,用戶再次返回到主屏幕并選擇最初啟動任務 A 的應用。現在,任務 A 進入前臺,其堆棧中的所有三個 Activity 都完好如初,堆棧頂部的 Activity 恢復運行。此時,用戶仍可通過以下方式切換到任務 B:轉到主屏幕并選擇啟動該任務的應用圖標(或者從最近使用的應用屏幕中選擇該應用的任務)。這就是在 Android 上進行多任務處理的一個例子。
注意:多個任務可以同時在后臺進行。但是,如果用戶同時運行很多后臺任務,系統可能會為了恢復內存而開始銷毀后臺 Activity,導致 Activity 狀態丟失。
圖 3.?單個 Activity 會被多次實例化。
由于返回堆棧中的 Activity 不會被重新排列,如果您的應用允許用戶從多個 Activity 啟動特定的 Activity,系統便會創建該 Activity 的新實例并將其推送到堆棧中(而不是將該 Activity 的某個先前的實例移至堆棧頂部)。這樣一來,應用中的一個 Activity 就可能被多次實例化(甚至是從其他任務對其進行實例化),如圖 3 所示。因此,如果用戶使用返回按鈕向后導航,Activity 的每個實例將按照它們被打開的順序顯示出來(每個實例都有自己的界面狀態)。不過,如果您不希望某個 Activity 被實例化多次,可以修改此行為。有關如何實現此操作,將在后面的管理任務部分中討論。
Activity 和任務的默認行為總結如下:
當 Activity A 啟動 Activity B 時,Activity A 會停止,但系統會保留其狀態(例如滾動位置和輸入到表單中的文本)。如果用戶在 Activity B 中按返回按鈕,系統會恢復 Activity A 及其狀態。
當用戶通過按主屏幕按鈕離開任務時,當前 Activity 會停止,其任務會轉到后臺。系統會保留任務中每個 Activity 的狀態。如果用戶稍后通過點按該任務的啟動器圖標來恢復該任務,該任務會進入前臺并恢復堆棧頂部的 Activity。
如果用戶按返回按鈕,當前 Activity 將從堆棧中退出并銷毀。堆棧中的上一個 Activity 將恢復。Activity 被銷毀后,系統不會保留該 Activity 的狀態。
Activity 可以多次實例化,甚至是從其他任務對其進行實例化。
導航設計
要詳細了解 Android 上的應用導航如何運作,請參閱 Android 設計中的導航指南。
管理任務
如上文所述,Android 管理任務和返回堆棧的方式是將所有接連啟動的 Activity 放到同一任務和一個“后進先出”堆棧中,這對于大多數應用都很有效,而且您不必擔心 Activity 如何與任務相關聯,或者它們如何存在于返回堆棧中。不過,您可能需要決定是否要打破正常行為。或許您希望應用中的某個 Activity 在啟動時開啟一個新的任務(而不是被放入當前的任務中),或者當您啟動某個 Activity 時,您希望調用它的一個現有實例(而不是在返回堆棧頂部創建一個新實例),或者您希望在用戶離開任務時清除返回堆棧中除根 Activity 以外的所有 Activity。
您可以借助??清單元素中的屬性以及您傳遞給?startActivity()?的 intent 中的標記來實現上述目的。
在這方面,您可以使用的主要??屬性包括:
taskAffinity
launchMode
allowTaskReparenting
clearTaskOnLaunch
alwaysRetainTaskState
finishOnTaskLaunch
您可以使用的主要 intent 標記包括:
FLAG_ACTIVITY_NEW_TASK
FLAG_ACTIVITY_CLEAR_TOP
FLAG_ACTIVITY_SINGLE_TOP
在下面幾節中,您將了解到如何使用這些清單屬性和 intent 標記來定義 Activity 與任務之間的關聯方式,以及它們在返回堆棧中的行為。
另外,下面還分別介紹了如何在最近使用的應用屏幕中表示和管理任務與 Activity。有關詳情,請參閱“最近使用的應用”屏幕。通常,您應允許系統定義任務和 Activity 在最近使用的應用屏幕中的表示方式,您無需修改此行為。
注意:大多數應用不應打破 Activity 和任務的默認行為。如果您確定需要讓 Activity 改變默認行為,請謹慎操作,并且務必要測試該 Activity 在以下情況下的可用性:啟動期間以及您通過返回按鈕從其他 Activity 和任務返回該 Activity 時。務必要測試是否存在可能與用戶預期的行為沖突的導航行為。
定義啟動模式
您可以通過啟動模式定義 Activity 的新實例如何與當前任務關聯。您可以通過兩種方式定義不同的啟動模式:
使用清單文件
當您在清單文件中聲明 Activity 時,您可以指定該 Activity 在啟動時如何與任務關聯。
使用 Intent 標記
當您調用?startActivity()?時,可以在?Intent?中添加一個標記,用于聲明新 Activity 如何(或是否)與當前任務相關聯。
因此,如果 Activity A 啟動 Activity B,Activity B 可在其清單中定義如何與當前任務相關聯(如果關聯的話),Activity A 也可以請求 Activity B 應該如何與當前任務關聯。如果兩個 Activity 都定義了 Activity B 應如何與任務關聯,將優先遵循 Activity A 的請求(在 intent 中定義),而不是 Activity B 的請求(在清單中定義)。
注意:有些啟動模式可通過清單文件定義,但不能通過 intent 標記定義,同樣,有些啟動模式可通過 intent 標記定義,卻不能在清單中定義。
使用清單文件
在清單文件中聲明 Activity 時,可以使用??元素的?launchMode?屬性指定 Activity 應該如何與任務關聯。
launchMode?屬性說明了 Activity 應如何啟動到任務中。您可以為?launchMode?屬性指定 4 種不同的啟動模式:
"standard"(默認模式)
默認值。系統在啟動該 Activity 的任務中創建 Activity 的新實例,并將 intent 傳送給該實例。Activity 可以多次實例化,每個實例可以屬于不同的任務,一個任務可以擁有多個實例。
"singleTop"
如果當前任務的頂部已存在 Activity 的實例,則系統會通過調用其?onNewIntent()?方法來將 intent 轉送給該實例,而不是創建 Activity 的新實例。Activity 可以多次實例化,每個實例可以屬于不同的任務,一個任務可以擁有多個實例(但前提是返回堆棧頂部的 Activity 不是該 Activity 的現有實例)。
例如,假設任務的返回堆棧包含根 Activity A 以及 Activity B、C 和位于頂部的 D(堆棧為 A-B-C-D;D 位于頂部)。收到以 D 類型 Activity 為目標的 intent。如果 D 采用默認的?"standard"?啟動模式,則會啟動該類的新實例,并且堆棧將變為 A-B-C-D-D。但是,如果 D 的啟動模式為?"singleTop",則 D 的現有實例會通過?onNewIntent()?接收 intent,因為它位于堆棧頂部,堆棧仍為 A-B-C-D。但是,如果收到以 B 類型 Activity 為目標的 intent,則會在堆棧中添加 B 的新實例,即使其啟動模式為?"singleTop"?也是如此。
注意:創建 Activity 的新實例后,用戶可以按返回按鈕返回到上一個 Activity。但是,當由 Activity 的現有實例處理新 intent 時,用戶將無法通過按返回按鈕返回到?onNewIntent()?收到新 intent 之前的 Activity 狀態。
"singleTask"
系統會創建新任務,并實例化新任務的根 Activity。但是,如果另外的任務中已存在該 Activity 的實例,則系統會通過調用其?onNewIntent()?方法將 intent 轉送到該現有實例,而不是創建新實例。Activity 一次只能有一個實例存在。
注意:雖然 Activity 在新任務中啟動,但用戶按返回按鈕仍會返回到上一個 Activity。
"singleInstance"
與?"singleTask"?相似,唯一不同的是系統不會將任何其他 Activity 啟動到包含該實例的任務中。該 Activity 始終是其任務唯一的成員;由該 Activity 啟動的任何 Activity 都會在其他的任務中打開。
再舉個例子,Android 瀏覽器應用在??元素中指定?singleTask?啟動模式,由此聲明網絡瀏覽器 Activity 應始終在它自己的任務中打開。這意味著,如果您的應用發出打開 Android 瀏覽器的 intent,系統不會將其 Activity 置于您的應用所在的任務中,而是會為瀏覽器啟動一個新任務,如果瀏覽器已經有任務在后臺運行,則會將該任務轉到前臺來處理新 intent。
無論 Activity 是在新任務中啟動的,還是在和啟動它的 Activity 相同的任務中啟動,用戶按返回按鈕都會回到上一個 Activity。但是,如果您啟動了指定?singleTask?啟動模式的 Activity,而后臺任務中已存在該 Activity 的實例,則系統會將該后臺任務整個轉到前臺運行。此時,返回堆棧包含了轉到前臺的任務中的所有 Activity,這些 Activity 都位于堆棧的頂部。圖 4 展示了具體的情景。
圖 4.?采用“singleTask”啟動模式的 Activity 添加到返回堆棧的過程圖示。如果 Activity 已經存在于某個具有自己的返回堆棧的后臺任務中,那么整個返回堆棧也會轉到前臺,覆蓋當前任務。
要詳細了解如何在清單文件中設置啟動模式,請參閱??元素的說明文檔,里面詳細介紹了?launchMode?屬性和可接受的值。
注意:您通過?launchMode?屬性為 Activity 指定的行為,可被啟動 Activity 的 intent 所包含的標記替換。下一節將對此進行介紹。
使用 Intent 標記
啟動 Activity 時,您可以在傳送給?startActivity()?的 intent 中添加相應的標記來修改 Activity 與其任務的默認關聯。您可以使用以下標記來修改默認行為:
FLAG_ACTIVITY_NEW_TASK
在新任務中啟動 Activity。如果您現在啟動的 Activity 已經有任務在運行,則系統會將該任務轉到前臺并恢復其最后的狀態,而 Activity 將在?onNewIntent()?中收到新的 intent。
這與上一節中介紹的?"singleTask"?launchMode?值產生的行為相同。
FLAG_ACTIVITY_SINGLE_TOP
如果要啟動的 Activity 是當前 Activity(即位于返回堆棧頂部的 Activity),則現有實例會收到對?onNewIntent()?的調用,而不會創建 Activity 的新實例。
這與上一節中介紹的?"singleTop"?launchMode?值產生的行為相同。
FLAG_ACTIVITY_CLEAR_TOP
如果要啟動的 Activity 已經在當前任務中運行,則不會啟動該 Activity 的新實例,而是會銷毀位于它之上的所有其他 Activity,并通過?onNewIntent()?將此 intent 傳送給它的已恢復實例(現在位于堆棧頂部)。
launchMode?屬性沒有可產生此行為的值。
FLAG_ACTIVITY_CLEAR_TOP?最常與?FLAG_ACTIVITY_NEW_TASK?結合使用。將這兩個標記結合使用,可以查找其他任務中的現有 Activity,并將其置于能夠響應 intent 的位置。
注意:如果指定 Activity 的啟動模式為?"standard",系統也會將其從堆棧中移除,并在它的位置啟動一個新實例來處理傳入的 intent。這是因為當啟動模式為?"standard"?時,始終會為新 intent 創建新的實例。
處理親和性
“親和性”表示 Activity 傾向于屬于哪個任務。默認情況下,同一應用中的所有 Activity 彼此具有親和性。因此,在默認情況下,同一應用中的所有 Activity 都傾向于位于同一任務。不過,您可以修改 Activity 的默認親和性。在不同應用中定義的 Activity 可以具有相同的親和性,或者在同一應用中定義的 Activity 也可以被指定不同的任務親和性。
您可以使用??元素的?taskAffinity?屬性修改任何給定 Activity 的親和性。
taskAffinity?屬性采用字符串值,該值必須不同于??元素中聲明的默認軟件包名稱,因為系統使用該名稱來標識應用的默認任務親和性。
親和性可在兩種情況下發揮作用:
當啟動 Activity 的 intent 包含?FLAG_ACTIVITY_NEW_TASK?標記時。
默認情況下,新 Activity 會啟動到調用?startActivity()?的 Activity 的任務中。它會被推送到調用方 Activity 所在的返回堆棧中。但是,如果傳遞給?startActivity()?的 intent 包含?FLAG_ACTIVITY_NEW_TASK?標記,則系統會尋找其他任務來容納新 Activity。通常會是一個新任務,但也可能不是。如果已存在與新 Activity 具有相同親和性的現有任務,則會將 Activity 啟動到該任務中。如果不存在,則會啟動一個新任務。
如果此標記導致 Activity 啟動一個新任務,而用戶按下主屏幕按鈕離開該任務,則必須為用戶提供某種方式來返回到該任務。有些實體(例如通知管理器)總是在外部任務中啟動 Activity,而不在它們自己的任務中啟動,因此它們總是將?FLAG_ACTIVITY_NEW_TASK?添加到傳遞給?startActivity()?的 intent 中。如果您的 Activity 可由外部實體調用,而該實體可能使用此標記,請注意用戶可以通過一種獨立的方式返回到所啟動的任務,例如使用啟動器圖標(任務的根 Activity 具有一個?CATEGORY_LAUNCHER?intent 過濾器;請參閱下面的啟動任務部分)。
當 Activity 的?allowTaskReparenting?屬性設為?"true"?時。
在這種情況下,一旦和 Activity 有親和性的任務進入前臺運行,Activity 就可從其啟動的任務轉移到該任務。
舉例來說,假設一款旅行應用中定義了一個報告特定城市天氣狀況的 Activity。該 Activity 與同一應用中的其他 Activity 具有相同的親和性(默認應用親和性),并通過此屬性支持重新歸屬。當您的某個 Activity 啟動該天氣預報 Activity 時,該天氣預報 Activity 最初會和您的 Activity 同屬于一個任務。不過,當旅行應用的任務進入前臺運行時,該天氣預報 Activity 就會被重新分配給該任務并顯示在其中。
提示:如果一個 APK 文件中包含了就用戶角度而言的多個“應用”,您可能需要使用?taskAffinity?屬性為每個“應用”所關聯的 Activity 指定不同的親和性。
清除返回堆棧
如果用戶離開任務較長時間,系統會清除任務中除根 Activity 以外的所有 Activity。當用戶再次返回到該任務時,只有根 Activity 會恢復。系統之所以采取這種行為方式是因為,經過一段時間后,用戶可能已經放棄了之前執行的操作,現在返回任務是為了開始某項新的操作。
您可以使用一些 Activity 屬性來修改此行為:
alwaysRetainTaskState
如果在任務的根 Activity 中將該屬性設為?"true",則不會發生上述默認行為。即使經過很長一段時間后,任務仍會在其堆棧中保留所有 Activity。
clearTaskOnLaunch
如果在任務的根 Activity 中將該屬性設為?"true",那么只要用戶離開任務再返回,堆棧就會被清除到只剩根 Activity。也就是說,它與?alwaysRetainTaskState?正好相反。用戶始終會返回到任務的初始狀態,即便只是短暫離開任務也是如此。
finishOnTaskLaunch
該屬性與?clearTaskOnLaunch?類似,但它只會作用于單個 Activity 而非整個任務。它還可導致任何 Activity 消失,包括根 Activity。如果將該屬性設為?"true",則 Activity 僅在當前會話中歸屬于任務。如果用戶離開任務再返回,則該任務將不再存在。
啟動任務
您可以設置一個 Activity 作為任務的入口點,方法是為該 Activity 提供一個 intent 過濾器,并將?"android.intent.action.MAIN"?作為指定操作,將?"android.intent.category.LAUNCHER"?作為指定類別。例如:
? ? ... > ... > android:name="android.intent.action.MAIN" /> android:name="android.intent.category.LAUNCHER" />? ? ? ? ...
這種 intent 過濾器可在應用啟動器中顯示 Activity 的圖標和標簽,讓用戶可以啟動 Activity 并在啟動后隨時返回到該 Activity 創建的任務。
第二個作用非常重要:用戶必須能夠離開任務,之后再使用此 Activity 啟動器返回到該任務。因此,只有當 Activity 具有?ACTION_MAIN?和?CATEGORY_LAUNCHER?過濾器時,才應使用?"singleTask"?和?"singleInstance"?這兩種啟動模式,它們會將 Activity 標記為始終啟動任務。比如,可以想象一下,如果缺少該過濾器會發生什么情況:intent 會啟動?"singleTask"?Activity,隨之啟動新任務,用戶花了一些時間在該任務上。然后,用戶按主屏幕按鈕。此時,該任務會轉到后臺,不再可見。現在,用戶無法返回到該任務,因為它未顯示在應用啟動器中。
對于那些您不希望用戶能夠返回到 Activity 的情況,請將??元素的?finishOnTaskLaunch?設置為?"true"(請參閱清除返回堆棧)。
如需詳細了解如何在概覽屏幕中顯示和管理任務及 Activity,請參閱“最近使用的應用”屏幕。
創作挑戰賽新人創作獎勵來咯,堅持創作打卡瓜分現金大獎總結
以上是生活随笔為你收集整理的activity 防止多次打开_Android开发Activity任务和返回栈的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 11尺寸长宽 iphone_新手必知LE
- 下一篇: 用苹果手机下载音乐还真是麻烦苹果怎么用手