深入理解Activity的啟動模式

面試的時候,面試官經常會和你聊聊Activity的啟動模式,但Activity啟動牽扯的知識點其實很多,并非能單單用四個啟動模式就能概括的,默認的啟動模式的表現會隨著Intent Flag的設置而改變。今天就來深入聊聊Activity的啟動模式。

基本的啟動模式

學Android的都知道,Activity有4種啟動模式:

  1. 標準模式 standard : 每次都會啟動一個新的Activity實例,這是默認的啟動模式。此模式下啟動的Activity會進入啟動它的Activity所在的任務棧中。
  2. 棧頂復用模式 singleTop :如果Activity實例位于當前任務棧頂,就重用棧頂實例,而不新建,并回調該實例onNewIntent()方法,否則走新建流程。
  3. 棧內復用模式 singleTask : 只要該Activity在一個任務棧中存在,就不會重新創建,而是將目標Activity上面的Activity全部出棧,然后回調 onNewIntent(intent)。 方法。如果不存在,系統會先尋找是否存在需要的棧,如果不存在該棧,就創建一個任務棧,并把該Activity放進去;如果存在,就會創建到已經存在的棧中。
  4. 單例模式 singleInstance : 具有此模式的Activity只能單獨位于一個任務棧中,且此任務棧中只有唯一一個實例。singleInstance算是加強型的singleTask模式,也就是說它也有棧內復用的特性,多次請求不會重復創建,也會調用onNewIntent(intent)方法。

相信上面的內容大家都知道,不過上面的場景僅僅適用于Activity啟動Activity,并且采用的都是默認Intent,沒有額外添加任何Flag,否則表現就可能跟上面的完全不一致。

其他啟動模式

大家都知道,啟動Activity是通過Intent的,而Intent是有一個Flags屬性的,可以用intent.setFlags()方法來設置。當我們設置了一個或多個Flag后,Activity的啟動方式有可能就會和之前的描述不同了。

taskAffinity

每個Activity都有taskAffinity屬性,這個屬性指出了它希望進入的Task。

如果一個Activity沒有顯式的指明該Activity的taskAffinity,那么它的這個屬性就等于Application指明的taskAffinity,如果Application也沒有指明,那么該taskAffinity的值就等于包名。而Task也有自己的affinity屬性,它的值等于它的根Activity的taskAffinity的值。

Flags

Flag 有很多,我就重點說幾個重要的。

FLAG_ACTIVITY_NEW_TASK

Intent.FLAG_ACTIVITY_NEW_TASK是啟動模式中最關鍵的一個Flag。是棧內復用模式的關鍵屬性。
我們知道,非Activity的Context(如Service)是沒有任務棧的,那么用standard模式啟動Activity時就會出現問題,需要給Intent設置flag,Intent.FLAG_ACTIVITY_NEW_TASK,強制Activity選擇新的任務棧(這就和singleTask一樣了)。

不設置會直接報異常,在startActivity()方法的底層ContextImpl.startActivity()中是有檢驗的。不過,在Android7.0、8.0系統源碼有Bug,不會報異常,9.0恢復正常。

//ContextImpl Android6.0
    @Override
    public void startActivity(Intent intent) {
        warnIfCallingFromSystemProcess();
        startActivity(intent, null);
    }

    @Override
    public void startActivity(Intent intent, Bundle options) {
        warnIfCallingFromSystemProcess();
        //檢驗是否添加了 FLAG_ACTIVITY_NEW_TASK 
        if ((intent.getFlags()&Intent.FLAG_ACTIVITY_NEW_TASK) == 0) {
            throw new AndroidRuntimeException(
                    "Calling startActivity() from outside of an Activity "
                    + " context requires the FLAG_ACTIVITY_NEW_TASK flag."
                    + " Is this really what you want?");
        }
        mMainThread.getInstrumentation().execStartActivity(
            getOuterContext(), mMainThread.getApplicationThread(), null,
            (Activity)null, intent, -1, options);
    }

依據該Flag啟動模式可以分成兩類,設置了該屬性的與未設置該屬性的。singleTask及singleInstance在AMS中被預處理后,會自動設置為Intent.FLAG_ACTIVITY_NEW_TASK。standard及singletTop的Activity不會被設置Intent.FLAG_ACTIVITY_NEW_TASK,可以通過顯示的Intent.setFlags進行設置。

        Intent intent = new Intent(TestService.this, SecondActivity.class);
        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);

FLAG_ACTIVITY_CLEAR_TASK

個屬性必須同FLAG_ACTIVITY_NEW_TASK配合使用。
設置了FLAG_ACTIVITY_NEW_TASK|Intent.FLAG_ACTIVITY_CLEAR_TASK后,如果目標task已經存在,將清空已存在的目標Task,否則,新建一個Task棧,之后,新建一個Activity作為根Activity。
Intent.FLAG_ACTIVITY_CLEAR_TASK的優先級非常高,基本可以無視所有的配置,包括啟動模式及其他的Intent Flag,哪怕是singleInstance也會被finish,并重建。

FLAG_ACTIVITY_CLEAR_TOP

