Android 訊息傳遞之Intent和IntentFilter的匹配規則
1.Intent概述及作用
Intent 是一個訊息傳遞物件,您可以使用它從其他應用元件請求操作。
詳見官方文件
主要功能如下:
- 啟動Activity: 通過將 Intent 傳遞給 startActivity(),您可以啟動新的 Activity 例項。Intent 描述了要啟動的 Activity,並攜帶了任何必要的資料。
如果您希望在 Activity 完成後收到結果,請呼叫 startActivityForResult()。在 Activity 的 onActivityResult() 回撥中,您的 Activity 將結果作為單獨的 Intent 物件接收。 - 啟動服務: 通過將 Intent 傳遞給 startService(),您可以啟動服務執行一次性操作(例如,下載檔案)。Intent 描述了要啟動的服務,並攜帶了任何必要的資料。
如果服務旨在使用客戶端-伺服器介面,則通過將 Intent 傳遞給 bindService(),您可以從其他元件繫結到此服務。
注意:為了確保應用的安全性,啟動 Service 時,請始終使用顯式 Intent,且不要為服務宣告 Intent 過濾器。使用隱式 Intent 啟動服務存在安全隱患,因為您無法確定哪些服務將響應 Intent,且使用者無法看到哪些服務已啟動。
- 傳遞廣播: 通過將 Intent 傳遞給 sendBroadcast()、sendOrderedBroadcast() 或 sendStickyBroadcast(),您可以將廣播傳遞給其他應用。
2.Intent分類
- 顯式Intent: 按名稱(完全限定類名)指定要啟動的元件。
- 隱式Intent: 無需明確指定元件,而是宣告要執行的常規操作,從而允許其他應用中的元件來處理它。
兩種Intent在啟動Activity或Service時的區別:
- 建立顯式Intent啟動 Activity 或服務時,系統將立即啟動 Intent 物件中指定的應用元件。
- 建立隱式Intent啟動Activity,Android 系統通過將 Intent 的內容與在裝置上其他應用的清單檔案中宣告的 Intent 過濾器進行比較,從而找到要啟動的相應元件。 如果 Intent 與 Intent 過濾器匹配,則系統將啟動該元件
圖 1. 隱式 Intent 如何通過系統傳遞以啟動其他 Activity 的圖解:[1] Activity A 建立包含操作描述的 Intent,並將其傳遞給 startActivity()。[2] Android 系統搜尋所有應用中與 Intent 匹配的 Intent 過濾器。 找到匹配項之後,[3] 該系統通過呼叫匹配 Activity(Activity B)的 onCreate() 方法並將其傳遞給 Intent,以此啟動匹配 Activity。
3.構建Intent
Intent 中包含的主要資訊如下:
-
元件名稱: Intent 的這一欄位是一個 ComponentName 物件,您可以使用目標元件的完全限定類名指定此物件,其中包括應用的軟體包名稱。 例如, com.example.ExampleActivity。您可以使用 setComponent()、setClass()、setClassName() 或 Intent 建構函式設定元件名稱。此欄位也用來區分顯/隱式Intent。
-
操作(action): 指定要執行的通用操作(例如,“檢視”或“選取”)的字串。您可以使用 setAction() 或 Intent 建構函式為 Intent 指定操作。
如果定義自己的操作,請確保將應用的軟體包名稱作為字首。 例如:
static final String ACTION_TIMETRAVEL = "com.example.action.TIMETRAVEL";
-
資料(data): 引用待操作資料和/或該資料 MIME 型別的 URI(Uri 物件)。提供的資料型別通常由 Intent 的操作決定。建立 Intent 時,除了指定 URI 以外,指定資料型別(其 MIME 型別)往往也很重要。例如,能夠顯示影象的 Activity 可能無法播放音訊檔案,即便 URI 格式十分類似時也是如此。因此,指定資料的 MIME 型別有助於 Android 系統找到接收 Intent 的最佳元件。但有時,MIME 型別可以從 URI 中推斷得出,特別當資料是 content: URI 時尤其如此。這表明資料位於裝置中,且由 ContentProvider 控制,這使得資料 MIME 型別對系統可見。
要僅設定資料 URI,請呼叫 setData()。 要僅設定 MIME 型別,請呼叫 setType()。如有必要,您可以使用 setDataAndType() 同時顯式設定二者。
注意:若要同時設定 URI 和 MIME 型別,請勿呼叫 setData() 和 setType(),因為它們會互相抵消彼此的值。請始終使用
setDataAndType() 同時設定 URI 和 MIME 型別。
-
類別(category): 您可以使用 addCategory() 指定類別。
CATEGORY_LAUNCHER
該 Activity 是任務的初始 Activity,在系統的應用啟動器中列出。
以上列出的這些屬性(元件名稱、操作、資料和類別)表示 Intent 的既定特徵。 通過讀取這些屬性,Android
系統能夠解析應當啟動哪個應用元件。
但是,Intent 也有可能會攜帶一些不影響其如何解析為應用元件的資訊。 Intent 還可以提供:
- Extra: 攜帶完成請求操作所需的附加資訊的鍵值對。正如某些操作使用特定型別的資料 URI 一樣,有些操作也使用特定的 extra。您可以使用各種 putExtra() 方法新增 extra 資料,每種方法均接受兩個引數:鍵名和值。您還可以建立一個包含所有 extra 資料的 Bundle 物件,然後使用 putExtras() 將Bundle 插入 Intent 中。
- 標誌: 在 Intent 類中定義的、充當 Intent 元資料的標誌。 標誌可以指示 Android 系統如何啟動 Activity(例如,Activity 應屬於哪個任務),以及啟動之後如何處理(例如,它是否屬於最近的 Activity 列表)。
4.解析Intent(IntentFliter匹配規則)
Intent 過濾器是應用清單檔案中的一個表示式,它指定該元件要接收的 Intent 型別。
匹配原則: Intent需要匹配多組intent-fliter中的任意一組,每一組包含action、data、category,即Intent同時滿足這三者的過濾規則。
- action的匹配原則:
Intent中的action存在且必須和過濾規則中的其中一個action相同。(區分大小寫)如果該過濾器未列出任何action,則 Intent 沒有任何匹配項,因此所有 Intent 均無法通過測試。 但是,如果 Intent 未指定操作,則會通過測試(只要過濾器至少包含一個操作)。 - category的匹配原則:
Intent中如果存在categary,那麼所有的category都必須和intent-filter中相同。如果不含category,也可以匹配成功,因為在startActivity()中會預設給intent新增“android.intent.category.DEFAULT”。因此,要想acitivity接受隱式Intent,則必須在intent-filter中新增此category。
注:Android 會自動將 CATEGORY_DEFAULT 類別應用於傳遞給 startActivity() 和 startActivityForResult() 的所有隱式 Intent。因此,如需 Activity 接收隱式 Intent,則必須將 “android.intent.category.DEFAULT” 的類別包括在其 Intent 過濾器中。
- data的匹配原則:
與action類似,如果過濾規則中定義了data,那麼Intent中則必須也要定義可匹配的data。data的語法結構如下:
<data
android:scheme="string"
android:host="string"
android:port="string"
android:path="string"
android:pathPrefix="string"
android:pathPattern="string"
android:mimeType="string"/>
data由兩部分組成:
- mimeType: 指媒體型別
- URI: 路徑
結構< scheme >://< host >:< port >/[< path >|< pathPrefix >|< pathPattern >]
例如:content://com.example.project:200/folder/subfolder/etc
介紹一下每個資料的含義:
- Scheme: URI的模式,如http、file、content(必須指定否則整個URI無效)
- Host:URI的主機名(必須指定否則整個URI無效)
- Port:URI的埠號
- Path/pathPattern/pathPrefix:表述路徑資訊,Path表示完整的路徑資訊;pathPattern也表示完整的路徑資訊,可包含萬用字元“*”,表示0到多個任意字元,pathPrefix表示路徑的字首資訊
原則:要求Intent中必須含有data資料,並且能完全匹配過濾規則中的都一個data。完全匹配指過濾規則中出現的data部分也出現在了Intent中的data。intent-filter中可以不指定URI,但是有預設值,URI的預設值為content和file。因此要匹配URI必須為content和file。
測試
如下是一個過濾規則的例項:
<activity android:name="MainActivity">
<!-- This activity is the main entry, should appear in app launcher -->
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name="ShareActivity">
<!-- This activity handles "SEND" actions with text data -->
<intent-filter>
<action android:name="android.intent.action.SEND"/>
<category android:name="android.intent.category.DEFAULT"/>
<data android:mimeType="text/plain"/>
</intent-filter>
<!-- This activity also handles "SEND" and "SEND_MULTIPLE" with media data -->
<intent-filter>
<action android:name="android.intent.action.SEND"/>
<action android:name="android.intent.action.SEND_MULTIPLE"/>
<category android:name="android.intent.category.DEFAULT"/>
<data android:mimeType="application/vnd.google.panorama360+jpg"/>
<data android:mimeType="image/*"/>
<data android:mimeType="video/*"/>
</intent-filter>
</activity>
- action測試
新增intent-filter:
<activity android:name=".Main2Activity">
<intent-filter>
<action android:name="android.intent.action.EDIT"/>
<action android:name="android.intent.action.CALL"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</activity>
MainActivity中新增:
btnAction.setOnClickListener {
val intent = Intent()
intent.action = Intent.ACTION_EDIT
startActivity(intent)
}
點選按鈕成功跳轉。
- category測試
新增intent-filter:
<activity android:name=".Main2Activity">
<intent-filter>
<action android:name="android.intent.action.EDIT"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="com.wdl.intentfliter.category.a"/>
<category android:name="com.wdl.intentfliter.category.b"/>
<category android:name="com.wdl.intentfliter.category.c"/>
</intent-filter>
</activity>
MainActivity中新增:
btnAction.setOnClickListener {
val intent = Intent()
intent.action = Intent.ACTION_EDIT
intent.addCategory("com.wdl.intentfliter.category.b")
intent.addCategory("com.wdl.intentfliter.category.a")
intent.addCategory("com.wdl.intentfliter.category.c")
startActivity(intent)
}
點選按鈕成功跳轉。
- data測試
新增intent-filter:
<activity android:name=".Main2Activity">
<intent-filter>
<action android:name="android.intent.action.EDIT"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="com.wdl.intentfliter.category.a"/>
<category android:name="com.wdl.intentfliter.category.b"/>
<category android:name="com.wdl.intentfliter.category.c"/>
<data android:mimeType="text/plain"/>
</intent-filter>
</activity>
MainActivity中:
btnAction.setOnClickListener {
val intent = Intent()
intent.action = Intent.ACTION_EDIT
intent.setDataAndType(Uri.parse("file://test"),"text/plain")
intent.addCategory("com.wdl.intentfliter.category.b")
intent.addCategory("com.wdl.intentfliter.category.a")
intent.addCategory("com.wdl.intentfliter.category.c")
startActivity(intent)
}
上面的例子中存在Android 7.0 FileProvider適配問題,請自行解決。
注意:若要同時設定 URI 和 MIME 型別,請勿呼叫 setData() 和 setType(),因為它們會互相抵消彼此的值。請始終使用 setDataAndType() 同時設定 URI 和 MIME 型別。
為了避免在intent-fliter匹配過程中出現找不到Activity導致的異常閃退問題,提供了2中判斷方法:
- PackageManager的resolveActivity方法
- Intent中的resolveActivity方法
如果以上兩種方法找不到匹配的Activity,就會返回null。防止出現異常。
btnAction.setOnClickListener {
val intent = Intent()
intent.action = Intent.ACTION_EDIT
intent.type = "text/plain"
intent.addCategory("com.wdl.intentfliter.category.b")
intent.addCategory("com.wdl.intentfliter.category.a")
intent.addCategory("com.wdl.intentfliter.category.c")
packageManager.resolveActivity(intent, MATCH_DEFAULT_ONLY)?.let {
startActivity(intent)
}
}
MATCH_DEFAULT_ONLY:
這個標記位含義是僅僅匹配那些聲明瞭 < category android:name=“android.intent.category.DEFAULT”/>的Activity。
另外PackageManager還提供了queryIntentActivities方法,返回所有匹配成功的Activity。