天天看點

Tasks and Back Stack-Android 6.0開發者文檔一、說明二、儲存activity狀态三、管理task

原文位址:Tasks and Back Stack

  • 一、說明
  • 二、儲存activity狀态
  • 三、管理task
    • 3.1 定義launch mode
      • 3.1.1 使用manifest指定
      • 3.1.2 使用intent的flag指定
    • 3.2 處理affinity
    • 3.3 清理回退棧
    • 3.4 啟動task

一、說明

一個應用通常會包含多個activity。每個activity都可以啟動其他的activity。例如,郵件應用會有一個activity顯示資訊清單,當使用者選擇了某個資訊時,它會啟動新的activity以顯示資訊的具體内容。

activity甚至可以啟動其他應用的activity。例如,如果你的應用想要發送一封郵件,那麼你可以定義一個加入了“send”action的intent,其他應用的聲明了可以處理此intent的activity将會被啟動(如果有多個activity可以處理此intent,系統會顯示對話框讓使用者選擇使用哪個activity)。當郵件發送完畢後,你的activity會重新進入resumed狀态,就好像發送郵件的activity是你的應用的一部分一樣。Android系統通過将這些activity放在同一個任務(task)中做到應用的無縫切換。

任務(task)是使用者在進行某些操作時互動的activity的集合。這些activity被放在一個棧中(back stack),表示activity打開的順序。

裝置的首頁(Home screen)是大多數task的起點。當使用者點選launcher的圖示或者首頁的快捷方式時,應用的task會到前台。如果這個應用的task不存在(此應用在最近沒有被打開過),那麼系統會建立一個task,并将應用的“main”activity啟動放置在棧底。

目前activity啟動另一個activity時,新的activity會push到棧頂并且獲得焦點。之前的activity會進入sotpped狀态。當activity進入stopped狀态時,系統會儲存界面的狀态。當使用者點選傳回鍵時,目前activity會被pop出棧頂并銷毀,之前的activity進入resume狀态(此activity的UI狀态會恢複)。棧中的activity不會重新排列,隻會進行push和pop操作——啟動新的activity時push到棧頂,使用者使用傳回鍵離開此activity時pop出棧頂。是以說,這個回退棧是“先進後出”(last in,first out)的結構。下圖1描述了不同動作對于回退棧的影響。

Tasks and Back Stack-Android 6.0開發者文檔一、說明二、儲存activity狀态三、管理task

如果使用者連續按傳回鍵,那麼回退棧中的activity将會依次pop出棧頂,直到傳回到首頁。當所有activity都移出回退棧時,task就不存在了。

當使用者啟動一個新的task或按home鍵時,task會進入背景。進入背景後,task中所有的activity都會進入stopped狀态,但是回退棧仍然存在——此task隻是失去了焦點,被另一個task覆寫了,如下圖2。task可以重新回到前台,使用者可以繼續他們的工作。例如,目前task(Task A)有三個activity在棧中,使用者按下home鍵,從應用launcher啟動一個新應用。在這個過程中,當首頁顯示時,Task A進入背景。當新的應用啟動時,系統為新應用建立一個task(Task B)。在與新應用互動完成後,使用者再次點選home鍵,然後選中Task A中的應用。這時,Task A會再次進入前台————棧中的三個activity都存在着,且棧頂的activity進入resume狀态。另外,使用者還可以用相同的方式切換到Task B。

Tasks and Back Stack-Android 6.0開發者文檔一、說明二、儲存activity狀态三、管理task
注意:背景可以存在多個task。如果使用者在背景運作了太多task,系統可能會為了回收記憶體銷毀一些背景activity,進而導緻activity狀态丢失。具體資訊見下文。

由于回退棧中的activity不會重排,是以如果你啟動一個已經存在的activity,那麼會建立一個新的此activity執行個體并加入到棧頂(而不是将已存在的activity移到棧頂)。是以,你的應用的activity可能會被執行個體化多次,如下圖3。使用者點選傳回鍵時,activity執行個體會按照打開的順序依次顯示。如果你不希望你的activity被執行個體化多次,你可以通過某種方式做到,具體方法見下文。

Tasks and Back Stack-Android 6.0開發者文檔一、說明二、儲存activity狀态三、管理task