這個Flag有點復雜,它涉及到的情況較多。

  • 如果單獨使用Intent.FLAG_ACTIVITY_CLEAR_TOP,并且沒有設置特殊的launchmode,那么,Google官方的示例是:如果ABCD Task中的D采用FLAG_ACTIVITY_CLEAR_TOP喚起B,這個時候首先會將CD出棧,但是至于B是否會重建,要視情況而定。

  • 如果沒有設置FLAG_ACTIVITY_SINGLE_TOP,則會將B finish掉,之后創建新的入棧。如果當前棧中沒有目標Activity則直接新建,而不會去其他棧中尋找。

  • 如果同時設置了FLAG_ACTIVITY_SINGLE_TOP,則在當前棧已有的情況下就不會重建,而是直接回調B的onNewIntent()。當前棧沒有依舊是直接新建。

  • 如果同時使用了FLAG_ACTIVITY_NEW_TASK,這個時候,目標是Activity自己所屬的Task棧,如果在自己的Task中能找到一個Activity實例,則將其上面的及自身清理掉,之后重建。

  • 如果同時在加上FLAG_ACTIVITY_SINGLE_TOP,則會更特殊一些,如果topActivity不是目標Activity,就會去目標Task中去找,并喚起。如果topActivity是目標Activity,就直接回調topActivity的onNewIntent,無論topActivity是不是在目標Task中。

FLAG_ACTIVITY_SINGLE_TOP

這個Flag和singleTop作用是一樣的,在Task棧頂有的話,就不新建,棧頂沒有的話,就新建,這里的Task可能是目標棧,也可能是當前Task棧。

IntentFilter相關

大家都知道,啟動Activity分為兩種方式,顯式和隱式調用。隱式調用需要Intent能夠匹配目標組件的IntentFilter中所設置的過濾信息,如果不匹配是無法啟動目標組件的。

IntentFilter中過濾的信息有action、category、data,保存在Activity配置文件的intent-filter標簽下。只有同時匹配有action、category、data才算是匹配成功。但是一個Activity可以有多個intent-filter標簽,只要有一組intent-filter標簽匹配,即可啟動對應的Activity。

一個普通的 <intent-filter>標簽如下:

        <activity android:name=".demo.MyActivity">
            <intent-filter>
                <action android:name="bupt.edu.demo.a1"/>
                <action android:name="bupt.edu.demo.a2"/>
                <category android:name="bupt.edu.demo.c1"/>
                <category android:name="bupt.edu.demo.c2"/>
                <category android:name="android.intent.category.DEFAULT"/>
                <data android:mimeType="text/plain"/>
            </intent-filter>
        </activity>

action的匹配

action是一個字符串,系統本身是有些預定義的action的,不過我們也可以自己指定action。action的匹配規則是:

Intent中的action必須和action標簽下的字符串完全一致,但是當<intent-filter>標簽下有多個action標簽時,只需要匹一個action就算是成功匹配。例如在上面的例子中,只要我們的action值為"bupt.edu.demo.a1"或者"bupt.edu.demo.a2"即可算是匹配成功。

不過,需要注意的是Intent中如果沒有指定action,那么匹配失敗。另外,action是區分大小寫的,大小寫不同也會匹配失敗。

即,action的匹配要求Intent中的action必須存在且和過濾規則中的一個action完全一致。

category

category也是一個字符串,系統本身也有些預定義的category,我們也可以自己指定category。category的匹配規則是:

Intent中如果存在category,無論存在幾個,每個都必須能夠匹配到<intent-filter>標簽下的category才算匹配成功。不過,Intent如果沒設置category是可以匹配成功的。因為系統在調用startActivity或startActivityForResult時會默認在intent中添加android.intent.category.DEFAULT這個category。

因此我們在配置activity時,必須加上android.intent.category.DEFAULT這個category,才能被隱式調用。

data

data的語法有些復雜,主要由mimeType(用于指定媒體類型)和URI組成。在匹配時通過intent.setDataAndType(Uri data, String type)方法對data進行設置。

setData()方法和setType()方法會將對方的值清除設為null,因此不能同時調用兩個方法來設置data。

另外,如果我們要啟動隱式啟動Activity,應該先判斷是否有對應的Activity可以啟動。可以用PackageManager的resolveActivity方法或者Intent的resolveActivity方法判斷是否有Activity匹配該隱式Intent。

PackageManager的resolveActivity方法會返回最佳匹配的Activity信息,沒匹配到的話會返回null。
Intent的resolveActivity方法,會返回所有匹配到的Activity信息,沒匹配到的話會返回null。

同樣,針對Service、BoardcastReceiver也有相應的方法可以用來判斷。

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,825評論 6 546
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,814評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 178,980評論 0 384
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 64,064評論 1 319
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,779評論 6 414
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 56,109評論 1 330
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,099評論 3 450
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,287評論 0 291
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,799評論 1 338
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,515評論 3 361
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,750評論 1 375
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,221評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,933評論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,327評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,667評論 1 296
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,492評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,703評論 2 380

推薦閱讀更多精彩內容