Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android總結 - Activity任務和返回棧、保存Activity的狀態

Android總結 - Activity任務和返回棧、保存Activity的狀態

編輯:關於Android編程

任務和返回棧

一個應用程序當中通常都會包含很多個Activity,每個Activity都應該設計成為一個具有特定的功能,並且可以讓用戶進行操作的組件。另外,Activity之間還應該是可以相互啟動的。比如,一個郵件應用中可能會包含一個用於展示郵件列表的Activity,而當用戶點擊了其中某一封郵件的時候,就會打開另外一個Activity來顯示該封郵件的具體內容。

除此之外,一個Activity甚至還可以去啟動其它應用程序當中的Activity。打個比方,如果你的應用希望去發送一封郵件,你就可以定義一個具有”send”動作的Intent,並且傳入一些數據,如對方郵箱地址、郵件內容等。這樣,如果另外一個應用程序中的某個Activity聲明自己是可以響應這種Intent的,那麼這個Activity就會被打開。在當前場景下,這個Intent是為了要發送郵件的,所以說郵件應用程序當中的編寫郵件Activity就應該被打開。當郵件發送出去之後,仍然還是會回到你的應用程序當中,這讓用戶看起來好像剛才那個編寫郵件的Activity就是你的應用程序當中的一部分。所以說,即使有很多個Activity分別都是來自於不同應用程序的,Android系統仍然可以將它們無縫地結合到一起,之所以能實現這一點,就是因為這些Activity都是存在於一個相同的任務(Task)當中的。

任務是一個Activity的集合,它使用棧的方式來管理其中的Activity,這個棧又被稱為返回棧(back stack),棧中Activity的順序就是按照它們被打開的順序依次存放的。

手機的Home界面是大多數任務開始的地方,當用戶在Home界面上點擊了一個應用的圖標時,這個應用的任務就會被轉移到前台。如果這個應用目前並沒有任何一個任務的話(說明這個應用最近沒有被啟動過),系統就會去創建一個新的任務,並且將該應用的主Activity放入到返回棧當中。

當一個Activity啟動了另外一個Activity的時候,新的Activity就會被放置到返回棧的棧頂並將獲得焦點。前一個Activity仍然保留在返回棧當中,但會處於停止狀態。當用戶按下Back鍵的時候,棧中最頂端的Activity會被移除掉,然後前一個Activity則會得重新回到最頂端的位置。返回棧中的Activity的順序永遠都不會發生改變,我們只能向棧頂添加Activity,或者將棧頂的Activity移除掉。因此,返回棧是一個典型的後進先出(last in, first out)的數據結構。下圖通過時間線的方式非常清晰地向我們展示了多個Activity在返回棧當中的狀態變化:

 

 

如果用戶一直地按Back鍵,這樣返回棧中的Activity會一個個地被移除,直到最終返回到主屏幕。當返回棧中所有的Activity都被移除掉的時候,對應的任務也就不存在了。

任務除了可以被轉移到前台之外,當然也是可以被轉移到後台的。當用戶開啟了一個新的任務,或者點擊Home鍵回到主屏幕的時候,之前任務就會被轉移到後台了。當任務處於後台狀態的時候,返回棧中所有的Activity都會進入停止狀態,但這些Activity在棧中的順序都會原封不動地保留著,如下圖所示:

 

 

這個時候,用戶還可以將任意後台的任務切換到前台,這樣用戶應該就會看到之前離開這個任務時處於最頂端的那個Activity。舉個例子來說,當前任務A的棧中有三個Activity,現在用戶按下Home鍵,然後點擊桌面上的圖標啟動了另外一個應用程序。當系統回到桌面的時候,其實任務A就已經進入後台了,然後當另外一個應用程序啟動的時候,系統會為這個程序開啟一個新的任務(任務B)。當用戶使用完這個程序之後,再次按下Home鍵回到桌面,這個時候任務B也進入了後台。然後用戶又重新打開了第一次使用的程序,這個時候任務A又會回到前台,A任務棧中的三個Activity仍然會保留著剛才的順序,最頂端的Activity將重新變為運行狀態。之後用戶仍然可以通過Home鍵或者多任務鍵來切換回任務B,或者啟動更多的任務,這就是Android中多任務切換的例子。

