Activity啟動模式圖文詳解:standard, singleTop, singleTask 以及 singleInstance

這是一個針對技術開發者的一個應用,你可以在掘金上獲取最新最優質的技術干貨,不僅僅是Android知識、前端、后端以至于產品和設計都有涉獵,想成為全棧工程師的朋友不要錯過!

英文原文:Understand Android Activity's launchMode: standard, singleTop, singleTask and singleInstance另外關于啟動模式還有篇很好的文章:Android中Activity四種啟動模式和taskAffinity屬性詳解

Activity是安卓上最聰明的設計之一,優秀的內存管理讓多任務完美運行在最流行的操作系統之上。并不是讓Activity在屏幕上啟動就完事了,其啟動方式也是需要關注的。這個話題的內容很多,其中很重要的就是啟動模式(launchMode)。這也是我們這篇博客要討論的內容。

因為不同的Activity有不同的目的。有些被設計成每發送一個intent都單獨一個Activity工作,比如郵件客戶端中撰寫郵件的Activity,而有些則被設計成單例的,比如郵件收件箱的Activity。

這就是為什么指明一個Activity是否需要新建還是使用現有Activity是很有必要的,否則可能導致糟糕的用戶體驗。多虧了安卓的核心工程師,讓launchMode可以幫助你專門應對這種情況。

設置一個launchMode

一般地,我們可以直接在AndroidManifest.xml 標簽的一個屬性中設置launchMode,如下:

android:name=".SingleTaskActivity"

android:label="singleTask?launchMode"

android:launchMode="singleTask">

有4種類型的launchMode,我們一個一個的看。

standard

這是默認的模式。

這種模式下,當Intent發送的時候,Activity總是被創建一個新的出來單獨工作。想象一下,如果有發送10個撰寫郵件的Intent,那么將有10個不同的Activity啟動。

Lollipop之前設備上的表現

這種Activity將被創建并置于棧頂,和發送intent的Activity處于同一個任務中。注:一般來講,安卓第三個虛擬鍵所列出的那些就是任務。

下面的圖片顯示了向標準啟動模式的Activity分享照片時的情況。雖然分別來自不同的應用,但仍然它會和發送intent的Activity處于同一個任務中。

注:從圖中可以看出分享圖片的是Gallery應用。

同時你會看到此時任務管理器是這樣的(有一點怪異)。

如果我們切換到另外一個應用然后再切回到Gallery,你會發現standard launchMode啟動的Activity仍然在Gallery任務的上面,導致在操作Gallery之前,我們必須首先結束這個額外的Activity。

Lollipop設備上的表現

如果Activity都是來自同一個應用,其表現和Lollipop之前的設備一樣,在任務的頂端。

但是如果intent來自其他應用,將創建一個新的任務,同時新創建的Activity會被作為一個根Activity,如下:

注:圖片中的Task#2和Task#3分別表示兩個任務,序號大的比序號小的后啟動。

下面是任務管理器中的樣子:

發生這種情況的原因是Lollipop中任務管理系統做了修改,讓它看起來更合理了。因為它們在不同的任務中,你可以直接切回Gallery,你還可以觸發另一個Intent,創建新的與之前相同的任務。

撰寫郵件的Activity或者發布社交網絡狀態的Activity都是采用這種Activity的例子。如果你希望Activity單獨服務于一個Intent,就可以考慮standard啟動模式。

singleTop

接下來就是singleTop模式。它的表現幾乎和standard模式一模一樣,一個singleTop Activity 的實例可以無限多,唯一的區別是如果在棧頂已經有一個相同類型的Activity實例,Intent不會再創建一個Activity,而是通過onNewIntent()被發送到現有的Activity。

在singleTop模式下我們需要同時在onCreate() 和 onNewIntent()中處理發來的intent,以滿足不同情況。

這種啟動模式的用例之一就是搜索功能。假設我們創建了一個搜索框,點擊搜索的時候將導航到一個顯示搜索結果列表的SearchActivity中,為了更好的用戶體驗,這個搜索框一般也會被放到SearchActivity中,這樣用戶想要再次搜索就不需要按返回鍵。

想像一下,如果每次顯示搜索結果的時候我們都啟動一個新的activity,10次搜索10個activity,那樣當我們想返回最初的那個activity的時候需要按10次返回。

所以我們應該這樣,如果棧頂已經有一個SearchActivity,我們將Intent發送給現有的activity,讓它來更新搜索結果。這樣就只會有一個在棧頂的SearchActivity,只需點一次back就可以回到之前的activity。

不管怎樣,singleTop和它的調用者處在一個任務中。如果你想要讓intent發送給另一個任務中處于棧頂的Activity,是不行的。

而當Intent來自于另外一個應用的時候,新的Activity的啟動方式和standard模式是一致的(pre-Lollipop:處于調用者任務的棧頂,Lollipop:會創建一個新的任務)。

singleTask

這種模式和standard以及singleTop有很大不同。singleTask模式的Activity只允許在系統中有一個實例。如果系統中已經有了一個實例,持有這個實例的任務將移動到頂部,同時intent將被通過onNewIntent()發送。如果沒有,則會創建一個新的Activity并置放在合適的任務中。

在同一個應用中的情況

如果系統中還沒有singleTask的Activity,會新創建一個,并放在同一任務的棧頂。

但是如果已經存在,singleTask Activity上面的所有Activity將以合適的方式自動銷毀,讓我們想要顯示的Activity處于棧頂。同時Intent也會通過onNewIntent()方法發送到這個singleTask Activity。

