Activity的啟動模式及IntentFilter匹配規(guī)則總結(jié)

android的啟動模式是在我們?nèi)粘i_發(fā)中經(jīng)常用使用到丐谋,這個也是在面試用經(jīng)常問到的一個問題淆两。雖然我們對他很熟悉鬼店,但也會有些地方了解的太全面咐柜,因此寫篇文章來來總結(jié)這方面的知識踢涌。文章主要內(nèi)容來自《android開發(fā)藝術(shù)探討》這本書通孽,在文章的最后這本書的網(wǎng)頁版本可供查看。


image

項目源碼

目錄

  • 四中啟動模式
  • 什么是任務(wù)棧
  • Activity如何指定需要的任務(wù)棧
  • TaskAffinity使用場景
  • Activity 的Flags
  • IntentFilter的匹配規(guī)則
  • 如何判斷隱式啟動是否成功

1. 四中啟動模式

standard: 標準啟動模式

  • 每啟動一個Activity都會重新創(chuàng)建斯嚎,不管這個實例是否存在利虫。
<!--系統(tǒng)默認啟動方式,不需要指定launchMode值-->
<activity 
android:name=".StandardActivity"/>

下面內(nèi)容摘自《android開發(fā)藝術(shù)探討》第一章16頁底部

在standard模式下堡僻,誰啟動了這個Activity那么這個Activity就運行在它的任務(wù)棧中糠惫。例如:ActivityA啟動了ActivityB(B為標準模式),那么ActivityB就會進入ActivityA的任務(wù)棧中钉疫。

啟動Activity的時候傳入的Context不要是ApplicationContext硼讽。如果一定要傳,那么一定要設(shè)置intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);否則回報下面異常:

image

singleTop: 棧頂復用模式

  • 新啟動Activity棧頂已經(jīng)存在牲阁,不會重新創(chuàng)建固阁,同時會調(diào)用onNewIntent方法
  • Activity如果存在壤躲,但是不再棧頂,則會重新創(chuàng)建备燃,并將新的Activity壓入棧頂碉克。
<activity 
android:name=".SingleTopActivity"
android:launchMode="singleTop"/>

singleTask: 棧內(nèi)復用模式

  • 只要Activity在棧內(nèi)存在,多次啟動此Activity也不會創(chuàng)建新的實例并齐,并且系統(tǒng)會調(diào)用onNewIntent方法
  • 如果Activity在棧內(nèi)存在漏麦,但是沒有在棧頂,系統(tǒng)會將該Activity之上的Activity全部擠出棧頂况褪,使該Activity位于棧頂撕贞。
<activity 
android:name=".SingleTaskActivity"
android:launchMode="singleTask" />

singleInstance: 單實例模式

  • 該Activity只能單獨位于一個任務(wù)棧中
<activity 
android:name=".SingleInstanceActivity"
android:launchMode="singleInstance" >

生命周期執(zhí)行:

  • singleTask、singleInstance测垛、singleInstance模式下捏膨,如果啟動該Activity正好在頂部。那么他的生命周期執(zhí)行為:
onPause-->onNewIntent-->onResume
  • singleTask食侮、singleInstance模式下号涯,如果棧內(nèi)有Activity實例,但不在棧頂疙描。那么生命周期執(zhí)行如下
onNewIntent-->onRestart-->onStart

2. 什么是任務(wù)棧

查看activity在棧中的情況诚隙,可在控制臺輸入:adb shell dumpsys activity activities 通過搜索關(guān)鍵字 most recent first 快速定位 留意包名

任務(wù)棧(Task):

Task特點:

  • android的任務(wù)棧主要用于存放Activity,遵循先進后出的原則起胰。
  • android的任務(wù)棧是一個包含了Activity的集合久又,我們每次打開新的Activity或者關(guān)閉一個Activity任務(wù)棧中就會增加或減少一個Activity組件。
  • 任務(wù)棧在沒有Activity或者App退出的時候都會被銷毀效五。
  • 一個App不止有一個任務(wù)棧地消,任務(wù)棧的Activity可以來自不同的App,同一個App的Activity也可以不再一個任務(wù)棧中畏妖。

3. Activity如何指定需要的任務(wù)棧

Activity指定需要啟動的任務(wù)椔鲋矗可以用過在配置文件中添加taskAffinity屬性來實現(xiàn)。

TaskAffinity特點:

  • 默認情況下戒劫,Activity啟動的任務(wù)棧名稱為應(yīng)用包名半夷。
  • 如果自己指定該屬性值,不能與包名相同迅细,否則相當于沒指定巫橄。
  • 該屬于一般配合singleTask或者allowTaskReparenting屬性結(jié)合使用,在其他情況下沒有實際意義茵典。
