通過使用場景徹底明白Activity啓動模式

先給出結論:Activity的啓動模式的設計的主要目的是爲了解決應用頁面交互需求的不同場景。

具體不同的啓動模式,適合解決什麼樣的場景問題,我們先來看看Activity的啓動模式都有哪些。

1、standard標準模式

在介紹標準模式之前,先介紹一下多個Activity實例在內存當中的維護數據結構是什麼,所有的Activity實例被創建後,都會加入到棧數據結構中,在Android知識體系的術語中,被稱爲任務棧。任務棧是一種後進先出的數據結構。在後續的其它三種模式的介紹中,雖然其和標準模式存在差異,但無論如何是離不開棧這種數據結構的特性,亦即”後進先出“。這一句話,讀起來不是很好理解,那麼需要在後文中需要細緻品味至理解。

那麼標準模式正是每次啓動一個Activity都會重新創建一個新的實例,不管這個實例是否已經存在。在這種模式下,啓動了這個Activity,那麼這個Activity就運行在啓動它的那個Activity的任務棧中。比如Activity A啓動了Activity B(B是標準模式),那麼B就會進入到A所在任務棧中。

這裏面有一個問題,android應用中的第一個Activity是如何啓動的,第一個任務棧又是如何創建的?

在Android的應用工程中,我們應用程序能夠啓動,我們必須在Manifest.xml清單文件中,配置啓動Activity,代碼如下:

       <activity
            android:name=".business.user.activity.SplashActivity"
            android:theme="@style/SplashTheme"
            android:windowSoftInputMode="stateHidden|adjustResize">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity> 

本篇文章,主要是闡述Activity的啓動模式,就不對Launcher Activity的過程做詳細的分析,直接給出結論:應用的第一個Activity的啓動,是通過系統交互行爲完成的,並且在創建和啓動這個Activity的時候,也會創建管理這個Activity的任務棧(點擊桌面應用icon->創建用來管理Activity的任務棧->系統尋找對應的啓動Activity)。

另外,標準模式的Activity是不能使用ApplicationContext去啓動的,大多初學者,應該遇到過這樣的錯誤,如下:AndroidRuntimeException: Calling startActivity from outside of an Activity context requires the FLAG_ACTIVITY_NEW_TASK flag.Is this really you want?

這裏面的原因就是非Activity類型的context,並沒有所謂的任務棧,解決這個問題的方法根據建議是設置標誌位。但是通過設置標誌位的方式,其實改變了當前Activity的啓動模式,所以在實際應用開發的過程中,我們需要根據實際的場景去決定,是通過設置標誌位的方式換掉當前要啓動的Activity的啓動模式,還是使用一個Activity的Context來滿足當前Activity的啓動。

使用場景結論:標準啓動模式,適用於app裏面的大部分普通頁面,打開一個新的頁面,就創建一個Activity,由於標準啓動模式的是最普通的模式,所以在使用場景這塊,主要是看頁面交互需求裏,哪些不適合標準啓動模式,我們要用新的啓動模式替換標準啓動模式。

2、singleTop棧頂複用模式

這種模式,如果新Activity已經存在實例位於任務棧的棧頂,那麼此Activity不會被重新創建,同時它的onNewIntent的方法會被回調,通過此方法,我們可以取出當前請求的信息。簡單舉個例子:如果當前的Activity的任務棧是ABCD,D是singleTop模式,那麼再啓動一次D,任務棧中仍然是ABCD;D若是標準模式,那麼再啓動一個D,任務棧中就是ABCDD。切記此種模式的是棧頂複用就好了。

那麼singleTop啓動模式,在什麼樣的場景下會使用到呢?

這裏舉一個搜狐新聞App的例子,假設主界面爲 MainActivity,顯示新聞的界面是 DetailActivity,顯然顯示任何一條新聞都會使用 DetailActivity,即把新聞內容id通過 Intent 傳給 DetailActivity 就可以了。通過回調onNewIntent方法,可以獲取請求當前頁面的數據。爲什麼這樣的頁面適合使用singleTop這樣的啓動模式去啓動呢,還有一個原因,當我們收到文章推送的系統通知欄通知的時候,如果是多條推送,給用戶的體驗,自然不希望用戶點開一條推送,就重新創建一個頁面吧。比如極端情況下,這樣如果用戶點擊了10條通知欄推送,並進入到App,每次都重新創建了一個新的Activity,當用戶要回到主頁面的時候,用戶可是要點擊10次返回鍵。

使用場景結論:singleTop啓動模式,適用於一些文章類的內容呈現頁面,爲了解決用戶頁面交互可能需要多次按返回鍵回到主頁的場景(這種場景的發生,往往和系統通知欄打開頁面有關,當然還有可能是特定的交互設計,希望快速回到任務棧中的特定頁面),我們使用singleTop模式。

