Android面試官:這幾個問題都回答不出來你真的懂Activity的啓動模式嗎?

引言

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

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

假設有如下四個activity:

  1. A(standard)
  2. B(singleTop)
  3. C(singleTask)
  4. 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就是這個任務的根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如果已經實例化過了就不會重新創建。

例2:小明在微信中向你分享了一條微博內容,你打開後跳轉到了微博APP中的該條微博詳情頁,當你看完內容後按返回鍵退出該界面發現並不是回到了微信聊天界面,而是來到了微博主頁(或上一次在微博中停留的界面)。

本例中微博詳情頁的Activity雖然是由微信應用所在的任務啓動,但是沒有加入到微信應用的任務中,而是加入到了微博的任務棧中。

管理任務

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

定義啓動模式

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

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

  • 使用AndroidManifest.xml中定義
    在AndroidManifest.xml中<activity>標籤下使用lauchMode屬性來指定當前這個activity的啓動模式。

  • 使用Intent標誌定義
    在調用startActivity(Intent intent)前,通過調用intent.addFlags()或者intent.setFlags()方法爲Intent添加一個標誌,用於爲將要啓動的Activity聲明啓動模式。

那兩者有什麼區別呢?

上述兩種方法均可以爲activity聲明啓動模式,只是使用情景不同。

  1. 如果我們希望某個activity在任何情況下都會執行一種特殊的啓動模式,我們就可以採用AndroidManifest.xml的方法聲明。

  2. 如果我們希望某個activity大多數情況下正常啓動,而少數情況下執行特殊的啓動模式,我們就可以在需要執行特殊啓動模式時在Intent中添加標誌聲明。

  3. 如果一個activity兩種方式都聲明瞭的話,使用Intent標誌的方式要比AndroidManifest.xml的優先級高。

  4. 兩種方式中定義的啓動模式有些是不一樣的,Intent標誌中定義的某些啓動模式AndroidManifest.xml中沒有,反之一樣。

  5. 我們常說的四種啓動模式其實說的是AndroidManifest.xml中定義的。

使用AndroidManifest.xml聲明啓動模式

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

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

  • standard
  • singleTop
  • singleTask
  • singleInstance

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

  • standardsingleTop
    該類啓動模式的activity可以被多次的實例化,它們的實例可以放到任何任務中,並且可以位於返回棧的任何位置。
  • singleTasksingleInstance
    帶有此類啓動模式的activity,它們只能有一個實例存在,且實例只能存在於單獨的任務中。

standard:標準模式

默認啓動模式,啓動activity時直接創建新的實例並壓入啓動它的任務棧頂。

singleTop:棧頂複用模式

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

singleTask:棧內複用模式

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

  1. 沒有它要的任務棧
    系統會新創建一個任務,並將該activity實例化作爲該任務的根activity。

  2. 有它要的任務棧
    這時候系統會找到該任務棧,如果任務棧裏只有它自己則直接調用該activity實例的onNewIntent()方法。如果任務棧中它的上方還存在別的activity,那麼這些activity會被全部彈出棧。

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

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

使用Intent標誌聲明啓動模式

此方式可以通過調用intent.addFlags(int flags)或者intent.setFlags(int flags)方法爲Intent添加一個標誌,用於爲將要啓動的Activity聲明啓動模式。

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

  1. 一個Intent可以設置多個標誌,這就是爲啥有addflags()setFlags()兩個方法的原因了。
  2. 爲Intent設置標誌的參數都是Intent類的靜態常量。
  3. 設置Intent標誌不光只有設置activity啓動模式這一個功能,設置不同的參數還有其他功能。
  4. 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所屬的任務棧,那麼這個屬性在其他啓動模式情況下是什麼樣的呢?網上好多人都說沒有效果,我不信就親自測試了一下得出以下結論:

  1. 剛介紹 SingleInstance的時候說它跟singleTask一樣都會新建一個任務,既然singleTask是根據taskAffinity屬性來決定是否需要新建任務的,那麼singleInstance是不是也需要指定這個屬性呢?
    答案是否!只要啓動模式爲singleInstance它一定會單獨開一個任務。

  2. SingleTop模式下指定了taskAffinity屬性的值後,他就會神奇的切換到指定的那個任務棧中,除此之外跟原來一樣。

  3. 最神奇的就是Standard,它也同樣受到了taskAffinity屬性的影響,也會切換到指定的那個任務棧中,但當我們多次啓動這個activity時它不會再多次的創建實例,而是拉起了之前啓動過的實例,更特殊的是,其他三種啓動模式在複用之前實例時都會調用onNewIntent()方法,他卻不會調用該方法。

taskAffinity的其他作用

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

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

  1. 我們在清單文件中給taskAffinity="A"的activity標籤下添加屬性android:allowTaskReparenting=true
  2. taskAffinity="B"的任務下啓動這個activity,此時這個activity存在於任務B中。
  3. taskAffinity="A"的任務被創建或者被置於前臺,該activity將被轉移到其任務棧中,位於棧頂位置。

清理任務

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

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

alwaysRetainTaskState
如果在任務的根 Activity 中將此屬性設置爲 "true",則不會發生剛纔所述的默認行爲。即使在很長一段時間後,任務仍將所有 Activity 保留在其堆棧中。

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

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

啓動模式的實際應用

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

singleTop

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

例:如果讓你設計一個帶有搜索應用的APP,主頁有一個搜索框,輸入信息點擊搜索按鈕進入結果頁顯示結果,爲方便用戶使用,結果頁也有一個搜索框,跟主頁的搜索框功能一樣,你會怎麼設計?

singleTask

這個啓動模式可以分爲兩種情況:

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

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

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

singleInstance

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

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

面試複習路線

多餘的話就不講了,接下來將分享面試的一個複習路線,如果你也在準備面試但是不知道怎麼高效複習,可以參考一下我的複習路線,有任何問題也歡迎一起互相交流,加油吧!

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

1、看視頻進行系統學習

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

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

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

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

系統學習方向:

  • 架構師築基必備技能:深入Java泛型+註解深入淺出+併發編程+數據傳輸與序列化+Java虛擬機原理+反射與類加載+動態代理+高效IO

  • Android高級UI與FrameWork源碼:高級UI晉升+Framework內核解析+Android組件內核+數據持久化

  • 360°全方面性能調優:設計思想與代碼質量優化+程序性能優化+開發效率優化

  • 解讀開源框架設計思想:熱修復設計+插件化框架解讀+組件化框架設計+圖片加載框架+網絡訪問框架設計+RXJava響應式編程框架設計+IOC架構設計+Android架構組件Jetpack

  • NDK模塊開發:NDK基礎知識體系+底層圖片處理+音視頻開發

  • 微信小程序:小程序介紹+UI開發+API操作+微信對接

  • Hybrid 開發與Flutter:Html5項目實戰+Flutter進階

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

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

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

主要內含微信 MMKV 源碼、AsyncTask 源碼、Volley 源碼、Retrofit源碼、OkHttp 源碼等等。

4、面試前夕,刷題衝刺

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

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

以上內容均免費分享給大家,需要完整版的朋友,點這裏可以看到全部內容。或者關注主頁掃描加 微信 獲取。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章