<activity android:name=".TestActivity"
android:launchMode="singleTask"
android:taskAffinity="com.test.singleTask.affinity"/>

<activity android:name=".Test2ActivityC" 
android:exported="true"
android:allowTaskReparenting="true"/>

4. TaskAffinity使用場景

下面這段內(nèi)容摘自Activity啟動模式與任務(wù)棧(Task)全面深入記錄(下)這篇文章湘换。

TaskAffinity與singleTask應(yīng)用場景

假如現(xiàn)在有這么一個需求,我們的客戶端app正處于后臺運行,此時我們因為某些需要,讓微信調(diào)用自己客戶端app的某個頁面彩倚,用戶完成相關(guān)操作后筹我,我們不做任何處理,按下回退或者當前Activity.finish()帆离,頁面都會停留在自己的客戶端(此時我們的app回退棧不為空)蔬蕊,這顯然不符合邏輯的,用戶體驗也是相當出問題的哥谷。我們要求是袁串,回退必須回到微信客戶端,而且要保證不殺死自己的app.這時候我們的處理方案就是,設(shè)置當前被調(diào)起Activity的屬性為:
LaunchMode=""SingleTask" taskAffinity="com.tencent.mm"
其中com.tencent.mm是借助于工具找到的微信包名呼巷,就是把自己的Activity放到微信默認的Task棧里面,這樣回退時就會遵循“Task只要有Activity一定從本Task剩余Activity回退”的原則赎瑰,不會回到自己的客戶端王悍;而且也不會影響自己客戶端本來的Activity和Task邏輯。

TaskAffinity與allowTaskReparenting應(yīng)用場景

一個e-mail應(yīng)用消息包含一個網(wǎng)頁鏈接餐曼,點擊這個鏈接將出發(fā)一個activity來顯示這個頁面压储,雖然這個activity是瀏覽器應(yīng)用定義的,但是activity由于e-mail應(yīng)用程序加載的源譬,所以在這個時候該activity也屬于e-mail這個task集惋。如果e-mail應(yīng)用切換到后臺,瀏覽器在下次打開時由于allowTaskReparenting值為true踩娘,此時瀏覽器就會顯示該activity而不顯示瀏覽器主界面刮刑,同時actvity也將從e-mail的任務(wù)棧遷移到瀏覽器的任務(wù)棧,下次打開e-買了時并不會再顯示該activity养渴。

Taskffinity與singleTask實例:

注: 如果使用我在GitHub上建立的項目測這個功能的時候雷绢,請將TestTaskffinityOrAllowTaskRep.zip這個壓縮包解壓,并導入AS中理卑。這個壓縮包是我在測試的時候?qū)懙挠糜谔D(zhuǎn)androidreview應(yīng)用的testTask應(yīng)用翘紊。

testTask應(yīng)用(簡稱T應(yīng)用)MainActivity中有一個按鈕A,點擊按鈕會調(diào)用androidreview應(yīng)用(簡稱A應(yīng)用)的SingleTaskActivity藐唠。下面是兩個應(yīng)用的主要代碼帆疟。

testTask應(yīng)用代碼:

switch (v.getId()) {
    case R.id.mBnt_ForSingleTask:
    //跳轉(zhuǎn)androidreview應(yīng)用SingleToak頁面的按鈕方法
    
    ComponentName cnForSingleTask = new ComponentName(
            "com.hdd.androidreview",
            "com.hdd.androidreview.Patterm.SingleTaskActivity");
    intent.setComponent(cnForSingleTask);
    startActivity(intent);
    break;
}

androidreview應(yīng)用代碼:

<activity
    android:name=".Patterm.SingleTaskActivity"
    android:exported="true"
    android:launchMode="singleTask"
    android:taskAffinity="cmom.han.testbt.testTask">
</activity>

從上面的A應(yīng)用代碼配置信息中可以看到taskAffinity屬性配置的是T應(yīng)用的包名。因此SingleTaskActivity會在T的任務(wù)中被創(chuàng)建宇立。假如MainActivity的按鈕A點擊事件中啟動了SingleTaskActivity踪宠。那么cmom.han.testbt.testTask任務(wù)棧中會存在SingleTaskActivity和MainActivity兩個Activity。下面是Activity在棧中的信息泄伪。


image

