深入講解Android中Activity launchMode

2021-02-25 android程式設計師

Android系統中的Activity可以說一件很贊的設計,它在內存管理上良好的設計,使得多任務管理在Android系統中運行遊刃有餘。但是Activity絕非啟動展示在屏幕而已,其啟動方式也大有學問,本文講具體介紹Activity的啟動模式的諸多細節,糾正一些開發中可能錯誤的觀點,幫助大家深入理解Activity。

在正式行文之前,先介紹一些文章提到的概念

本文圖片較多,在看圖時,請注意觀察Activity頂部的title,來區分具體Activity。

為何有啟動模式

應用中的每一個Activity都是進行不同的事物處理。以郵件客戶端為例,InboxActivity目的就是為了展示收件箱,這個Activity不建議創建成多個實例。而ComposeMailActivity則是用來撰寫郵件,可以實例化多個此Activity對象。合理地設計Activity對象是否使用已有的實例還是多次創建,會使得互動設計更加良好,也能避免很多問題。至於想要達到前面的目標,就需要使用今天的Activity啟動模式。

如何使用

使用很簡單,只需要在manifest中對應的Activity元素加入android:launchMode屬性即可。如下述代碼


<activity
android:name=".SingleTaskActivity"
android:label="singleTask launchMode"
android:launchMode="singleTask">
</activity>

接下來就是介紹launchMode的四個值的時刻了。

standard

這是launchMode的默認值,Activity不包含android:launchMode或者顯示設置為standard的Activity就會使用這種模式。

一旦設置成這個值,每當有一次Intent請求,就會創建一個新的Activity實例。舉個例子,如果有10個撰寫郵件的Intent,那麼就會創建10個ComposeMailActivity的實例來處理這些Intent。結果很明顯,這種模式會創建某個Activity的多個實例。

Android 5.0之前的表現

這種Activity新生成的實例會放入發送Intent的Task的棧的頂部。下圖為啟動同一程序內的Activity。


下面的圖片展示跨程序之間調用,新生成的Activity實例會放入發送Intent的Task的棧的頂部,儘管它們屬於不同的程序。


但是當我們打開任務管理器,則會有一點奇怪,應為顯示的任務是Gallery,展示的界面確實另一個程序的Activity(因為其位於Task的棧頂)。


這時候如果我們從Gallery應用切換到撥號應用,再返回到Gallery,看到的還是這個非Gallery的Activity,如果我們想要對Gallery進行操作,必須按Back鍵返回到Gallery界面才可以。確實有點不太合理。

Android 5.0及之後表現

對於同一應用內部Activity啟動和5.0之前表現一樣,變化的就是不同應用之間Activity啟動變得合理了。

跨應用之間啟動Activity,會創建一個新的Task,新生成的Activity就會放入剛創建的Task中。如下圖


同時任務管理器查看任務也顯得更加合理了。


假設之前存在我們的測試程序,然後從Gallery又分享文件到我們的測試程序,則對應的任務管理器展示效果如下。


使用場景:standard這種啟動模式適合於撰寫郵件Activity或者社交網絡消息發布Activity。如果你想為每一個intent創建一個Activity處理,那麼就是用standard這種模式。


singleTop

singleTop其實和standard幾乎一樣,使用singleTop的Activity也可以創建很多個實例。唯一不同的就是,如果調用的目標Activity已經位於調用者的Task的棧頂,則不創建新實例,而是使用當前的這個Activity實例,並調用這個實例的onNewIntent方法


在singleTop這種模式下,我們需要處理應用這個模式的Activity的onCreate和onNewIntent兩個方法,確保邏輯正常。

使用場景

關於singleTop一個典型的使用場景就是搜索功能。假設有一個搜索框,每次搜索查詢都會將我們引導至SearchActivity查看結果,為了更好的交互體驗,我們在結果頁頂部也放置這樣的搜索框。

假設一下,SearchActivity啟動模式為standard,那麼每一個搜索都會創建一個新的SearchActivity實例,10次查詢就是10個Activity。當我們想要退回到非SearchActivity,我們需要按返回鍵10次,這顯然太不合理了。

但是如果我們使用singleTop的話,如果SearchActivity在棧頂,當有了新的查詢時,不再重新創建SearchActivity實例,而是使用當前的SearchActivity來更新結果。當我們需要返回到非SearchActivity只需要按一次返回鍵即可。使用了singleTop顯然比之前要合理。

總結
singleTask