下面對activity和task的行為做個總結:

  • 當activity A啟動activity B時,activity A進入stopped狀态,系統會儲存它的狀态資訊(如滑動的位置、清單中填寫的資訊)。如果使用者點選了傳回鍵,那麼activity A會進入resume狀态并恢複狀态資訊
  • 當使用者按下home鍵離開目前task時,目前activity會進入stopped狀态,它所在的task進入背景。系統會儲存task中所有activity的狀态資訊。如果後面使用者點選task對應的launcher圖示,那麼此task會重新進入前台,棧頂的activity進入resume狀态
  • 如果使用者按下傳回鍵,目前activity會被pop出棧頂并且銷毀。之前的activity會進入resume狀态。activity被銷毀後,系統不會再儲存activity的資訊
  • activity可能被執行個體化多次

二、儲存activity狀态

預設情況下,系統會儲存進入stopped狀态的activity的資訊。這樣的話,當user傳回到之前的activity時,activity就可以恢複之前的界面了。但是,你還是應該自己通過回調方法儲存activity狀态,以防activity被銷毀後需要恢複狀态。

當你的activity進入stopped狀态時,系統可能會因為記憶體緊張銷毀你的activity,系統儲存的你的activity的狀态也會丢失。這時,你的activity仍然在回退棧中,當此activity重新回到棧頂時,系統會重建它(recreate而不是resume)。為了避免丢失使用者操作資訊,你應該通過onSaveInstanceState()方法儲存使用者操作。

三、管理task

就如之前所說的,Android管理task和回退棧的方式,是将activity依次放在同一個棧中(先進後出),這種方式可以滿足大多數應用的需求,你不必擔心你的activity怎樣與task互動或者activity怎樣存在于回退棧中。如果你有“不正常需求”的話,比如你希望啟動activity時建立一個task并加入其中(而不是加入到目前task),或者你希望啟動activity時讓已存在的它的執行個體到達棧頂(而不是建立一個activity執行個體加入到棧中),或者你希望使用者離開此task時将除根activity的所有activity清空等,就需要知道一些非正常操作了。

你可以通過在manifest檔案中聲明

<activity>

的屬性,或者在startActivity()傳入的intent中加入flag做到以上的需求。

你可能使用到的

<activity>

的屬性有:

  • taskAffinity
  • launchMode
  • allowTaskReparenting
  • clearTaskOnLaunch
  • alwaysRetainTaskState
  • finishOnTaskLaunch

可能使用的intent的flag有:

  • FLAG_ACTIVITY_NEW_TASK
  • FLAG_ACTIVITY_CLEAR_TOP
  • FLAG_ACTIVITY_SINGLE_TOP

下面的部分将會說明,

<activity>

屬性和intent的flag,會對activity與task的互動和在回退棧中的行為,造成怎樣的影響。

另外,在“overview screen”部分也有task和activity如何管理的内容,你可以在overview screen部分檢視更多内容。通常,你允許系統管理task和activity在overview screen界面的顯示即可,不需要改變預設設定。

注意:大多數應用都不需要修改activity和task的預設實作。如果你确定你的activity需要改變這些内容,請確定activity在啟動時、從其他activity或者task中傳回時可以符合使用者的預期

3.1 定義launch mode

launch mode表示你的activity怎樣加入到task中。你可以通過兩種方式添加launch mode:

  • 使用manifest檔案。在manifest檔案中,你可以指定當activity啟動時怎樣與task互動
  • 使用intent的flag。當你調用startActivity()方法時,可以在傳入的intent中添加flag。此flag表示新的activity怎樣與目前task互動

如果activity A啟動activity B,那麼activity B可以在manifest檔案中指定它将怎樣與目前task互動,同時,activity A也可以要求activity B與目前task的互動方式(通過flag)。如果activity A和activity B都指定了activity B與task的互動方式,那麼activity A的請求(intent中的flag指定)會高于activity B的請求(manifest中指定)。

注意:有些launch mode,在manifest檔案中可用,而intent的flag不可用。同樣的,有些launch mode在intent的flag中可用,在manifest中不可用

3.1.1 使用manifest指定

你可以在manifest檔案中的

<activity>

标簽中添加launchMode屬性來指定launch mode。