這時如果按了hone鍵返回桌面SingleTaskActivity進入后臺殴蓬,然后點擊A應(yīng)用的圖標啟動的卻是A應(yīng)用的MainActivity。出現(xiàn)這種情況是因為SingleTaskActivity的taskAffinity屬性指定的是T應(yīng)用包名。在T應(yīng)用的MainActivity啟動的SingleTaskActivy是在T應(yīng)用的任務(wù)棧中染厅。

TaskAffinity與allowTaskReparenting實例
T應(yīng)用的B按鈕點擊事件代碼:

case R.id.mBnt_ForAllowTaskRep:
//allowTaskReparenting模式跳轉(zhuǎn)PattermActivity

intent.setAction("com.hdd.androidreview.PattermActivity");
ComponentName cnForAllowTaskRep = new ComponentName(
        "com.hdd.androidreview",
        "com.hdd.androidreview.Patterm.PattermActivity");
intent.setComponent(cnForAllowTaskRep);

break;

A應(yīng)用的PattermActivity(簡稱PActivity)的配置信息:

<activity
    android:name=".Patterm.PattermActivity"
    android:allowTaskReparenting="true"
    android:theme="@style/AppTheme.NoActionBar" >
    <intent-filter>
        <action android:name="com.hdd.androidreview.PattermActivity"/>
    </intent-filter>
</activity>

點擊T用于的B按鈕痘绎,會啟動A用的PActivity。該Activity的allowTaskReparenting屬性為true肖粮,那么Activity會被移動到T應(yīng)用的任務(wù)中站創(chuàng)建孤页。當T應(yīng)用按home返回桌面,再點擊A應(yīng)用涩馆;PActivity會被移回A的任務(wù)棧中行施。

T應(yīng)用調(diào)用A應(yīng)用的PActivity棧內(nèi)信息


image

T應(yīng)用按home返回桌面,在啟動A應(yīng)用棧內(nèi)信息:


image

allowTaskReparenting僅限于以standard 和singleTop啟動的activity使用

5. Activity 的Flags

指定Activity的啟動模式有兩種魂那,一種是在AndroidMenifest.xml中指定

<activity
    android:name=".Patterm.SingleInstanceActivity"
    android:launchMode="singleInstance">

另外一種是通過Intent來指定

Intent intent = new Intent();
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
//intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK|Intent.FLAG_ACTIVITY_CLEAR_TOP);
intent.setClass(context, CycleActivity.class);
context.startActivity(intent);

注:intent指定的優(yōu)先級大于xml中指定的優(yōu)先級蛾号;如果兩個方式都指定了啟動方式,那么系統(tǒng)會以intent指定的啟動方式為準涯雅。

FLAG_ACTIVITY_NEW_TASK

等同于在xml中配置了singleTask啟動碼模式

FLAG_ACTIVITY_SINGLE_TOP

等同于在xml中配置了singleTop啟動碼模式

FLAG_ACTIVITY_CLEAR_TOP

singTask自帶該標記鲜结。這個標記會清除同一個任務(wù)棧中目標Activity之上的Activity。
如果目標Activity采用了standard啟動模式活逆,但是任務(wù)棧中已經(jīng)存在了Activity的實例精刷。那么系統(tǒng)會清除任務(wù)中該實例以及它上面的Activity,并且會重新創(chuàng)建一個目標Activity實例放入棧頂蔗候。

6. IntentFilter的匹配規(guī)則

啟動Activity有兩種怒允,一種為顯示調(diào)用

Intent intent = new Intent(MainActivity.this, TestActivity.class);
startActivity(intent);

另一種為隱式調(diào)用要配置清單文件

<!--隱式調(diào)用-->
<activity
    android:name=".Patterm.PattermActivity"
    android:theme="@style/AppTheme.NoActionBar">
    <intent-filter>
        <action android:name="com.hdd.androidreview.asdf" />
        <category android:name="com.hdd.123456" />
        <!--比就加上,否則會報錯-->
        <category android:name="android.intent.category.DEFAULT" />
    </intent-filter>
</activity>

Activity跳轉(zhuǎn)代碼

Intent intent = new Intent();
intent.setAction("com.hdd.androidreview.asdf");
//category非必須指定锈遥。如果要指定纫事,一點要和清單文件中填寫的一至
// intent.addCategory("com.hdd.123456");
context.startActivity(intent);

1. action

  • 可以再清單文件中配置多個
  • intent指定的action值只要和清單文件中的其中一個字符串值一樣,即可匹配成功迷殿。
  • 如果清單文件中配置了action儿礼,那么在intent跳轉(zhuǎn)中必須至少指定且配對成功一個。