singleTask這個模式和前面提到的standard和singleTop截然不同。使用singleTask啟動模式的Activity在系統中只會存在一個實例。如果這個實例已經存在,intent就會通過onNewIntent傳遞到這個Activity。否則新的Activity實例被創建。


同一程序內

如果系統中不存在singleTask Activity的實例,那麼就需要創建這個Activity的實例,並且將這個實例放入和調用者相同的Task中並位於棧頂。



如果singleTask Activity實例已然存在,那麼在Activity回退棧中,所有位於該Activity上面的Activity實例都將被銷毀掉(銷毀過程會調用Activity生命周期回調),這樣使得singleTask Activity實例位於棧頂。與此同時,Intent會通過onNewIntent傳遞到這個SingleTask Activity實例。


然而在Google關於singleTask的文檔有這樣一段描述

The system creates a new task and instantiates the activity at the root of the new task.

意思為 系統會創建一個新的Task,並創建Activity實例放入這個新的Task的底部。

然而實際並非如此,在我的例子中,singleTask Activity並創建並放入了調用者所在的Task,而不是放入新的Task,使用adb shell dumpsys activity便可以進行驗證。

跨應用之間

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


系統的任務管理器也會如下變化



使用場景

該模式的使用場景多類似於郵件客戶端的收件箱或者社交應用的時間線Activity。上述兩種場景需要對應的Activity只保持一個實例即可,但是也要謹慎使用這種模式,因為它可以在用戶未感知的情況下銷毀掉其他Activity。


singleInstance

這個模式和singleTask差不多,因為他們在系統中都只有一份實例。唯一不同的就是存放singleInstance Activity實例的Task只能存放一個該模式的Activity實例。如果從singleInstance Activity實例啟動另一個Activity,那麼這個Activity實例會放入其他的Task中。同理,如果singleInstance Activity被別的Activity啟動,它也會放入不同於調用者的Task中。



雖然是兩個task,但是在系統的任務管理器中,卻始終顯示一個,即位於頂部的Task中。


另外當我們從任務管理器進入這個應用,是無法通過返回鍵會退到Task1的。

好在有辦法解決這個問題,就是之前提到的taskAffinity="",為launchMode為singleInstance的Activity加入這個屬性即可。


<activity
android:name=".SingleInstanceActivity"
android:label="singleInstance launchMode"
android:launchMode="singleInstance"
android:taskAffinity="">
</activity>

再次運行修改的代碼,查看任務管理器,這樣的結果就合理了。


使用情況

這種模式的使用情況比較罕見,在Launcher中可能使用。或者你確定你需要使Activity只有一個實例。建議謹慎使用。


Intent Flags

除了在manifest文件中設置launchMode之外,還可以在Intnet中設置flag達到同樣的效果。如下述代碼就可以讓StandardActivity已singleTop模式啟動。


Intent intent = new Intent(StandardActivity.this, StandardActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);startActivity(intent);

關於Intent Flags這裡暫不做重點介紹,具體可以參考官方文檔



