1. 程式人生 > >Android 訊息傳遞之Intent和IntentFilter的匹配規則

Android 訊息傳遞之Intent和IntentFilter的匹配規則

1.Intent概述及作用

Intent 是一個訊息傳遞物件,您可以使用它從其他應用元件請求操作。
詳見官方文件

主要功能如下:

  1. 啟動Activity: 通過將 Intent 傳遞給 startActivity(),您可以啟動新的 Activity 例項。Intent 描述了要啟動的 Activity,並攜帶了任何必要的資料。
    如果您希望在 Activity 完成後收到結果,請呼叫 startActivityForResult()。在 Activity 的 onActivityResult() 回撥中,您的 Activity 將結果作為單獨的 Intent 物件接收。
  2. 啟動服務: 通過將 Intent 傳遞給 startService(),您可以啟動服務執行一次性操作(例如,下載檔案)。Intent 描述了要啟動的服務,並攜帶了任何必要的資料。
    如果服務旨在使用客戶端-伺服器介面,則通過將 Intent 傳遞給 bindService(),您可以從其他元件繫結到此服務。
注意:為了確保應用的安全性,啟動 Service 時,請始終使用顯式 Intent,且不要為服務宣告 Intent 過濾器。使用隱式 Intent 啟動服務存在安全隱患,因為您無法確定哪些服務將響應 Intent,且使用者無法看到哪些服務已啟動。
  1. 傳遞廣播: 通過將 Intent 傳遞給 sendBroadcast()、sendOrderedBroadcast() 或 sendStickyBroadcast(),您可以將廣播傳遞給其他應用。

2.Intent分類

  • 顯式Intent: 按名稱(完全限定類名)指定要啟動的元件。
  • 隱式Intent: 無需明確指定元件,而是宣告要執行的常規操作,從而允許其他應用中的元件來處理它。

兩種Intent在啟動Activity或Service時的區別:

  1. 建立顯式Intent啟動 Activity 或服務時,系統將立即啟動 Intent 物件中指定的應用元件。
  2. 建立隱式Intent啟動Activity,Android 系統通過將 Intent 的內容與在裝置上其他應用的清單檔案中宣告的 Intent 過濾器進行比較,從而找到要啟動的相應元件。 如果 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同時滿足這三者的過濾規則。

  1. action的匹配原則:
    Intent中的action存在且必須和過濾規則中的其中一個action相同。(區分大小寫)如果該過濾器未列出任何action,則 Intent 沒有任何匹配項,因此所有 Intent 均無法通過測試。 但是,如果 Intent 未指定操作,則會通過測試(只要過濾器至少包含一個操作)。
  2. 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 過濾器中。

  1. 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>
  1. 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)
        }

點選按鈕成功跳轉。
在這裡插入圖片描述

  1. 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)
        }

點選按鈕成功跳轉。
在這裡插入圖片描述

  1. 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中判斷方法:

  1. PackageManager的resolveActivity方法
  2. 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。