<activity
    android:name=".Patterm.PattermActivity"
    android:theme="@style/AppTheme.NoActionBar">
    <intent-filter>
        <action android:name="com.hdd.androidreview.asdf" />
        <action android:name="com.hdd.androidreview.qwer" />
        <action android:name="12345678" />
        <!--必須加上庆寺,否則會報錯-->
        <category android:name="android.intent.category.DEFAULT" />
    </intent-filter>
</activity>

//java代碼
Intent intent = new Intent();
intent.setAction("12345678");
context.startActivity(intent);

2. category

  • category可以再清單文件中添加多個
  • intent的addCategory()字符串有一個相同就可以匹配成功蚊夫。
  • 他和action區(qū)別是,action是要在intent必須要指定的懦尝,切至少和一個匹配成功知纷。category可以不用指定。如果指定也是至少和一個匹配成功陵霉。
Intent intent = new Intent();
//必須指定一個并匹配成功
intent.setAction("com.hdd.androidreview.asdf");
//category非必須指定琅轧。如果要指定,一點要和清單文件中填寫的一至
// intent.addCategory("com.hdd.123456");
context.startActivity(intent);

3. data

  • data的匹配規(guī)則和action類似踊挠,如果過濾規(guī)則中定義了data乍桂,那么intent中必須要匹配data冲杀。

  • data有mimeType和URL兩部分組成