由於返回棧中的Activity的順序永遠都不會發生改變,所以如果你的應用程序中允許有多個入口都可以啟動同一個Activity,那麼每次啟動的時候就都會創建該Activity的一個新的實例,而不是將下面的Activity的移動到棧頂。這樣的話就容易導致一個問題的產生,即同一個Activity有可能會被實例化很多次,如下圖所示:

 

 

但是呢,如果你不希望同一個Activity可以被多次實例化,那當然也是可以的,馬上我們就將開始討論如果實現這一功能,現在我們先把默認的任務和Activity的行為簡單概括一下:

當Activity A啟動Activity B時,Activity A進入停止狀態,但系統仍然會將它的所有相關信息保留,比如滾動的位置,還有文本框輸入的內容等。如果用戶在Activity B中按下Back鍵,那麼Activity A將會重新回到運行狀態。

當用戶通過Home鍵離開一個任務時,該任務會進入後台,並且返回棧中所有的Activity都會進入停止狀態。系統會將這些Activity的狀態進行保留,這樣當用戶下一次重新打開這個應用程序時,就可以將後台任務直接提取到前台,並將之前最頂端的Activity進行恢復。

當用戶按下Back鍵時,當前最頂端的Activity會被從返回棧中移除掉,移除掉的Activity將被銷毀,然後前面一個Activity將處於棧頂位置並進入活動狀態。當一個Activity被銷毀了之後,系統不會再為它保留任何的狀態信息。

每個Activity都可以被實例化很多次,即使是在不同的任務當中。

保存Activity的狀態

當系統為了恢復內存而銷毀某項 Activity 時,Activity 對象也會被銷毀,因此系統在繼續 Activity 時根本無法讓其狀態保持完好,而是必須在用戶返回Activity時重建 Activity 對象。但用戶並不知道系統銷毀 Activity 後又對其進行了重建,因此他們很可能認為 Activity 狀態毫無變化。 在這種情況下,您可以實現另一個回調方法對有關 Activity 狀態的信息進行保存,以確保有關 Activity 狀態的重要信息得到保留:onSaveInstanceState()。

系統會先調用 onSaveInstanceState(),然後再使 Activity 變得易於銷毀。系統會向該方法傳遞一個 Bundle,您可以在其中使用 putString() 和 putInt() 等方法以名稱-值對形式保存有關 Activity 狀態的信息。然後,如果系統終止您的應用進程,並且當用戶返回您的 Activity,則系統會重建該 Activity,並將 Bundle 同時傳遞給 onCreate() 和 onRestoreInstanceState()。您可以使用上述任一方法從 Bundle 提取您保存的狀態並恢復該 Activity 狀態。如果沒有狀態信息需要恢復,則傳遞給您的 Bundle 是空值(如果是首次創建該 Activity,就會出現這種情況)。

 


restore_instance
在兩種情況下,Activity 重獲用戶焦點時可保持狀態完好:系統在銷毀 Activity 後重建 Activity,Activity 必須恢復之前保存的狀態;系統停止 Activity 後繼續執行 Activity,並且 Activity 狀態保持完好。

 

注:無法保證系統會在銷毀您的 Activity 前調用 onSaveInstanceState(),因為存在不需要保存狀態的情況(例如用戶使用“返回” 按鈕離開您的 Activity 時,因為用戶的行為是在顯式關閉 Activity)。 如果系統調用 onSaveInstanceState(),它會在調用 onStop() 之前,並且可能會在調用 onPause() 之前進行調用。