另外,啓動模式的應用是靈活的,和我們文章開頭的結論一致,大多數情況下,是爲了解決用戶頁面交互更加友好而設計。

再舉一個例子,假如是一個社交類的app,此app也會收到通知欄通知,點擊通知欄通知會進入到好友的對方空間,當然也會出現上述網易新聞的交互問題,此時我們的所設計的對方空間頁,也就可以設計成singleTop啓動模式了。還有一些操作結果回調頁面,比如微信支付回調頁面,也是singleTop啓動模式,微信支付的回調頁面,是微信支付結果的數據呈現頁面,爲了規避多次回調(有可能是異常),出現了多個支付回調頁面,交互體驗也並不友好,那麼設計singleTop啓動模式,即使出現上述異常,用戶感知的也只是頁面上數據狀態的變化。

使用singleTop模式,在我們實際的應用還有一個場景,比如解決重複點擊,打開了相同的Activity頁面,當然爲了保證業務正常,我們也同時同時處理onNewIntent的回調方法,保證頁面的業務數據正常。

3、singleTask棧內複用模式

棧內複用模式是一種單實例模式,在這種模式下,只要Activity在一個棧中存在,多次啓動Activity都不會去重新創建Activity的實例。和SingleTop一樣,系統也會回調onNewIntent的方法。下面通過表格的方式,來看一下以下幾種情況,其中D爲需要啓動的singleTask模式的Activity:

序號 所需任務棧 啓動前任務棧 啓動D 啓動後任務棧
1 創建任務棧S1,棧內無其它Activity實例 創建D並壓入棧S1中 D
2 S1任務棧中,ABC,無D 創建D並壓入棧S1中 ABCD
3 S1任務棧中,ABCD 不創建 ABCD
4 S1任務棧中,ADBC 不創建 AD

通過上述表格,基本能夠概括singleTask模式在不同的情形下被啓動後,是否要創建任務棧,是否要創建實例,以及singleTask模式的啓動Activity後,對原有任務棧中Activity數據影響。

這裏我們重點看一下序號4情形,爲什麼啓動D後,任務棧中的BC消失了。很多同學,應該看過一些文章說,因爲singleTask模式默認具有clearTop的效果,會導致棧內所有在D上面的Activity全部消失。首先這個觀點的確是對的,我在這裏只是想強調一下因果關係,在本篇文章standard標準模式介紹部分的第一段最後一句話,我們要深刻理解棧的數據結構的”後進先出“的特性。序號4情形出現最終AD的結果,是由於如果需要把ADBC棧中的D移到棧頂,則必須讓BC先出棧,才能使得D停留在棧頂上。

所以是由於singleTask模式本身的實現+棧數據結構的特性導致了singleTask模式具有了clearTop的效果。

使用場景結論:如果在應用的整個存活生命週期中,希望當前頁面交互的Activity只有一個,我們可以選擇使用singleTask啓動模式,一句話,如果希望當前Activity在當前應用內唯一,使用singleTask模式。

具體舉幾個場景的例子:

場景案例1:應用的主界面一般可以設計成singleTask模式,一般主頁面的設計都是一個Activity加三四個tab頁,tab頁控制這三個Fragment。手機淘寶的主界面就設計成了singleTask模式,在文章的後半部分,還有對手機淘寶啓動模式的代碼層面的印證。

場景案例2:有些app有這樣的一種設計,首先有一個登錄頁面LoginActivity-A包含賬號、密碼輸入框,登錄按鈕,還有註冊按鈕,用戶點註冊按鈕會進入到註冊流程中,整個註冊流程是有2個頁面完成,比如選擇性別暱稱頁面B(B頁面支持返回到上一步,進行登錄操作)->二次密碼確認頁面C(C頁面支持返回到上一步,修改性別和暱稱)>C頁面點擊註冊,註冊成功後,回到登錄LoginActivity頁面A,要求用戶進行主動登錄。那麼這整個流程的任務棧情況是ABC,然後C頁面點擊註冊成功後,進入登錄A頁面,那麼如果A標準模式,當前任務棧應該是ABCA,這樣應用程序的返回棧就奇怪了,此時頁面交互層面是希望只有登錄A頁面,所以此時的登錄A頁面就可以設計singleTask模式,來解決上述場景的頁面交互問題。

案例2在我們的實際開發中,並不算是常見的交互場景,很多app註冊成功就直接進入到app的主頁面了,那麼通過案例2的場景呢,再一次強調了,如果希望一個頁面Activity在應用內唯一,可以選擇設計成singleTask設計模式。

