首頁>技術>

引言

當面試官說請你介紹一下activity啟動模式,大多數人都能整兩句,什麼棧頂複用啊棧內複用啊,不過,你確定你真的懂啟動模式嗎?

如果你能回答出下面的問題,那麼你可以直接退出當前介面。

假設有如下四個activity:

A(standard)B(singleTop)C(singleTask)D(singleInstance)

它們的啟動順序依次是ABCDABCD,請描述activity棧內變化。

基於互動的分析

例:1,使用者在主螢幕中點選應用的圖示啟動應用後,彈出了第一Activity介面:A,並依次打開了如下介面 A -> B -> C -> D。2,此時按下home鍵返回主螢幕,然後重新點選圖示啟動這個應用,我們會發現彈出的介面還是 D 而不是介面 A。3,當我們連續點選返回鍵時,應用中介面會按照啟動順序反向的依次展示,也就是D -> C -> B -> A -> 主螢幕

透過這個例子我們可以知道Android系統會為應用暫時性的儲存一組Activity啟動鏈,記錄啟動順序,這就引出了第一個概念:任務

任務

先說下任務的定義,Android官方把上述這種為了完成某些工作而鏈式啟動的一系列Activity合集稱之為 任務

我們都知道每個Activity都是互相獨立的介面,正是有了任務這樣的概念,多個Activity才能夠關聯起來組成一個完整的應用。

任務可以同時存在多個嗎

當然可以!

任務裡Activity必須是來自同一個應用嗎

當然不是!

例:當我們在社交軟體設定使用者頭像時一般會有拍照和相簿兩個選項,選擇拍照會跳轉到攝像機軟體,選擇相簿會跳到系統相簿軟體。透過這幾個軟體之間的共同合作完成了一次任務。

任務中的根Activity

<activity       android:name=".HelloActivity"">       <intent-filter>              <action android:name="android.intent.action.MAIN" />              <category android:name="android.intent.category.LAUNCHER" />        </intent-filter></activity>

當用戶點選主螢幕應用圖示開啟應用時,如果該應用最近未曾被使用過,則會建立一個任務,並將該應用中的入口Activity作為任務中的根Activity開啟。反之直接把該應用所在的任務調出來置於前臺即可。

瞭解完任務之後,我們就大概知道了上述幾個例子中Android系統如何儲存Activity使用狀態的規則。

返回棧

任務呢是一個特別虛的概念,是為了方便開發者理解才有的它,而系統中真正儲存Activity的是一個遵循先進後出原則的資料結構:棧。一般叫它返回棧(任務棧,堆疊,其實叫什麼的都有)。

返回棧是任務的實際載體,每個任務中所有的Activity都會按照各自的開啟順序儲存在對應的返回棧中。所以Android系統顯示介面的順序是先找到要顯示介面所在的任務,然後在對應的返回棧中找到顯示的Activity。

值得一提的是由於返回棧儲存結構的特殊性,外部只能訪問到棧頂的Activity,也就是最後入棧的那個。所以一個Activity想要能顯示在螢幕上那麼它必須存在於棧頂位置。

進棧與出棧

當前 Activity 啟動另一個 Activity 時,新的 Activity 會被推送到堆疊頂部,成為焦點顯示在螢幕上。 前一個 Activity 仍保留在堆疊中,但是處於停止狀態。

使用者按“返回”按鈕時,當前 Activity 會從堆疊頂部彈出(Activity 被銷燬),而前一個 Activity 恢復執行。如果使用者繼續按“返回”,堆疊中的相應 Activity 就會彈出,以顯示前一個 Activity,直到使用者返回主螢幕為止(或者,返回任務開始時正在執行的任意 Activity)。 當所有 Activity 均從堆疊中移除後,任務即不復存在。棧也就會被回收掉。

特殊的任務

透過前面的瞭解,我們知道如果要開啟新的介面需要把Activity例項放到當前任務對應的返回棧的棧頂。該操作是不管該Activity之前有沒有例項化過或者棧中是否已經存在了的。

但是,有些特殊情況下,我們會發現一些“例外”。

例1:當來自多個不同任務中的應用選擇使用系統瀏覽器訪問網頁的時候,瀏覽器應用並不會在每個任務的返回棧中都建立Activity,而是將所有網頁以選項卡的形式展示在同一個介面中。