不過,即使您什麼都不做,也不實現 onSaveInstanceState(),Activity 類的 onSaveInstanceState() 默認實現也會恢復部分 Activity 狀態。具體地講,默認實現會為布局中的每個 View 調用相應的 onSaveInstanceState() 方法,讓每個視圖都能提供有關自身的應保存信息。Android 框架中幾乎每個小工具都會根據需要實現此方法,以便在重建 Activity 時自動保存和恢復對 UI 所做的任何可見更改。例如,EditText 小工具保存用戶輸入的任何文本,CheckBox 小工具保存復選框的選中或未選中狀態。您只需為想要保存其狀態的每個小工具提供一個唯一的 ID(通過 android:id 屬性)。如果小工具沒有 ID,則系統無法保存其狀態。<喎?/kf/ware/vc/" target="_blank" class="keylink">vcD4NCjxwPsT6u7m/ydLUzai5/b2rYW5kcm9pZDpzYXZlRW5hYmxlZCDK9NDUyejWw86qJnJkcXVvO2ZhbHNlJnJkcXVvOyC78s2ouf2199PDc2V0U2F2ZUVuYWJsZWQoKSC3vbeoz9TKvdfo1rmyvL7WxNq1xMrTzbyxo7TmxuTXtMysoaPE+s2os6Oyu9Omvau4w8r00NS9+9PDo6y1q8jnufvE+s/r0tSyu82st73KvbvWuLQgQWN0aXZpdHkgVUkgtcTXtMyso6y+zb/JxNzQ6NKq1eLR+df2oaM8L3A+DQo8YmxvY2txdW90ZT4NCgk8cD7XoqO608nT2s7et6ixo9akz7XNs7vhtffTwyBvblNhdmVJbnN0YW5jZVN0YXRlKCmjrNLytMvE+ta706bA+9PDy/zAtLzHwrwgQWN0aXZpdHkgtcTLssyso6hVSSC1xNe0zKyjqSZtZGFzaDsmbWRhc2g7IMfQzvDKudPDy/zAtLTmtKKz1r7D0NTK/b7do6y2+NOmyrnTwyBvblBhdXNlKCkg1NrTw7unwOu/qiBBY3Rpdml0eSC687TmtKKz1r7D0NTK/b7do6jA/cjn06axo7Tmtb3K/b7dv+K1xMr9vt2jqaGjPC9wPg0KPC9ibG9ja3F1b3RlPg0KPGgxIGlkPQ=="管理任務">管理任務

Android系統管理任務和返回棧的方式,正如上面所描述的一樣,就是把所有啟動的Activity都放入到一個相同的任務當中,通過一個“後進先出”的棧來進行管理的。這種方式在絕大多數情況下都是沒問題的,開發者也無須去關心任務中的Activity到底是怎麼樣存放在返回棧當中的。但是呢,如果你想打破這種默認的行為,比如說當啟動一個新的Activity時,你希望它可以存在於一個獨立的任務當中,而不是現有的任務當中。或者說,當啟動一個Activity時,如果這個Activity已經存在於返回棧中了,你希望能把這個Activity直接移動到棧頂,而不是再創建一個它的實例。再或者,你希望可以將返回棧中除了最底層的那個Activity之外的其它所有Activity全部清除掉。這些功能甚至更多功能,都是可以通過在manifest文件中設置元素的屬性,或者是在啟動Activity時配置Intent的flag來實現的。

在元素中,有以下幾個屬性是可以使用的:

taskAffinity

launchMode

allowTaskReparenting

clearTaskOnLaunch

alwaysRetainTaskState

finishOnTaskLaunch

而在Intent當中,有以下幾個flag是比較常用的:

FLAG_ACTIVITY_NEW_TASK

FLAG_ACTIVITY_CLEAR_TOP

http://blog.csdn.net/siobhan/article/details/FLAG_ACTIVITY_SINGLE_TOP

注意:大多數應用都不得中斷 Activity 和任務的默認行為: 如果確定您的 Activity 必須修改默認行為,當使用“返回”按鈕從其他 Activity 和任務導航回到該 Activity 時,請務必要謹慎並確保在啟動期間測試該 Activity 的可用性。請確保測試導航行為是否有可能與用戶的預期行為沖突。

定義啟動模式

啟動模式允許您定義 Activity 的新實例如何與當前任務關聯。 您可以通過兩種方法定義不同的啟動模式:

Intent標識優先級高於在AndroidManifest中定義的啟動模式。

使用清單文件

在清單文件中聲明 Activity 時,您可以使用 元素的 launchMode 屬性指定 Activity 應該如何與任務關聯。

launchMode 屬性指定有關應如何將 Activity 啟動到任務中的指令。您可以分配給 launchMode 屬性的啟動模式共有四種:

“standard”(默認模式)
默認。系統在啟動 Activity 的任務中創建 Activity 的新實例並向其傳送 Intent。Activity 可以多次實例化,而每個實例均可屬於不同的任務,並且一個任務可以擁有多個實例。