相關焦點

  • android中的啟動模式 - CSDN
    Android總Activity的啟動模式分為四種: Activity啟動模式設置: <activity android:name=".MainActivity" android:launchMode="standard" />Activity的四種啟動模式: 1. standard 模式啟動模式,
  • Android中的窗口——Activity
    現在來準備3個Activity(Activity1、Activity2和Activity3),這3個Activity使用的布局文件分別為activity1.xml、activity2.xml和activity3.xml,代碼如下:activity1.xml &
  • Android深入四大組件(六)Android8.0 根Activity啟動過程(前篇)
    前言在幾個月前我寫了Android深入四大組件(一)應用程式啟動過程這篇文章,它是基於Android 7.0的,當我開始閱讀Android 8.0源碼時發現應用程式(根Activity)啟動過程照Android 7.0有了一些變化,因此又寫下了本篇文章,本篇文章照此前的文章不僅流程發生變化,而且增加了一些分析,算是升級版本。
  • 如何使用am命令啟動Android應用
    命令格式為:adb shell am start -n package/launch_activity示例:打開設置App命令:adb shell am start -n com.android.settings/com.android.settings.Settings命令的關鍵在於獲取package和launch_activity。
  • 我以為我理解Android的四大啟動模式,直到被打臉
    解答部分2.4正文一、理解概念在學習Ativity的過程中,Task的概念多少是我們無可迴避的概念。從官方文檔中我們基本能夠理解明白Task和Stack的關係…一張很經典的圖:Activity以Task的概念聚合在一起,而且無論是單一Task中的多個Activity還是多個Task混在一起,它們都是以Stack形式所包含在一起。
  • 月薪20+的Android面試都問些什麼?(含答案)
    為了不讓Activity實例化多次,我們需要通過在AndroidManifest.xml配置activity的加載方式(launchMode)以實現單任務模式,如下所示:<activity android:label="@string/app_name"android:launchmode="singleTask"android:name="Activity1
  • 使用Junit對Android應用進行單元測試
    本文的示例代碼可以在http://code.google.com/p/simple-calc-unit-testing/中下載  步驟1 被測試的應用SimpleCalc概況  在本文中,將以一個寫好了的應用SimpleCalc簡單計算器為例子進行講解。
  • Android-SetContentView內部原理
    現在我們具體看一下setContentView中到底寫了什麼?通過尋找,發現AppCompatDelegateImplV9中進行了重寫:    android:fitsSystemWindows="true">    <android.support.v7.widget.ViewStubCompat        android:id="@+id/action_mode_bar_stub
  • 深入理解Android Crash 流程
    /internal/os/RuntimeInit.java/frameworks/base/core/java/android/app/ActivityManagerNative.java (含內部類AMP)/frameworks/base/core/java/android/app/ApplicationErrorReport.java/frameworks/base
  • 【Hook】實現無清單啟動Activity的應用
    個問題就是,它是什麼,有什麼用,怎麼用本系列將由淺入深 手把手講解這三大問題本文是第四篇, 技術應用篇前面3篇文章,由易到難(入門,深入,高級),用通俗易懂的語言講述了Hook的用法,實現了 啟動沒有在 menifest中註冊的
  • Activity 使用詳解
    //需要在AndroidManifest 中聲明 have you declared this activity in your AndroidManifest.xml?
  • 學習安卓開發[2]-在Activity中託管Fragment
    要讓Activity能夠託管Fragment,則需要activity視圖預留fragment插入其中的位置。一個activity視圖中可以插入過個fragment視圖。Fragment本身沒有在屏幕上顯示視圖的能力,所以它必須放置在Activity的視圖層級中。
  • Activity 使用方法詳解
    例如以下ActivityNotFoundException報錯信息://提示未在 AndroidMainfest.xml 中找到Activity類的聲明android.content.ActivityNotFoundException: Unable to find explicit activity
  • Android 中的 MVP 模式(帶實例)
    在 Android中很重要的一點就是對UI的操作基本上需要異步進行也就是在MainThread中才能操作UI,所以對View與Model的切斷分離是 合理的。此外Presenter與View、Model的交互使用接口定義交互操作可以進一步達到鬆耦合也可以通過接口更加方便地進行單元測試。
  • Android 通過adb shell am broadcast發送廣播
    Android中提供了多種鍵值對,具體參見下表。舉例:-e class com.android.phone.FIncomingCallTests#testRejectCall-r作用:以原始形式輸出測試結果。該選項通常是在性能測試時與-e perf true一起使用。
  • Android爬坑之路(十三)Activity啟動模式
    以singleInstance模式啟動的Activity在整個系統中是單例的,如果在啟動這樣的Activiyt時,已經存在了一個實例,那麼會把它所在的任務調度到前臺,重用這個實例。singleInstance特點:直接創建一個新的任務棧,並創建該Activity實例放於新的任務棧中
  • 帶你了解什麼是Activity四種啟動模式
    設置Activity的啟動模式,只需要在AndroidManifest.xml裡對應的標籤設置Android:launchMode屬性 android:name=".A1" android
  • Android插件化系列三:技術流派和四大組件支持
    獨立插件是單獨運行在一個進程中的,與宿主完全隔離,崩潰不會影響到宿主。但是耦合插件卻是和宿主運行在一個進程中,所以插件崩潰,宿主也崩潰了。所以一般業務要根據資源和代碼的耦合程度,插件的可靠性等綜合考慮插件類型。我們接下來慢慢講解。
  • 最全Android6.0及以上系統APP保活總結和實現,附進程常駐開源Demo
    如果希望指定的組件和應用運行在指定的進程中,就需要通過android:process屬性來為其創建一個進程,因此android:process=":daemon_service"就是讓DaemonService運行在名為「com.jiangdg.keepappalive:daemon_service」進程中;android:enabled屬性的作用是Android