本例中瀏覽器應用的Activity如果已經例項化過了就不會重新建立。

管理任務

很顯然上述兩個例子在實際使用中並不少見,對於這種特殊的情況我們需要針對性的管理任務,而眾所周知的啟動模式僅僅是其中的一種。

定義啟動模式

定義Activity的啟動模式其實就是定義一個Activity的新例項如何(是否)與當前任務做關聯。以什麼樣的方式進入到當前(或其他)任務中。

如果你只說Activity的啟動模式有四種,其實是不準確的,因為我們可以透過兩種方法定義不同的啟動模式:

使用AndroidManifest.xml中定義在AndroidManifest.xml中<activity>標籤下使用lauchMode屬性來指定當前這個activity的啟動模式。使用Intent標誌定義在呼叫startActivity(Intent intent)前,透過呼叫intent.addFlags()或者intent.setFlags()方法為Intent新增一個標誌,用於為將要啟動的Activity宣告啟動模式。

那兩者有什麼區別呢?

上述兩種方法均可以為activity宣告啟動模式,只是使用情景不同。

如果我們希望某個activity在任何情況下都會執行一種特殊的啟動模式,我們就可以採用AndroidManifest.xml的方法宣告。如果我們希望某個activity大多數情況下正常啟動,而少數情況下執行特殊的啟動模式,我們就可以在需要執行特殊啟動模式時在Intent中新增標誌宣告。如果一個activity兩種方式都聲明瞭的話,使用Intent標誌的方式要比AndroidManifest.xml的優先順序高。兩種方式中定義的啟動模式有些是不一樣的,Intent標誌中定義的某些啟動模式AndroidManifest.xml中沒有,反之一樣。我們常說的四種啟動模式其實說的是AndroidManifest.xml中定義的。使用AndroidManifest.xml宣告啟動模式