launchMode屬性可用的launch mode有以下幾種:

  • standard(預設)。系統會建立一個activity執行個體加入到它被啟動的intent所在的棧中。此activity可以被執行個體化多次,它們可以處于不同的task中,同一個task也可以有多個此activity執行個體
  • singleTop。如果目前task的棧頂已經是此activity的執行個體了,那麼系統會調用此activity的onNewIntent()方法,而不是建立一個activity。此activity可以被執行個體化多次,并且存在于不同的task中,一個task可以有多個此activity的執行個體。

    注意:當新的activity的執行個體被建立時,使用者可以通過傳回鍵傳回到上一個activity。但是,如果棧頂的activity是singleTop模式且通過onNewIntent()更新了某些資料,那麼使用者就不能通過傳回鍵回到之前的狀态了

  • singleTask。系統會建立一個新的task,并且将此activity作為根activity放置在task中。如果此activity的執行個體已經存在于其他task中,系統會調用此activity的onNewIntent()方法,而不是建立一個activity執行個體(此時如果此activity上面還有activity,會全部彈出,讓此activity成為棧頂)。此activity的執行個體同一時間僅允許存在一個。

    注意:雖然此activity是在新的task中,但是傳回鍵還是可以傳回之前的activity

  • singleInstance。類似于singleTask,但是singleInstance不會将其他activity加入到此activity存在的task中。此activity将會是此task的唯一成員,此activity啟動的其他activity會在其他task啟動

例如,Android浏覽器應用可以将

<activity>

的launchMode設定為singleTask,這意味着,如果你的應用啟動了此浏覽器,它的activity不會跟你的應用的activity處于同一個task,而是建立一個task并把它的activity放進去(如果此浏覽器已經有了一個task,那麼此task會到前台并處理新的intent)

不管被啟動的activity和啟動它的activity是在同一個task還是不同task,使用者都可以用傳回鍵傳回到之前的activity。但是,如果目标activity為singleTask且已經存在于背景的task,那麼當啟動目标activity時,會将它所在的task放到前台。這樣的話,回退棧中就會包含背景task的所有activity,下圖4顯示了當這種情況發生時系統的動作

Tasks and Back Stack-Android 6.0開發者文檔一、說明二、儲存activity狀态三、管理task

注意:通過launchMode屬性指定的launch mode可以被intent中的flag覆寫(overridden)

3.1.2 使用intent的flag指定

在啟動activity時,你可以在startActivity()方法傳入的intent中添加flag,來修改目标activity預設與task的互動方式。你可以使用的flag如下:

  • FLAG_ACTIVITY_NEW_TASK。在新的task中啟動activity。如果有某個task已經有了目标activity的執行個體,那麼系統會将此task放在前台,并通過onNewIntent()将資料發送給目标activity(原文中還有一句——此flag的作用與“singleTask”相同。但是實踐發現,并不相同,不同之處為:1.此flag啟動的activity如果存在于其他task,而且其他task的此activity執行個體之上還有其他activity,那麼不會像“singleTask”那樣将其他activity彈出,2.此flag被taskAffinity嚴重影響,會影響其是否建立task,3.此flag不保證activity在所有棧中唯一,甚至不保證在同一棧中唯一。是以這個flag的描述應該為:嘗試尋找一個合适的task啟動activity,不存在這樣的task則建立一個task)
  • FLAG_ACTIVITY_SINGLE_TOP。如果啟動的activity是目前(原文檔沒有“目前”兩個字,但是我在寫demo測試的時候發現,隻有目前task的棧頂為目标activity時,FLAG_ACTIVITY_SINGLE_TOP才有用)task頂部activity,那麼不會建立此activity,而是調用其onNewIntent()方法。此flag的作用與“singleTop”相同
  • FLAG_ACTIVITY_CLEAR_TOP。如果啟動的activity已經在目前task中,那麼不會建立此activity,而是将棧中此activity執行個體之上的activity銷毀,然後調用其onNewIntent()方法。FLAG_ACTIVITY_CLEAR_TOP通常與FLAG_ACTIVITY_NEW_TASK一起使用。

    注意:如果目标activity的launch mode為“standard”,那麼它也會被彈出回退棧,系統會在它的位置建立一個activity執行個體

