【建議收藏系列】:我打賭你一定沒搞明白的Activity啟動模式!

南方吳彥祖_藍斯發表於2020-11-06

一個應用程式當中通常都會包含很多個Activity,每個Activity都是一個具有特定的功能,並且可以讓使用者進行操作的元件。另外,Activity之間可以相互啟動,當前應用的Activity甚至可以去啟動其他應用的Activity。

比如你的應用希望去傳送一封郵件,你就可以定義一個具有"send"動作的Intent,並且傳入一些資料,如對方郵箱地址、郵件內容等。這樣,如果另外一個應用程式中的某個Activity宣告自己是可以響應這種Intent的,那麼這個Activity就會被開啟。

當郵件傳送之後,按下返回鍵仍然還是會回到你的應用程式當中,這讓使用者看起來好像剛才那個編寫郵件的Activity就是你的應用程式當中的一部分。所以說,即使有很多個Activity分別都是來自於不同應用程式的,Android系統仍然可以將它們無縫地結合到一起。那這一切是怎麼實現的呢?這就要講到本文要介紹的Activity任務棧以及Activity啟動模式了。

任務棧是什麼

任務棧Task,是一種用來放置Activity例項的容器,他是以棧的形式進行盛放,也就是所謂的先進後出,主要有2個基本操作:壓棧和出棧,其所存放的Activity是不支援重新排序的,只能根據壓棧和出棧操作更改Activity的順序。

啟動一個Application的時候,系統會為它預設建立一個對應的Task,用來放置根Activity。預設啟動Activity會放在同一個Task中,新啟動的Activity會被壓入啟動它的那個Activity的棧中,並且顯示它。當使用者按下回退鍵時,這個Activity就會被彈出棧,按下Home鍵回到桌面,再啟動另一個應用,這時候之前那個Task就被移到後臺,成為後臺任務棧,而剛啟動的那個Task就被調到前臺,成為前臺任務棧,Android系統顯示的就是前臺任務棧中的Top例項Activity。

任務棧的作用

以往基於應用(application)的程式開發中,程式具有明確的邊界,一個程式就是一個應用,一個應用為了實現功能可以採用開闢新執行緒甚至新程式來輔助,但是應用與應用之間不能複用資源和功能。

而Android引入了基於元件開發的軟體架構,雖然我們開發android程式,仍然使用一個apk工程一個Application的開發形式,但是對於Aplication的開發就用到了Activity、service等四大元件,其中的每一個元件,都是可以被跨應用複用的,這就是android的神奇之處。

雖然元件可以跨應用被呼叫,但是一個元件所在的程式必須是在元件所在的Aplication程式中。由於android強化了元件概念,弱化了Aplication的概念,所以在android程式開發中,A應用的A元件想要使用拍照或錄影的功能就可以不用去針對Camera類進行開發,直接呼叫系統自帶的攝像頭應用(稱其B應用)中的元件(稱其B元件)就可以了,但是這就引發了一個新問題,A元件執行在A應用中,B元件執行在B應用中,自然都不在同一個程式中,那麼從B元件中返回的時候,如何實現正確返回到A元件呢?Task就是來負責實現這個功能的,它是從使用者角度來理解應用而建立的一個抽象概念。因為使用者所能看到的元件就是Activity,所以Task可以理解為實現一個功能而負責管理所有用到的Activity例項的棧。

棧是一個先進後出的線性表,根據Activity在當前棧結構中的位置,來決定該Activity的狀態。正常情況下,當一個Activity啟動了另一個Activity的時候,新啟動的Activity就會置於任務棧的頂端,並處於活動狀態,而啟動它的Activity雖然成功身退,但依然保留在任務棧中,處於停止狀態,當使用者按下返回鍵或者呼叫finish()方法時,系統會移除頂部Activity,讓後面的Activity恢復活動狀態。

當然,世界不可能一直這麼“和諧”,可以給Activity設定一些“特權”,來打破這種“和諧”的模式,這種特權,就是透過在AndroidManifest檔案中的屬性andorid:launchMode來設定或者透過Intent的flag來設定的,下面就先介紹下Activity的幾種啟動模式。

standard

預設模式,可以不用寫配置。在這個模式下,都會預設建立一個新的例項。因此,在這種模式下,可以有多個相同的例項,也允許多個相同Activity疊加。應用場景:絕大多數Activity。

【建議收藏系列】:我打賭你一定沒搞明白的Activity啟動模式!