4、singleInstance單實例模式

singleInstance單實例模式,是一種加強的singleTask模式,那麼它除了具備singleTask模式的所有特性以外,加強的部分是,具有此種模式的Activity只能單獨位於一個任務棧中。比如A是singleInstance模式Activity,當A啓動後,系統會創建一個新的任務棧,然後A獨自在這個新的任務棧中,由於仍然具有棧內複用的特性,後續都不會重新創建新的Activity,除非這個獨特的任務棧被系統銷燬了。

使用場景結論:

希望整個android手機系統全局唯一頁面,我們使用此種模式。在我的實際工作中,singleInstance的場景基本沒有用到。這裏面截取網上大家可能都能搜到的一個場景案例:

你以前設置了一個鬧鈴:上午6點。在上午5點58分,你啓動了鬧鈴設置界面,並按 Home 鍵回桌面;在上午5點59分時,你在微信和朋友聊天;在6點時,鬧鈴響了,並且彈出了一個對話框形式的 Activity(名爲 AlarmAlertActivity) 提示你到6點了(這個 Activity 就是以 SingleInstance 加載模式打開的),你按返回鍵,回到的是微信的聊天界面,這是因爲 AlarmAlertActivity 所在的 Task 的棧只有他一個元素,因此退出之後這個 Task 的棧空了。如果是以 SingleTask 打開 AlarmAlertActivity,那麼當鬧鈴響了的時候,按返回鍵應該進入鬧鈴設置界面。

我們在研究一個技術的知識點的時候,一定不能離開場景,所以在前面的四個啓動模式的分析結尾,都給出了使用場景結論,那麼最後我們還是來看一下,一些我們常用的應用,在啓動模式上的應用。

首先我們看一下launchMode在android api 28中的定義:

/**
     * Constant corresponding to <code>standard</code> in
     * the {@link android.R.attr#launchMode} attribute.
     */
    public static final int LAUNCH_MULTIPLE = 0;
    /**
     * Constant corresponding to <code>singleTop</code> in
     * the {@link android.R.attr#launchMode} attribute.
     */
    public static final int LAUNCH_SINGLE_TOP = 1;
    /**
     * Constant corresponding to <code>singleTask</code> in
     * the {@link android.R.attr#launchMode} attribute.
     */
    public static final int LAUNCH_SINGLE_TASK = 2;
    /**
     * Constant corresponding to <code>singleInstance</code> in
     * the {@link android.R.attr#launchMode} attribute.
     */
    public static final int LAUNCH_SINGLE_INSTANCE = 3;

再看一下微信最新版本2019月11月19日在應用包下載的apk的清單文件中部分代碼:

<activity
    android:theme="@ref/0x7f0d009d"
    android:label="@ref/0x7f0a1380"
    android:name="com.tencent.mm.ui.LauncherUI"
    android:launchMode="1"
    android:configChanges="0xda0"
    android:windowSoftInputMode="0x32">
  ...
</activity>

LanucherUI 是微信主界面的Activity,其啓動模式的是1,1代表的singleTop啓動模式。

下面的代碼是截取自淘寶的apk清單文件中的部分代碼:

<activity
    android:theme="@ref/0x7f11039c"
    android:name="com.taobao.tao.TBMainActivity"
    android:launchMode="2"
    android:screenOrientation="1">

    <intent-filter
        android:label="@ref/0x7f101218">
        <action
            android:name="android.intent.action.VIEW" />
        <category
            android:name="android.intent.category.DEFAULT" />
        <category
            android:name="android.intent.category.BROWSABLE" />
      ....  
    </intent-filter>

TBMainActivity 是手機淘寶的主界面Activity,其啓動模式是2,2代表singleTask啓動模式。

淘寶的主界面的確設置成了singleTask模式,但是微信的主界面並沒有像我們前面分析的結論一樣,而是使用的singleTop模式,可能有什麼其他特定的原因,我們在設置主界面的啓動模式時,仍然可以遵循我們前面分析的結論,應用的主界面,在應用程序的生命週期中,只希望有一個,就可以選擇使用singleTask啓動模式。

最終,我們呼應文章開始給出的結論:Activity的啓動模式的設計的主要目的是爲了解決應用頁面交互需求的不同場景。在我們實際的場景應用中主要解決

1、App需要生成給其它app調用Activity,例如瀏覽器應用,照相機應用

2、解決快速點擊造成的多個重複頁面的交互問題

3、任務棧過深,解決按返回鍵回到指定頁面的問題

總之,巧妙的使用啓動模式,能夠幫我們解決頁面交互問題提供便捷的解決方案。

關注公衆號HymKing

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