在用戶體驗方面,可能不是很合理,但是它就是這樣設計的...

你可能注意到了官方文檔中提到的一個問題:

系統會創建一個新的任務,并將這個Activity實例化為新任務的根部(root)。

但從實驗結果來看,并不是這么回事。singleTask Activity仍然在任務的Activity棧頂,我們可以從dumpsys activity 命令顯示上看出來:

Taskid#239

TaskRecord{428efe30#239?A=com.thecheesefactory.lab.launchmode?U=0?sz=2}

Intent

{act=android.intent.action.MAIN?cat=[android.intent.category.LAUNCHER]

flg=0x10000000

cmp=com.thecheesefactory.lab.launchmode/.StandardActivity}

Hist#1:?ActivityRecord{429a88d0?u0?com.thecheesefactory.lab.launchmode/.SingleTaskActivity?t239}

Intent{cmp=com.thecheesefactory.lab.launchmode/.SingleTaskActivity}

ProcessRecord{4224313018965:com.thecheesefactory.lab.launchmode/u0a123}

Hist#0:?ActivityRecord{425fec98?u0?com.thecheesefactory.lab.launchmode/.StandardActivity?t239}

Intent

{act=android.intent.action.MAIN?cat=[android.intent.category.LAUNCHER]

flg=0x10000000

cmp=com.thecheesefactory.lab.launchmode/.StandardActivity}

ProcessRecord{4224313018965:com.thecheesefactory.lab.launchmode/u0a123}

如果你希望singleTask Activity表現的和文檔中描述的一致,你需要為singleTask Activity設置taskAffinity屬性。

android:name=".SingleTaskActivity"

android:label="singleTask?launchMode"

android:launchMode="singleTask"

android:taskAffinity="">

這里是啟動SingleTaskActivity的的結果(使用了taskAffinity之后)。

是否使用taskAffinity取決于你自己。

和其他應用一起工作的情況

一旦intent是從另外的應用發送過來,并且系統中也沒有任何Activity的實例,則會創建一個新的任務,并且新的Activity被作為根Activity創建。

除非擁有這個singleTask Activity 的應用已經存在,那樣的話,新建的Activity會置于這個任務的上面(而不是新建一個任務)。

In case that there is an Activity instance existed?in any Task, the whole Task would be moved to top and every single Activity placed above the singleTask Activity will be destroyed with?lifecycle.If back button is pressed, user has to travel through the Activities in the stack before going back to the caller?Task.

假設已經有了一個Activity的實例,不管它是在哪個任務中(包括上面的那種情況,在用于這個Activity的應用中),整個任務將被移到頂端,而singleTask ?Activity上面的所有 Activity 都將被銷毀, 用戶需要按back鍵遍歷玩棧中的Activity才能回到調用者任務。

這種模式的應用案例有。郵件客戶端的收件箱或者社交網絡的時間軸。這些Activity一般不會設計成擁有多個實例,singleTask可以滿足。但是在使用這種模式的時候必須要明智,因為有些Activity會在用戶不知情的情況下被銷毀。

singleInstance

這個模式非常接近于singleTask,系統中只允許一個Activity的實例存在。區別在于持有這個Activity的任務中只能有一個Activity:即這個單例本身。If?another Activity is called from this kind of Activity, a new Task would be automatically created to place that new Activity. Likewise, if singleInstance Activity is called, new Task would be created to place the Activity.

不過結果卻很怪異,從dumpsys提供的信息來看,似乎系統中有兩個任務但任務管理器中只顯示一個,即最后被移到頂部的那個。導致雖然后臺有一個任務在運行,我們卻無法切換回去,這一點也不科學。

下面是當singleInstance Activity被調用的同時棧中已經有一些Activity的情況下所發生的事情:

本來有兩個任務,但是任務管理器中卻只顯示一個任務:

Since this Task could has only one Activity, we couldn't switch back to Task #1 anymore. Only?way to do so is to relaunch the application from launcher but it appears that the singleInstance Task would be hidden in the background instead.

因為這個任務只有一個Activity,我們再也無法切回到任務#1了。唯一的辦法是重新在launcher中啟動這個應用。?but之后的沒有翻譯,因為我也不明白作者的意思。

不過這個問題也有解決方案,就像我們在singleTask Acvity中做的,只要為singleInstance Activity設置taskAffinity屬性就可以了。

android:name=".SingleInstanceActivity"

android:label="singleInstance?launchMode"

android:launchMode="singleInstance"

android:taskAffinity="">

現在科學多了。

這種模式很少被使用。實際使用的案例如Launcher的Activity或者100%確定只有一個Activity的應用。總之除非完全有必要,不然我不建議使用這種模式。

Intent Flags

除了在AndroidManifest.xml中直接設置launch mode,我們還可以通過叫做Intent Flags的東西設置更多的行為,比如:

Intentintent=newIntent(StandardActivity.this,StandardActivity.class);

intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);

startActivity(intent);

這段代碼將會啟動一個singleTop啟動模式的的StandardActivity。

有許多種Flag可以使用,更多的請參考Intent

希望這篇文章對你有用。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,197評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,415評論 3 415
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,104評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,884評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,647評論 6 408
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,130評論 1 323
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,208評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,366評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,887評論 1 334
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,737評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,939評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,478評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,174評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,586評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,827評論 1 283
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,608評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,914評論 2 372

推薦閱讀更多精彩內容