“singleTop”
如果當前任務的頂部已存在 Activity 的一個實例,則系統會通過調用該實例的 onNewIntent() 方法向其傳送 Intent,而不是創建 Activity 的新實例。Activity 可以多次實例化,而每個實例均可屬於不同的任務,並且一個任務可以擁有多個實例(但前提是位於返回棧頂部的 Activity 並不是 Activity 的現有實例)。

效果同Intent添加”http://blog.csdn.net/siobhan/article/details/FLAG_ACTIVITY_SINGLE_TOP”

注:為某個 Activity 創建新實例時,用戶可以按“返回”按鈕返回到前一個 Activity。 但是,當 Activity 的現有實例處理新 Intent 時,則在新 Intent 到達 onNewIntent() 之前,用戶無法按“返回”按鈕返回到 Activity 的狀態。

“singleTask”

這種啟動模式表示,系統會創建一個新的任務,並將啟動的Activity放入這個新任務的棧底位置。但是,如果現有任務當中已經存在一個該Activity的實例了,那麼系統就不會再創建一次它的實例,而是會直接調用它的onNewIntent()方法。聲明成這種啟動模式的Activity,在同一個任務當中只會存在一個實例。注意這裡我們所說的啟動Activity,都指的是啟動其它應用程序中的Activity,因為”singleTask”模式在默認情況下只有啟動其它程序的Activity才會創建一個新的任務,啟動自己程序中的Activity還是會使用相同的任務,具體原因會在下面 處理affinity 部分進行解釋。

效果同Intent添加“FLAG_ACTIVITY_NEW_TASK”。

注:盡管 Activity 在新任務中啟動,但是用戶按“返回”按鈕仍會返回到前一個 Activity。

“singleInstance”
這種啟動模式和”singleTask”有點相似,只不過系統不會向聲明成”singleInstance”的Activity所在的任務當中再添加其它Activity。也就是說,這種Activity所在的任務中始終只會有一個Activity,通過這個Activity再打開的其它Activity也會被放入到別的任務當中。

再舉一個例子,Android系統內置的浏覽器程序聲明自己浏覽網頁的Activity始終應該在一個獨立的任務當中打開,也就是通過在元素中設置”singleTask”啟動模式來實現的。這意味著,當你的程序准備去打開Android內置浏覽器的時候,新打開的Activity並不會放入到你當前的任務中,而是會啟動一個新的任務。而如果浏覽器程序在後台已經存在一個任務了,則會把這個任務切換到前台。

其實不管是Activity在一個新任務當中啟動,還是在當前任務中啟動,返回鍵永遠都會把我們帶回到之前的一個Activity中的。但是有一種情況是比較特殊的,就是如果Activity指定了啟動模式是”singleTask”,並且啟動的是另外一個應用程序中的Activity,這個時候當發現該Activity正好處於一個後台任務當中的話,就會直接將這整個後台任務一起切換到前台。此時按下返回鍵會優先將目前最前台的任務(剛剛從後台切換到最前台)進行回退,下圖比較形象地展示了這種情況:

 

 

使用 Intent 標志

啟動 Activity 時,您可以通過在傳遞給 startActivity() 的 Intent 中加入相應的標志,修改 Activity 與其任務的默認關聯方式。可用於修改默認行為的標志包括:

FLAG_ACTIVITY_NEW_TASK
設置了這個flag,新啟動Activity就會被放置到一個新的任務當中(與”singleTask”有點類似,但不完全一樣),當然這裡討論的仍然還是啟動其它程序中的Activity。這個flag的作用通常是模擬一種Launcher的行為,即列出一推可以啟動的東西,但啟動的每一個Activity都是在運行在自己獨立的任務當中的。

等同於launchMode設置為 “singleTask”

http://blog.csdn.net/siobhan/article/details/FLAG_ACTIVITY_SINGLE_TOP
如果正在啟動的 Activity 是當前 Activity(位於返回棧的頂部),則 現有實例會接收對 onNewIntent() 的調用,而不是創建 Activity 的新實例。

等同於launchMode設置為 “singleTop”

FLAG_ACTIVITY_CLEAR_TOP

如果正在啟動的 Activity 已在當前任務中運行,則會銷毀當前任務頂部的所有 Activity,並通過 onNewIntent() 將此 Intent 傳遞給 Activity 已恢復的實例(現在位於頂部),而不是啟動該 Activity 的新實例。