3.2 處理affinity

affinity表示activity應該屬于哪個task。預設情況下,同一個應用的所有activity有相同的affinity。是以預設情況下同一應用的所有activity都會放在同一個task中。但是,你可以修改activity的預設affinity。不同應用的activity可以使用相同的affinity,同一應用的不同activity也可以使用不同的affinity。

你可以在

<activity>

标簽下添加taskAffinity屬性以修改activity的affinity。

taskAffinity是一個string。它的值不能為manifest檔案的預設包名,因為系統将包名作為應用的預設taskAffinity。

affinity在以下兩個情況起作用:

  • 啟動目标activity時添加了FLAG_ACTIVITY_NEW_TASK。

    預設情況下,被啟動的activity會加入到調用startActivity()的activity所在的task中。但是,如果startActivity()傳入的intent中添加了FLAG_ACTIVITY_NEW_TASK,那麼系統會尋找一個新的task以存放新的activity。通常,那會是一個新的task。但是,如果已經存在一個與新activity的affinity一緻的task,那麼新activity會加入到此task中。

    如果這個flag導緻啟動了一個新task,且使用者通過home鍵離開了此task,那麼你必須提供某種方式讓使用者傳回到此task。有些實體,如通知欄,總是在其他task中啟動activity,即總是在startActivity()時傳入FLAG_ACTIVITY_NEW_TASK。如果你的activity可能被外部實體用FLAG_ACTIVITY_NEW_TASK調用,請注意為使用者提供某種方法回到此task,例如通過launcher圖示

  • activity的allowTaskReparenting屬性為“true”。

    如果将activity此屬性設為true,那麼當與此activity的affinity相同的task進入前台時,此activity會從啟動它的task移動到此task中。

    例如,某個activity可以檢視某個城市的天氣,它是一個旅遊應用的一部分,它的affinity為預設,allowTaskReparenting為true,此時,你的應用啟動此天氣activity,那麼此activity跟你自己的activity在同一task中,然後,如果旅遊應用的task進入前台,那麼此天氣activity會移動到旅遊應用的task并顯示

    提示:如果你的應用包含多個使用者可以看到的“應用”(即launcher有多個),那麼你最好根據不同的launcher為相關的activity配置設定不同的taskAffinity

3.3 清理回退棧

如果使用者離開某個task很長時間,那麼系統會清理此task除了根activity的所有activity。當使用者傳回到此task時,隻有根activity會恢複。

如果你想要改變這一點,可以修改以下activity屬性:

  • alwaysRetainTaskState。如果某個task的根activity的這個屬性為true,那麼系統就不會在長時間未回到此task後清理task了
  • clearTaskOnLaunch。如果某個task的根activity的這個屬性設定為true,那麼不論使用者在離開此task後過了多久,系統都會清理task。換句話說,它就是alwaysRetainTaskState的反面。使用者總是會回到此task的初始狀态,不論離開此task多久
  • finishOnTaskLaunch。這個屬性類似于clearTaskOnLaunch,但是它隻作用于單個activity,而不是整個task。它會關閉任何activity,包括根activity。如果将此屬性設定為true,那麼task中的此activity将隻用于此次會話,如果使用者離開此task,那麼當使用者傳回到此task時,此activity不會顯示

3.4 啟動task

你可以将一個activity設定為task的入口(添加intent filter:action為“

android.intent.action.MAIN

”,category為“

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 filter後,會在launcher中顯示代表這個activity的圖示和标簽,使用者可以:1.通過此圖示啟動此activity,2.在此activity所在task啟動後通過此圖示傳回到此task。

第二個功能很重要。Android要求:在離開task後,使用者必須可以通過某種方式回到此task。是以,launch mode為singleTask或singleInstance的activity應該添加有ACTION_MAIN和CATEGORY_LAUNCHER的filter。想象一下,如果沒有此filter會發生什麼:某個intent啟動了“singleTask”的activity,系統建立了一個task,使用者在這個task中有一些動作,然後,使用者點選home鍵,此時task在背景且不可見,那麼使用者将不能回到此task。

當然,如果你壓根不希望使用者傳回到此activity,可以将

<activity>

的finishOnTaskLaunch設定為true。

繼續閱讀