如果以這種方式啟動的Activity被跨程式呼叫,在5.0之前新啟動的Activity例項會放入傳送Intent的Task的棧的頂部,儘管它們屬於不同的程式,這似乎有點費解看起來也不是那麼合理,所以在5.0之後,上述情景會建立一個新的Task,新啟動的Activity就會放入剛建立的Task中,這樣就合理的多了。

singleTop

棧頂複用模式,如果要開啟的activity在任務棧的頂部已經存在,就不會建立新的例項,而是呼叫 onNewIntent() 方法。避免棧頂的activity被重複的建立。應用場景:在通知欄點選收到的通知,然後需要啟動一個Activity,這個Activity就可以用singleTop,否則每次點選都會新建一個Activity。當然實際的開發過程中,測試妹紙沒準給你提過這樣的bug:某個場景下連續快速點選,啟動了兩個Activity。如果這個時候待啟動的Activity使用 singleTop模式也是可以避免這個Bug的。

【建議收藏系列】:我打賭你一定沒搞明白的Activity啟動模式!

同standard模式,如果是外部程式啟動singleTop的Activity,在Android 5.0之前新建立的Activity會位於呼叫者的Task中,5.0及以後會放入新的Task中。

singleTask

棧內複用模式, activity只會在任務棧裡面存在一個例項。如果要啟用的activity,在任務棧裡面已經存在,就不會建立新的activity,而是複用這個已經存在的activity,呼叫 onNewIntent() 方法,並且清空這個activity任務棧上面所有的activity。應用場景:大多數App的主頁。

對於大部分應用,當我們在主介面點選回退按鈕的時候都是退出應用,那麼當我們第一次進入主介面之後,主介面位於棧底,以後不管我們開啟了多少個Activity,只要我們再次回到主介面,都應該使用將主介面Activity上所有的Activity移除的方式來讓主介面Activity處於棧頂,而不是往棧頂新加一個主介面Activity的例項,透過這種方式能夠保證退出應用時所有的Activity都能報銷燬。

在跨應用Intent傳遞時,如果系統中不存在singleTask Activity的例項,那麼將建立一個新的Task,然後建立SingleTask Activity的例項,將其放入新的Task中。

1:假如目前有個任務棧T1中的情況是ABC,這個時候ActivityD以singleTask模式請求啟動,其所需要的任務棧正是T1,則系統會直接建立D的例項並將其入棧到T1中。

【建議收藏系列】:我打賭你一定沒搞明白的Activity啟動模式!

2:假如DActivity啟動所需要的任務棧為T2,由於T2和D的例項均不存在,那麼系統會先建立任務棧T2,然後再建立D的例項並將其入棧到T2中。我們可以透過設定Activity的taskAffinity屬性來模擬這一場景。

<activity android:name=".SingleTaskActivity" android:label="singleTask launchMode" android:launchMode="singleTask" android:taskAffinity=""></activity>
【建議收藏系列】:我打賭你一定沒搞明白的Activity啟動模式!

3:如果D所需的任務棧為T3,並且當前任務棧T3的情況為ADBC,根據棧內複用的原則,此時D不會重新建立,系統會把D切換到棧頂並呼叫其onNewIntent()方法,同時由於singleTask預設具有ClearTop的效果,會導致棧內所有在D上面的Activity全部出棧,於是最終T3的情況為AD。

【建議收藏系列】:我打賭你一定沒搞明白的Activity啟動模式!

4:假如目前有兩個任務棧,前臺任務棧T4的情況為AB,後臺任務棧t4裡存有CD,假設CD的啟動模式均為singleTask,現在由B去啟動D,那麼整個後臺任務都會被切換到前臺,這個時候整個棧就變成了ABCD。

【建議收藏系列】:我打賭你一定沒搞明白的Activity啟動模式!

5:假如上面的其他條件不變,B啟動的是C而不是D,那麼整個棧的情況就變成了ABC,因為D在C上面,會被清理出棧。

【建議收藏系列】:我打賭你一定沒搞明白的Activity啟動模式!

singleInstance

單一例項模式,整個手機作業系統裡面只有一個例項存在。不同的應用去開啟這個activity 共享公用的同一個activity。他會執行在自己單獨,獨立的任務棧裡面,並且任務棧裡面只有他一個例項存在。應用場景:呼叫來電介面。這種模式的使用情況比較罕見,在Launcher中可能使用。或者你確定你需要使Activity只有一個例項。建議謹慎使用。

【建議收藏系列】:我打賭你一定沒搞明白的Activity啟動模式!

設定Intent的Flag