在清單檔案中宣告 Activity 時,您可以使用<activity>元素的 ][launchMode屬性指定 Activity 應該如何與任務關聯。

您可以分配給 launchMode 屬性的啟動模式共有四種:

standardsingleTopsingleTasksingleInstance

先不用管他們具體的操作是什麼,我們首先要知道這四種啟動模式可以分為兩大類:

standard和singleTop該類啟動模式的activity可以被多次的例項化,它們的例項可以放到任何任務中,並且可以位於返回棧的任何位置。singleTask和singleInstance帶有此類啟動模式的activity,它們只能有一個例項存在,且例項只能存在於單獨的任務中。

standard:標準模式

預設啟動模式,啟動activity時直接建立新的例項並壓入啟動它的任務棧頂。

singleTop:棧頂複用模式

該模式唯一與standard不同的就是,如果啟動singleTop模式的activity時發現當前任務的棧頂已經存在著這個activity的例項,那麼就不會建立新的例項,而是呼叫該例項的onNewIntent()方法。其他的跟標準模式一樣。

singleTask:棧內複用模式

這個模式有些特殊一點,我們先按使用情景介紹它,當我們將要啟動該模式的activity時,系統會判斷當前是否有它想要的任務棧:

沒有它要的任務棧系統會新建立一個任務,並將該activity例項化作為該任務的根activity。有它要的任務棧這時候系統會找到該任務棧,如果任務棧裡只有它自己則直接呼叫該activity例項的onNewIntent()方法。如果任務棧中它的上方還存在別的activity,那麼這些activity會被全部彈出棧。

至於什麼是“它想要的任務棧”,我們會在下面單獨分析。

singleInstance:單例模式基本上跟singleTask相同,會為activity單獨建立一個任務並能夠複用。但是該模式的activity不允許其他activity跟自己存在於同一個任務中,由此 activity 啟動的任何 activity 均會被在其他的任務中開啟。

使用Intent標誌宣告啟動模式

此方式可以透過呼叫intent.addFlags(int flags)或者intent.setFlags(int flags)方法為Intent新增一個標誌,用於為將要啟動的Activity宣告啟動模式。

在開始介紹前,先進行幾點掃盲科普:

一個Intent可以設定多個標誌,這就是為啥有addflags()和setFlags()兩個方法的原因了。為Intent設定標誌的引數都是Intent類的靜態常量。設定Intent標誌不光只有設定activity啟動模式這一個功能,設定不同的引數還有其他功能。Intent標誌中可以對activity啟動模式進行操作的標誌可多了,我們只介紹特別典型的三種。

Intent.FLAG_ACTIVITY_SINGLE_TOP同AndroidManifest.xml方式中的singleTop啟動模式。

Intent.FLAG_ACTIVITY_NEW_TASK同AndroidManifest.xml方式中的singleTask啟動模式。

Intent.FLAG_ACTIVITY_CLEAR_TOP如果即將啟動的 activity 已經存在於當前任務棧中,則會彈出銷燬它上方的所有 activity,並呼叫該activity例項的onNewIntent()方法,而不是啟動該 Activity 的新例項。

跟singleTask有點像但不一樣,在AndroidManifest.xml方式中沒有與此對應的值。

singleTask預設就包含了FLAG_ACTIVITY_CLEAR_TOP的功能。

關聯任務

在分析singleTask時有提到過該模式下啟動activity前會去找“它想要的任務棧”,那麼如何去找呢?這就引出了AndroidManifest.xml中<activity>標籤下的taskAffinity屬性。

taskAffinity 屬性

taskAffinity 屬性學名任務相關性,說白了其實就是這個引數可以指定當前activity所屬任務棧的名字,該屬性的值為字串:

例:android:taskAffinity="com.test.demo.task1"

如果你在<activity>標籤沒指定這個屬性,那麼它就用<application>標籤的taskAffinity屬性,如果<application>標籤下也沒指定,它就應用包名當做預設值。

taskAffinity 與 singleTask

瞭解到taskAffinity屬性後我們在重新梳理一下singleTask啟動模式。

如果我們指定了taskAffinity屬性的值,那麼就跟之前分析的一樣,建立新任務等等...如果我們未指定taskAffinity屬性的值,新activity就與當前任務的taskAffinity屬性值一樣,所以新activity的例項會被放置到當前的任務棧中。

除了singleTask呢?現在我們知道了taskAffinity屬性可以強行指定activity所屬的任務棧,那麼這個屬性在其他啟動模式情況下是什麼樣的呢?網上好多人都說沒有效果,我不信就親自測試了一下得出以下結論:

剛介紹 SingleInstance的時候說它跟singleTask一樣都會新建一個任務,既然singleTask是根據taskAffinity屬性來決定是否需要新建任務的,那麼singleInstance是不是也需要指定這個屬性呢?答案是否!只要啟動模式為singleInstance它一定會單獨開一個任務。SingleTop模式下指定了taskAffinity屬性的值後,他就會神奇的切換到指定的那個任務棧中,除此之外跟原來一樣。最神奇的就是Standard,它也同樣受到了taskAffinity屬性的影響,也會切換到指定的那個任務棧中,但當我們多次啟動這個activity時它不會再多次的建立例項,而是拉起了之前啟動過的例項,更特殊的是,其他三種啟動模式在複用之前例項時都會呼叫onNewIntent()方法,他卻不會呼叫該方法。

taskAffinity的其他作用

taskAffinity還有一個功能就是可以重新定向所屬任務,意思就是這個activity原來是屬於任務A的,當有一個跟該activity的taskAffinity屬性值相同的任務B被建立後,這個activity就會從任務A中轉移到任務B中。

想要實現這個功能我們還需要allowTaskReparenting屬性的配合:

我們在清單檔案中給taskAffinity="A"的activity標籤下新增屬性android:allowTaskReparenting=true。在taskAffinity="B"的任務下啟動這個activity,此時這個activity存在於任務B中。當taskAffinity="A"的任務被建立或者被置於前臺,該activity將被轉移到其任務棧中,位於棧頂位置。清理任務

如果使用者長時間離開任務,則系統會清除所有 Activity 的任務,根 Activity 除外。 當用戶再次返回到任務時,僅恢復根 Activity。系統這樣做的原因是,經過很長一段時間後,使用者可能已經放棄之前執行的操作,返回到任務是要開始執行新的操作。

您可以使用下列幾個 Activity 屬性修改此行為:

alwaysRetainTaskState如果在任務的根 Activity 中將此屬性設定為 "true",則不會發生剛才所述的預設行為。即使在很長一段時間後,任務仍將所有 Activity 保留在其堆疊中。

clearTaskOnLaunch如果在任務的根 Activity 中將此屬性設定為 "true",則每當使用者離開任務然後返回時,系統都會將堆疊清除到只剩下根 Activity。 即使只離開任務片刻時間,使用者也始終會返回到任務的初始狀態。

finishOnTaskLaunch類似於clearTaskOnLaunch,但是更狠一些,當用戶離開任務再回來的時候,整個任務的activity都會清除,連根activity也是,相當於第一次啟動這個任務。

啟動模式的實際應用

個人覺得常見的四種啟動模式中要屬singleTop不是很好理解,其他的還好。

singleTop

singleTop模式的activity特點就是除了外部可以啟動它顯示資訊外,它也可以用同樣的方式啟動自己更新顯示資訊,這樣就減少了冗餘程式碼,降低了維護成本。

singleTask

這個啟動模式可以分為兩種情況:

未指定taskAffinity此時該activity可以當應用中的某一模組的入口處

有如下啟動流程,微信主頁 >> 聊天頁 >> 聊天設定頁 >> 使用者資料頁 >> 聊天頁,此時我們按下返回鍵直接回到了微信主頁。

指定了taskAffinity如果利用該啟動模式新開了任務,在使用者的視角里就相當開了兩個應用(在任務管理器中會看到兩個最近應用),所以謹慎使用,我能想到的使用情況就是一個Word應用打開了兩份文件。

singleInstance

這種情況下不管有多少個任務啟動它,它都會作為一個單獨任務存在著,這種模式極其特殊,謹慎使用。

例:撥號介面,鬧鐘介面。

這裡給大家提供一個方向,進行體系化的學習:

1、看影片進行系統學習

前幾年的Crud經歷,讓我明白自己真的算是菜雞中的戰鬥機,也正因為Crud,導致自己技術比較零散,也不夠深入不夠系統,所以重新進行學習是很有必要的。我差的是系統知識,差的結構框架和思路,所以透過影片來學習,效果更好,也更全面。關於影片學習,個人可以推薦去B站進行學習,B站上有很多學習影片,唯一的缺點就是免費的容易過時。

另外,我自己也珍藏了好幾套影片,有需要的我也可以分享給你。

2、進行系統梳理知識,提升儲備

客戶端開發的知識點就那麼多,面試問來問去還是那麼點東西。所以面試沒有其他的訣竅,只看你對這些知識點準備的充分程度。so,出去面試時先看看自己複習到了哪個階段就好。

系統學習方向:

架構師築基必備技能:深入Java泛型+註解深入淺出+併發程式設計+資料傳輸與序列化+Java虛擬機器原理+反射與類載入+動態代理+高效IOAndroid高階UI與FrameWork原始碼:高階UI晉升+Framework核心解析+Android元件核心+資料持久化360°全方面效能調優:設計思想與程式碼質量最佳化+程式效能最佳化+開發效率最佳化解讀開源框架設計思想:熱修復設計+外掛化框架解讀+元件化框架設計+圖片載入框架+網路訪問框架設計+RXJava響應式程式設計框架設計+IOC架構設計+Android架構元件JetpackNDK模組開發:NDK基礎知識體系+底層圖片處理+音影片開發微信小程式:小程式介紹+UI開發+API操作+微信對接Hybrid 開發與Flutter:Html5專案實戰+Flutter進階

知識梳理完之後,就需要進行查漏補缺,所以針對這些知識點,我手頭上也準備了不少的電子書和筆記,這些筆記將各個知識點進行了完美的總結。

3、讀原始碼,看實戰筆記,學習大神思路

“程式語言是程式設計師的表達的方式,而架構是程式設計師對世界的認知”。所以,程式設計師要想快速認知並學習架構,讀原始碼是必不可少的。閱讀原始碼,是解決問題 + 理解事物,更重要的:看到原始碼背後的想法;程式設計師說:讀萬行原始碼,行萬種實踐。

4、面試前夕,刷題衝刺

面試的前一週時間內,就可以開始刷題衝刺了。請記住,刷題的時候,技術的優先,演算法的看些基本的,比如排序等即可,而智力題,除非是校招,否則一般不怎麼會問。

關於面試刷題,我個人也準備了一套系統的面試題,幫助你舉一反三:

7
  • BSA-TRITC(10mg/ml) TRITC-BSA 牛血清白蛋白改性標記羅丹明
  • 理解C# 核心概念 – C# 程式集本地化