mimeType為媒體類型: image/jpeg、audio/mpeg4-generic以及video/*等睹酌。

URL數(shù)據(jù)結(jié)構(gòu)為:

<scheme>://<host>:<prot>/[<path>|<pathPrefix>|<pathPattern>]

例如:

content://com.example.project:200/folder/subfolder/etc
http://www.baidu.com:80/search/info

Scheme:URL的模式权谁,比如http、file憋沿、content等旺芽;如果URL沒有指定scheme,這個URL是無效的辐啄。

Host:URL主機名,比如www.baidu.com采章,如果未指定,URL無效壶辜。

Port:RUL端口號悯舟,比如80,只有scheme和host指定了port才有意義砸民。

Path:表示完整的路徑信息图谷。
PathPattern:表示完整路徑信息,里面可以包含通配符阱洪。
PathPrefix:表示路徑的前綴信息

data在定義的時候有兩種情況需要注意

  1. 第一種情況

非完整寫法,及只指定了mimeType或者只指定了URL菠镇。

注:如果只指定了URL冗荸,系統(tǒng)會默認設(shè)置mimeType的值為content和file

<!-- 隱式調(diào)用,只配置URL -->
<activity android:name=".RegulationActivity">
    <intent-filter>
        <action android:name="12345678" />
        <!-- 必須加上,否則會報錯 -->
        <category android:name="android.intent.category.DEFAULT" />
        <data
            android:host="www.baidu.com"
            android:scheme="http" />
    </intent-filter>
</activity>


 <!-- 隱式調(diào)用利耍,只配置mimeType· -->
<activity android:name=".RegulationActivity">
    <intent-filter>
        <action android:name="12345678" />
        <!-- 必須加上蚌本,否則會報錯 -->
        <category android:name="android.intent.category.DEFAULT" />
        <data android:mimeType="audio/mpeg" />
    </intent-filter>
</activity>

java代碼:

//intent指定只配置了URL的data
intent.setAction("12345678");
intent.setData(Uri.parse("http://www.baidu.com"));
context.startActivity(intent);

//intent指定只配置了mimeType的data
intent.setAction("12345678");
intent.setType("audio/mpeg");
context.startActivity(intent);
  1. 第二種情況

完整寫法

注:如果intent指定的為完整的data,必須要使用setDataAndType(),因為setData()和setType()會彼此清楚對方的值隘梨。

<!-- 隱式調(diào)用 -->
<activity android:name=".RegulationActivity">
    <intent-filter>
        <action android:name="12345678" />
        <!-- 必須加上程癌,否則會報錯 -->
        <category android:name="android.intent.category.DEFAULT" />
        <data
            android:mimeType="audio/mpeg"
            android:host="www.baidu.com"
            android:scheme="http" />
    </intent-filter>
</activity>

java代碼:

//intent完整的data
intent.setAction("12345678");
intent.setDataAndType(Uri.parse("http://www.baidu.com"), "audio/mpeg");
context.startActivity(intent);

還有一種特殊寫法:

//寫法1
<intent-filter>
    <data
        android:mimeType="audio/mpeg"
        android:host="www.baidu.com"
        android:scheme="http" />
</intent-filter>

//寫法2
<intent-filter>
    <data android:mimeType="audio/mpeg" />
    <data android:scheme="http" />
    <data android:host="www.baidu.com" />
</intent-filter>

上面的兩個寫法上在使用效果上是一樣的。

7. 如何判斷隱式啟動是否成功

第一種轴猎,使用intent的resolveActivity

ComponentName componentName = intent.resolveActivity(context.getPackageManager());
if (componentName != null)
    context.startActivity(intent);
else
    Toast.makeText(context, "匹配不成功", Toast.LENGTH_SHORT).show();

第二種嵌莉,使用PackageManager的resolveActivity

PackageManager packageManager=context.getPackageManager();
ResolveInfo resolveInfo = packageManager.resolveActivity(intent,PackageManager.MATCH_DEFAULT_ONLY);
//判斷是否匹配成功
if (resolveInfo != null)
    context.startActivity(intent);
else
    Toast.makeText(context, "匹配不成功", Toast.LENGTH_SHORT).show();

上面的代碼中返回ResolveInfo不是最佳的Activity信息,而是所有匹配成功的Activity信息捻脖。resolveActivity()填寫的第二個參數(shù)必須是MATCH_DEFAULT_ONLY锐峭。具體原因可以查看《android開發(fā)藝術(shù)探索》第一章34頁,這里就不解釋了可婶。

終結(jié):

用了3天終于寫完了沿癞!其實寫這篇文章是為了復習android的基礎(chǔ)知識,在復習過程中增加了對Activity啟動模式以及Activity任務(wù)棧中的狀態(tài)的了解矛渴。文章中四個啟動模式中最麻煩的模式個人認為是singTask椎扬,最有意思的為Taskffinerty這個標簽的使用,自己可以寫個demo或者使用我在GitHub上的項目測試。

參考

Android開發(fā)藝術(shù)探索完結(jié)篇——天道酬勤

Activity啟動模式與任務(wù)棧(Task)全面深入記錄(下)

Activity啟動模式與任務(wù)棧(Task)全面深入記錄(上)

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末蚕涤,一起剝皮案震驚了整個濱河市筐赔,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌钻趋,老刑警劉巖川陆,帶你破解...
    沈念sama閱讀 217,907評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異蛮位,居然都是意外死亡较沪,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,987評論 3 395
  • 文/潘曉璐 我一進店門失仁,熙熙樓的掌柜王于貴愁眉苦臉地迎上來尸曼,“玉大人,你說我怎么就攤上這事萄焦】亟危” “怎么了?”我有些...
    開封第一講書人閱讀 164,298評論 0 354
  • 文/不壞的土叔 我叫張陵拂封,是天一觀的道長茬射。 經(jīng)常有香客問我,道長冒签,這世上最難降的妖魔是什么在抛? 我笑而不...
    開封第一講書人閱讀 58,586評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮萧恕,結(jié)果婚禮上刚梭,老公的妹妹穿的比我還像新娘。我一直安慰自己票唆,他們只是感情好朴读,可當我...
    茶點故事閱讀 67,633評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著走趋,像睡著了一般衅金。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上簿煌,一...
    開封第一講書人閱讀 51,488評論 1 302
  • 那天典挑,我揣著相機與錄音,去河邊找鬼啦吧。 笑死您觉,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的授滓。 我是一名探鬼主播琳水,決...
    沈念sama閱讀 40,275評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼肆糕,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了在孝?” 一聲冷哼從身側(cè)響起诚啃,我...
    開封第一講書人閱讀 39,176評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎私沮,沒想到半個月后始赎,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,619評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡仔燕,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,819評論 3 336
  • 正文 我和宋清朗相戀三年造垛,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片晰搀。...
    茶點故事閱讀 39,932評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡五辽,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出外恕,到底是詐尸還是另有隱情杆逗,我是刑警寧澤,帶...
    沈念sama閱讀 35,655評論 5 346
  • 正文 年R本政府宣布鳞疲,位于F島的核電站罪郊,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏尚洽。R本人自食惡果不足惜排龄,卻給世界環(huán)境...
    茶點故事閱讀 41,265評論 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望翎朱。 院中可真熱鬧,春花似錦尺铣、人聲如沸拴曲。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,871評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽澈灼。三九已至,卻和暖如春店溢,著一層夾襖步出監(jiān)牢的瞬間叁熔,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,994評論 1 269
  • 我被黑心中介騙來泰國打工床牧, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留荣回,地道東北人。 一個月前我還...
    沈念sama閱讀 48,095評論 3 370
  • 正文 我出身青樓戈咳,卻偏偏與公主長得像心软,于是被迫代替她去往敵國和親壕吹。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,884評論 2 354

推薦閱讀更多精彩內(nèi)容