系統提供了兩種方式來設定一個Activity的啟動模式,除了在AndroidManifest檔案中設定以外,還可以透過Intent的Flag來設定一個Activity的啟動模式,下面我們在簡單介紹下一些Flag。

FLAG_ACTIVITY_NEW_TASK

使用一個新的Task來啟動一個Activity,但啟動的每個Activity都講在一個新的Task中。該Flag通常使用在從Service中啟動Activity的場景,由於Service中並不存在Activity棧,所以使用該Flag來建立一個新的Activity棧,並建立新的Activity例項。

FLAG_ACTIVITY_SINGLE_TOP

使用singletop模式啟動一個Activity,與指定android:launchMode=“singleTop”效果相同。

FLAG_ACTIVITY_CLEAR_TOP

使用SingleTask模式來啟動一個Activity,與指定android:launchMode=“singleTask”效果相同。

FLAG_ACTIVITY_NO_HISTORY

Activity使用這種模式啟動Activity,當該Activity啟動其他Activity後,該Activity就消失了,不會保留在Activity棧中。

LaunchMode與StartActivityForResult

我們在開發過程中經常會用到StartActivityForResult方法啟動一個Activity,然後在onActivityResult()方法中可以接收到上個頁面的回傳值,但你有可能遇到過拿不到返回值的情況,那有可能是因為Activity的LaunchMode設定為了singleTask。5.0之後,android的LaunchMode與StartActivityForResult的關係發生了一些改變。兩個Activity,A和B,現在由A頁面跳轉到B頁面,看一下LaunchMode與StartActivityForResult之間的關係:

【建議收藏系列】:我打賭你一定沒搞明白的Activity啟動模式!
【建議收藏系列】:我打賭你一定沒搞明白的Activity啟動模式!

這是為什麼呢?

這是因為ActivityStackSupervisor類中的startActivityUncheckedLocked方法在5.0中進行了修改。

在5.0之前,當啟動一個Activity時,系統將首先檢查Activity的launchMode,如果為A頁面設定為SingleInstance或者B頁面設定為singleTask或者singleInstance,則會在LaunchFlags中加入FLAG_ACTIVITY_NEW_TASK標誌,而如果含有FLAG_ACTIVITY_NEW_TASK標誌的話,onActivityResult將會立即接收到一個cancle的資訊。

而5.0之後這個方法做了修改,修改之後即便啟動的頁面設定launchMode為singleTask或singleInstance,onActivityResult依舊可以正常工作,也就是說無論設定哪種啟動方式,StartActivityForResult和onActivityResult()這一組合都是有效的。所以如果你目前正好基於5.0做相關開發,不要忘了向下相容,這裡有個坑請注意避讓。

總結

實際開發過程中如果採用比較合理的Activity啟動模式來做好任務棧的管理,可以事半功倍。在launchMode的選擇上首先要搞清楚當前的Activity的作用,以及實際使用場景來做出合理選擇。關於Activity任務棧的相關知識,短短一篇文章也很難涵蓋的全。

最後對於程式設計師來說,要學習的知識內容、技術有太多太多,要想不被環境淘汰就只有不斷提升自己, 從來都是我們去適應環境,而不是環境來適應我們!

這裡附上上述的技術體系圖相關的幾十套 騰訊、頭條、阿里、美團等公司19、20年的面試題,把技術點整理成了影片和PDF(實際上比預期多花了不少精力),包含 知識脈絡 + 諸多細節,由於篇幅有限,這裡以圖片的形式給大家展示一部分。

相信它會給大家帶來很多收穫:

【建議收藏系列】:我打賭你一定沒搞明白的Activity啟動模式!
【建議收藏系列】:我打賭你一定沒搞明白的Activity啟動模式!
960頁全網最全Android開發筆記
【建議收藏系列】:我打賭你一定沒搞明白的Activity啟動模式!
379頁的Android進階知識大全

資料太多,全部展示會影響篇幅,暫時就先列舉這些部分截圖,以上資源均免費分享,以上內容均放在了開源專案: github  中已收錄,大家可以自行獲取。

當程式設計師容易,當一個優秀的程式設計師是需要不斷學習的,從初級程式設計師到高階程式設計師,從初級架構師到資深架構師,或者走向管理,從技術經理到技術總監,每個階段都需要掌握不同的能力。早早確定自己的職業方向,才能在工作和能力提升中甩開同齡人。

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69983917/viewspace-2732740/,如需轉載,請註明出處,否則將追究法律責任。

相關文章