單獨使用的情況:ABCD 啟動 B ,會銷毀B和B以上的實例 變成 AB ,B 重新執行onCreate -> onStart
配合http://blog.csdn.net/siobhan/article/details/FLAG_ACTIVITY_SINGLE_TOP使用,則 B 不會銷毀只銷毀B以上實例,然後B 執行onNewIntent -> onStart
配合FLAG_ACTIVITY_NEW_TASK則是singleTask效果

處理affinity

affinity可以用於指定一個Activity更加願意依附於哪一個任務,在默認情況下,同一個應用程序中的所有Activity都具有相同的affinity,所以,這些Activity都更加傾向於運行在相同的任務當中。當然了,你也可以去改變每個Activity的affinity值,通過元素的taskAffinity屬性就可以實現了。

taskAffinity屬性接收一個字符串參數,你可以指定成任意的值(經我測試字符串中至少要包含一個.),但必須不能和應用程序的包名相同,因為系統會使用包名來作為默認的affinity值。

affinity主要有以下兩種應用場景:

當調用startActivity()方法來啟動一個Activity時,默認是將它放入到當前的任務當中。但是,如果在Intent中加入了一個FLAG_ACTIVITY_NEW_TASK flag的話(或者該Activity在manifest文件中聲明的啟動模式是”singleTask”),系統就會嘗試為這個Activity單獨創建一個任務。但是規則並不是只有這麼簡單,系統會去檢測要啟動的這個Activity的affinity和當前任務的affinity是否相同,如果相同的話就會把它放入到現有任務當中,如果不同則會去創建一個新的任務。而同一個程序中所有Activity的affinity默認都是相同的,這也是前面為什麼說,同一個應用程序中即使聲明成”singleTask”,也不會為這個Activity再去創建一個新的任務了。

當把Activity的allowTaskReparenting屬性設置成true時,Activity就擁有了一個轉移所在任務的能力。具體點來說,就是一個Activity現在是處於某個任務當中的,但是它與另外一個任務具有相同的affinity值,那麼當另外這個任務切換到前台的時候,該Activity就可以轉移到現在的這個任務當中。

那還是舉一個形象點的例子吧,比如有一個天氣預報程序,它有一個Activity是專門用於顯示天氣信息的,這個Activity和該天氣預報程序的所有其它Activity具體相同的affinity值,並且還將allowTaskReparenting屬性設置成true了。這個時候,你自己的應用程序通過Intent去啟動了這個用於顯示天氣信息的Activity,那麼此時這個Activity應該是和你的應用程序是在同一個任務當中的。但是當把天氣預報程序切換到前台的時候,這個Activity又會被轉移到天氣預報程序的任務當中,並顯示出來,因為它們擁有相同的affinity值,並且將allowTaskReparenting屬性設置成了true。

清空返回棧

如何用戶將任務切換到後台之後過了很長一段時間,系統會將這個任務中除了最底層的那個Activity之外的其它所有Activity全部清除掉。當用戶重新回到這個任務的時候,最底層的那個Activity將得到恢復。這個是系統默認的行為,因為既然過了這麼長的一段時間,用戶很有可能早就忘記了當時正在做什麼,那麼重新回到這個任務的時候,基本上應該是要去做點新的事情了。

當然,既然說是默認的行為,那就說明我們肯定是有辦法來改變的,在元素中設置以下幾種屬性就可以改變系統這一默認行為:

alwaysRetainTaskState
如果將最底層的那個Activity的這個屬性設置為true,那麼上面所描述的默認行為就將不會發生,任務中所有的Activity即使過了很長一段時間之後仍然會被繼續保留。

clearTaskOnLaunch
如果將最底層的那個Activity的這個屬性設置為true,那麼只要用戶離開了當前任務,再次返回的時候就會將最底層Activity之上的所有其它Activity全部清除掉。簡單來講,就是一種和alwaysRetainTaskState完全相反的工作模式,它保證每次返回任務的時候都會是一種初始化狀態,即使用戶僅僅離開了很短的一段時間。

finishOnTaskLaunch
這個屬性和clearTaskOnLaunch是比較類似的,不過它不是作用於整個任務上的,而是作用於單個Activity上。如果某個Activity將這個屬性設置成true,那麼用戶一旦離開了當前任務,再次返回時這個Activity就會被清除掉。


部分內容轉發自:http://blog.csdn.net/guolin_blog/article/details/41087993

   
  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved