Intent以及IntentFilter詳解

1. 前言

       在Android中有四大組件,這些組件中有三個組件與Intent相關,可見Intent在Android整個生態中的地位高度。Intent是信息的載體,用它能夠去請求組件作相應的操做,可是相對於這個功能,Intent自己的結構更值得咱們去研究。html

2. Intent與組件

       Intent促進了組件之間的交互,這對於開發者很是重要,並且它還能作爲消息的載體,去指導組件作出相應的行爲,也就是說Intent能夠攜帶數據,傳遞給Activity/Service/BroadcastReceiver。android

  • 啓動Activity。Activity能夠簡單的理解爲手機屏幕中的一個頁面,你能夠經過將Intent傳入startActivity方法來啓動一個Activity的實例,也就是一個頁面,同時,Intent也能夠攜帶數據,傳遞給新的Activity。若是想要獲取新建的Activity執行結果,能夠經過onActivityResult()方法去啓動Activity。
  • 啓動Service。Service是一個不呈現交互畫面的後臺執行操做組件,能夠經過將Intent穿入startService()方法來啓動一個Service來啓動服務。
  • 傳遞廣播BroadCast。廣播是任何應用均可以接收到的消息,經過將Intent傳遞給 sendBroadcast()sendOrderedBroadcast()sendStickyBroadcast()方法,能夠將廣播傳遞接收方。

3. Intent類型

在Android中,Intent分爲兩種類型,顯式和隱式。web

  • 顯式Intent,能夠經過類名來找到相應的組件,在應用中用顯式Intent去啓動一個組件,一般是由於咱們知道這個組件(Activity或者Service)的名字。以下代碼,咱們知道具體的Activity的名字,要啓動一個新的Activity,下面就是用的顯示Intent。瀏覽器

    Intent intent = new Intent(context,XXActivity.class);
    startActivity(intent);
  • 隱式Intent,不指定具體的組件,可是它會聲明將要執行的操做,從而匹配到相應的組件。最簡單的Android中調用系統撥號頁面準備打電話的操做,就是隱式Intent。安全

    Intent intent = new Intent(Intent.ACTION_DIAL);
    Uri data = Uri.parse("tel:" + "135xxxxxxxx");
    intent.setData(data);
    startActivity(intent);
使用顯示Intent去啓動Activity或者Service的時候,系統將會當即啓動Intent對象中指定的組件。

       使用隱式Intent的時候,系統經過將Intent對象中的IntentFilter與組件在AndroidManifest.xml或者代碼中動態聲明的IntentFilter進行比較,從而找到要啓動的相應組件。若是組件的IntentFilter與Intent中的IntentFilter正好匹配,系統就會啓動該組件,並把Intent傳遞給它。若是有多個組件同時匹配到了,系統則會彈出一個選擇框,讓用戶選擇使用哪一個應用去處理這個Intent,好比有時候點擊一個網頁連接,會彈出多個應用,讓用戶選擇用哪一個瀏覽器去打開該連接,就是這種狀況。網絡

       IntentFilter一般是定義在AndroidManifest.xml文件中,也能夠動態設置,一般是用來聲明組件想要接受哪一種Intent。例如,你若是爲一個Activity設置了IntentFilter,你就能夠在應用內或者其餘應用中,用特定的隱式Intent來啓動這個Activity,若是沒有爲Activity設置IntentFilter,那麼你就只能經過顯示Intent來啓動這個Activity。app

注意,爲了確保系統的穩定性,官方建議使用顯示Intent來啓動Service,同時也不要爲Service設置IntentFilter,由於若是使用隱式Intent去啓動Service,咱們並不知道那些服務會響應Intent,並且因爲服務大可能是不可見的,咱們也不知道那些服務被啓動了,這是很是危險的。在Android 5.0(API 21)之後,若是使用隱式的Intent去調用bindService()方法,系統會拋出異常。ide

4. Intent的屬性

       Intent做爲消息的載體,系統根據它去決定啓動哪一個具體的組件同時將組件執行中須要的信息傳遞過去。Intent可以包含的屬性有Component、Action、Data、Category、Extras、Flags,關於這些屬性的更詳細信息可查看這裏函數

  • Component,要啓動的組件名稱。這個屬性是可選的,但它是顯式Intent的一個重要屬性,設置了這個屬性後,該Intent只能被傳遞給由Component定義的組件。隱式Intent是沒有該屬性的,系統是根據其餘的信息(例如,Action、Data等)來判斷該Intent應該傳遞給哪一個組件。這個屬性是目標的組件的具體名稱(徹底限定類名),例如,com.example.DemoActivity。該屬性能夠經過setComonentName()setClass()setClassName()或者Intent的構造函數來設置。
  • Action,代表執行操做的字符串。它會影響Intent的其他信息,好比Data、Extras。該屬性能夠經過setAction()方法或者Intent的構造函數來設置。用戶能夠自定義這個屬性,也可使用系統中已經有的Action值。下面列出啓動Activity時候的一些通用Action屬性。工具

    • ACTION_VIEW,當有一些信息須要展現出來,能夠設置Intent的Action爲這個值,並調用startActivity()方法
    • ACTION_SEND,當用戶有一些信息須要分享到其餘應用,能夠設置Intent的Action爲這個值,並調用startActivity()方法
    • ACTION_DIAL,撥打電話,能夠設置Intent的Action爲這個值,並調用startActivity()方法
    • ACTION_EDIT,編輯某些文件,能夠設置Intent的Action爲這個值,並調用startActivity()方法
  • Data,它是待操做數據的引用URI或者數據MIME類型的URI,它的值一般與Intent的Action有關聯。好比,若是設置Action的值爲ACTION_EDIT,那麼Data的值就必須包含被編輯文檔的URI。當咱們建立Intent的時候,設置MIME類型很是重要。例如,一個能夠顯示圖片的Activity可能不能播放音頻,圖片和音頻的URI很是相似,若是咱們設置了MIME類型,能夠幫助系統找到最合適的組件接受Intent。有時候,MIME類型也能夠從URI判斷出來,例如當Data是一個包含content:字符串的URI時候,能夠明確的知道,待處理的數據存在設備中,並且由ContentProvider控制。

    • 使用setData()方法設置數據引用的URI,使用setType()方法設置數據的MIME類型,使用setDataAndType()方法同時設置這兩個屬性。
    • 注意:若是想要設置兩個的屬性,直接用setDataAndType()方法,不要同時調用setData()setType()方法,由於這兩個方法設置的值會相互覆蓋

      public Intent setData(Uri data) {
          mData = data;
          mType = null;
          return this;
      }
      
      public Intent setType(String type) {
          mData = null;
          mType = type;
          return this;
      }
  • Category,這個屬性是對處理該Intent組件信息的補充。它是一個ArraySet類型的容器,因此能夠向裏面添加任意數量的補充信息,同時,Intent沒有設置這個屬性不會影響解析組件信息。能夠經過addCategory()方法來設置該屬性。下面列出一些經常使用的Category的值。

    • CATEGORY_BROWSABLE,設置Category爲該值後,在網頁上點擊圖片或連接時,系統會考慮將此目標Activity列入可選列表,供用戶選擇以打開圖片或連接。
    • CATEGORY_LAUNCHER,應用啓動的初始Activity,這個Activity會被添加到系統啓動launcher當中。

以上列出的這些關於Intent的屬性(Component、Action、Data、Category)能夠幫助系統來肯定具體的組件,可是有一些Intent的屬性,不會影響到組件的肯定。

  • Extras,以key-value鍵值對的形式來存儲組件執行操做過程當中須要的額外信息,能夠調用putExtra()方法來設置該屬性,這個方法接受兩個參數,一個是key,一個是value。也能夠經過實例化一個儲存額外信息的Bundle對象,而後調用putExtras()方法將咱們實例化的Bundle添加到Intent中。
  • Flags,這個屬性能夠指示系統如何啓動一個Activity,以及啓動以後如何處理。例如Activity屬於哪個task(參考Activity的四種啓動方式)。

5. 顯式Intent示例

       上文說到,顯式Intent是用於啓動某個特定的組件(Activity或者Service)的Intent,穿建立顯式的Intent的時候須要設置組件名稱(Component)屬性,其餘的屬性都是可選屬性。

// fileUrl是一個URL字符串,例如 "http://www.example.com/image.png"
Intent downloadIntent = new Intent(context, DownloadService.class);
downloadIntent.setData(Uri.parse(fileUrl));
startService(downloadIntent);

這裏的Intent的構造函數傳入了兩個參數,context和組件名(Component),調用了startService()方法後,會在當前的應用中啓動DownloadService這個服務。
顯示Intent中設置的組件名(Component)須要在AndroidManifest.xml進行註冊,因此它通常用來啓動當前應用內的組件。

6. 隱式Intent示例

       隱式Intent比顯示的Intent會複雜一些,它既能夠啓動當前應用內的組件,也能夠啓動當前應用外的組件。若是當前應用沒法處理隱式Intent,可是其餘應用中的組件能夠處理,那麼系統會彈框讓用戶選擇啓動哪一個應用中的組件。

       例如,若是用戶有內容想分享給其餘應用,就建立一個Intent,將它的Action屬性設置爲ACTION_SEND,而後將要分享的內容設置到Extras屬性中,而後調用startActivity()方法,用戶就能夠選擇將內容分享到哪個應用。

注意,若是沒有任何應用能處理用戶發送的隱形Intent,調用組件失敗,應用可能會崩潰。調用resolveActivity()方法能夠確認是否有Activity可以處理這個Intent,若是返回爲非空,那麼至少有一個組件可以處理這個Intent,調用startActivity()就很安全了;若是返回的是空(null),那麼說明沒有組件可以處理這個Intent,這個時候就不該該使用這個隱式的Intent了。

// 要將textMessage信息分享出去
Intent sendIntent = new Intent();
sendIntent.setAction(Intent.ACTION_SEND);
sendIntent.putExtra(Intent.EXTRA_TEXT, textMessage);
sendIntent.setType("text/plain");

// 確認是否有組件可以處理這個隱式Intent
if (sendIntent.resolveActivity(getPackageManager()) != null) {
    startActivity(sendIntent);
}

       調用startActivity()傳入一個隱式Intent時候,系統會檢查設備中全部的應用,肯定哪些應用能夠處理這個隱式的Intent(含有startActivity()操做並攜帶text/plain類型的Intent),若是隻有一個應用能夠處理這個Intent,那麼直接喚起這個應用,並將Intent傳給它;若是有多個應用能夠處理這個Intent,那麼系統會彈出一個選擇框,讓用戶選擇喚起哪一個應用。

7. 強制喚起選擇框

       上文說了,若是多個應用能夠處理同一個隱式Intent,系統會彈出選擇框,讓用戶選擇喚起哪一個應用,並設置該應用爲默認的打開方式,之後就不會彈出選擇框了。若是用戶但願之後一直使用該用戶處理這個隱式Intent(好比打開網頁,用戶一般會傾向於使用同一個web瀏覽器),那麼十分方便。

       可是若是用戶想每一次都用不一樣的應用去處理這個隱式的Intent的,就應該每次彈出選擇框,用戶能夠在選擇框中選擇喚起的應用,可是沒法設置默認的打開方式。例如,當用戶想根據當前的位置將內容分享到不一樣的應用,因此每次都須要彈出選擇框。

用戶須要經過Intent.createChooser()建立一個Intent,而後調用startActivity()

Intent sendIntent = new Intent(Intent.ACTION_SEND);
...

// 分享的標題
String title = getResources().getString(R.string.chooser_title);
// 建立一個調用選擇框的Intent
Intent chooser = Intent.createChooser(sendIntent, title);

// 確認是否有應用能夠處理這個Intent
if (sendIntent.resolveActivity(getPackageManager()) != null) {
    startActivity(chooser);
}

8. 接受隱式Intent

       想配置你的應用能夠處理哪些隱式的Intent,須要在AndroidManifest.xml文件中使用<intent-filter>標籤爲組件設置一個或者多個過濾器。每個過濾器基於ActionDataCategory來指定自身能夠處理的Intent類型。若是隱式Intent的可以匹配到用戶設置的其中一個過濾器,系統才能喚起這個應用相應的組件並將Intent傳遞給這個組件。

       組件應該爲爲一個它能夠處理的操做單獨設置一個處理器。例如,相冊中的Activity可能有兩個過濾器,一個過濾器對應瀏覽照片的操做,另外一個過濾器對應編輯照片的操做。當這個Activity被啓動的時候,根據Intent中攜帶的信息來決定執行哪一種操做。

       每個過濾器是在AndroidManifest.xml使用<intent-filter>標籤來定義的,嵌套在組件標籤中,例如<activity><service>標籤。在<intent-filter>標籤中,用戶可使用一下三個屬性中的一個或者多個來指定能夠接受的Intent。

  • <action>,在這個屬性中,聲明該組件能夠執行的操做。該值必須是關於操做的一個字符串,並非類常量
  • <data>,使用一個或者多個數據URI(scheme、host、port、path等等)和數據的MIME類型來指定接受的數據類型
  • <category>,聲明接受的Intent類型

       Activity組件要接受隱式Intent,它必須有一個<category>屬性爲CATEGORY_DEFAULT的過濾器,由於startActivity()和startActivityForResult()方法處理Intent時候,默認的認爲接受組件有一個<category>屬性爲CATEGORY_DEFAULT的過濾器。若是一個Activity組件不聲明這樣一個過濾器,它就接收不到隱式Intent。

例如,如下代碼聲明瞭一個Activity組件,這個組件能夠處理action屬性爲ACTION_SEND,數據類型是文本(text/plain)的隱式Intent。

<activity android:name="ShareActivity">
    <intent-filter>
        <action android:name="android.intent.action.SEND"/>
        <category android:name="android.intent.category.DEFAULT"/>
        <data android:mimeType="text/plain"/>
    </intent-filter>
</activity>

用戶也能夠建立一個包含多個<action><data><category>標籤的過濾器,建立時候僅須要肯定該組件可以處理過濾器定義的操做便可。

若是是根據<action><data><category>標籤的組合來處理多個Intent,那麼須要爲這個組件聲明多個過濾器。

系統會以這三個屬性將隱式Intent與全部組件聲明的過濾器進行對比,若是這三個屬性所有可以匹配上,系統纔有可能將這個隱式Intent傳遞給這個組件,由於若是多個應用的組件都能匹配上會彈出選擇框,讓用戶選擇一個應用去處理這個隱式Intent。

爲了不無心中啓動了其餘的Service,因此在應用內,建議一直使用顯示的Intent去啓動服務,這樣就沒必要再AndroidManifest.xml文件中爲Service聲明過濾器了。

對於Activity的過濾器,必須在AndroidManifest.xml文件中聲明,也能夠不聲明,直接使用顯示Intent喚起Activity組件。

廣播接收器的過濾器聲明能夠在AndroidManifest.xml文件中聲明,也可使用registerReceiver()方法動態註冊,使用完畢後,使用unregisterReceiver()方法動態註銷。

9. 過濾器聲明示例

下面一些過濾器的聲明可以幫助你更好的理解。

<activity android:name="MainActivity">
    <!-- 該Activity是該應用的啓動入口頁面,它會被儲存在系統的launcher列表中 -->
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
</activity>

<activity android:name="ShareActivity">
    <!-- 該Activity可以處理ACTION_SEND行爲且數據類型爲text/plain的隱式Intent -->
    <intent-filter>
        <action android:name="android.intent.action.SEND"/>
        <category android:name="android.intent.category.DEFAULT"/>
        <data android:mimeType="text/plain"/>
    </intent-filter>
    <!-- 該Activity可以處理ACTION_SEND行爲且數據類型是媒體內容的隱式Intent -->
    <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="image/*"/>
        <data android:mimeType="video/*"/>
    </intent-filter>
</activity>

第一個名爲MainActivity的組件,是應用的啓動入口頁面,當用戶點擊應用圖標,該Activity會被啓動。

  • android.intent.action.MAIN,表示該Activity是應用的啓動入口,且不須要任何Intent攜帶的數據。
  • android.intent.category.LAUNCHER,表示將該Activity的圖標設爲手機主屏幕上的應用圖標,若是它沒有圖標,就用Application的圖標。

第二個名爲ShareActivity的組件,可以處理兩種隱式Intent,能夠接受文本和媒體內容的分享操做,也就是說若是一個隱式Intent可以匹配到任意一個過濾器均可以喚起該Activity。固然,也能夠直接經過顯示Intent指定啓動它。

9. PendingIntent

PendingIntent是對Intent的一種封裝。它主要做用在於,讓外部的應用執行內部的Intent時候,就好像是在你的應用中還行同樣。

一般在如下場景中會使用PendingIntent。

  • 當用戶點擊通知欄時候,才執行的Intent(系統的NotificationManager執行的Intent),詳情參考這裏
  • 當用戶操做懸浮在主屏幕中的小工具,才執行的Intent(主屏幕應用執行的Intent),詳情參考這裏
  • 在將來某一個特定時間執行的Intent(系統的AlarmManager執行的Intent)

由於每個Intent對象都是針對具體的組件類別(Activity/Service/BroadcastReceiver)進行實例化,所以在建立PendingIntent的時候,也要基於相同的因素去實例化,使用如下方法實例化PendingIntent。

  • PendingIntent.getActivity(),返回一個適用於Activity組件的PendingIntent
  • PendingIntent.getService(),返回一個適用於Service組件的PendingIntent
  • PendingIntent.getBroadcast(),返回一個適用於BroadcastReceiver的PendingIntent

固然官方還有一些其餘獲取PendingIntent對象的方法,不過內部也是使用上面三個方法來獲取實例化對象的。

這三個方法都須要當前應用的context,須要封裝的Intent,以及一個或者多個該如何使用該Intent的標誌(例如,是否能夠屢次使用該Intent)。

關於Pending的具體使用也再也不這裏展開,須要瞭解具體使用的能夠查看Notification中PendingIntent的使用懸浮工具欄中PendingIntent的使用

10. Intent匹配規則

       上文中提到了,當發送一個隱式Intent後,系統會將它與設備中的每個組件的過濾器進行匹配,匹配屬性有ActionCategoryData三個,須要這三個屬性都匹配成功才能喚起相應的組件。

10.1 Action匹配規則

一個過濾器能夠不聲明Action屬性也能夠聲明多個Action屬性。以下:

<intent-filter>
    <action android:name="android.intent.action.EDIT" />
    <action android:name="android.intent.action.VIEW" />
    ...
</intent-filter>

隱式Intent中的Action屬性,與組件中的某一個過濾器的Action可以匹配(若是一個過濾器聲明瞭多個Action屬性,只須要匹配其中一個就行),那麼就算是匹配成功。

若是過濾器沒有聲明Action屬性,那麼只有沒有設置Action屬性的隱式Intent才能匹配成功。

10.2 Category匹配規則

一個過濾器能夠不聲明Category屬性也能夠聲明多個Category屬性,以下:

<intent-filter>
    <category android:name="android.intent.category.DEFAULT" />
    <category android:name="android.intent.category.BROWSABLE" />
    ...
</intent-filter>

       隱式Intent中聲明的Category必須所有可以與某一個過濾器中的Category匹配纔算匹配成功。好比說一個Category屬性設爲CATEGORY_BROWSABLE的隱式Intent也能夠經過上面的過濾器,也就是說,過濾器的Category屬性內容必須是大於或者等於隱式Intent的Category屬性時候,隱式Intent才能匹配成功。

若是一個隱式Intent沒有設置Category屬性,那麼它能夠經過任何一個過濾器的Category匹配。

10.3 Data匹配規則

一個過濾器能夠不聲明Data屬性也能夠聲明多個Data屬性,以下:

<intent-filter>
    <data android:mimeType="video/mpeg" android:scheme="http" ... />
    <data android:mimeType="audio/mpeg" android:scheme="http" ... />
    ...
</intent-filter>

每一個Data屬性均可以指定數據的URI結構和數據MIME類型。URI包括scheme、host、port 和path四個部分,host和port合起來也成authority(host:port)部分。

<scheme>://<host>:<port>/<path>

例如:

content://192.168.0.1:8080/folder/subfolder/etc

在這個URI中,scheme是content,host是192.168.0.1,port是8080,path是folder/subfolder/etc。咱們平時使用的網絡url就是這種格式。

在URI中,每一個組成部分都是可選的,可是有線性的依賴關係

  • 若是沒有scheme部分,那麼host部分會被忽略
  • 若是沒有host部分,那麼port部分會被忽略
  • 若是host部分和port部分都沒有,那麼path部分會被忽略

當進行URI匹配時候,並非比較所有,而是局部對比,如下是URI匹配規則。

  • 若是一個URI僅聲明瞭scheme部分,那麼全部擁有與其相同的scheme的URI都會經過匹配,其餘部分不作匹配
  • 若是一個URI聲明瞭scheme部分和authority部分,那麼擁有與其相同schemeauthority的URI才能匹配成功,path部分不作匹配
  • 若是一個URI全部的部分都聲明瞭,那麼只有全部部分都相同的URI才能匹配成功

注意:path部分可使用通配符(*),也就是path其中的一部分進行匹配。

Data匹配時候,MIME類型和URI二者都會進行匹配,匹配規則以下:

  • 若是過濾器未聲明URI和MIME類型,則只有不含URI和MIME類型的隱形Intent才能匹配成功
  • 若是過濾器中聲明URI可是未聲明MIME類型(也不能從URI中分析出MIME類型),則只有URI與過濾器URI相同且不包含IME類型的隱式Intent才能匹配成功
  • 若是過濾器聲明MIME類型可是未聲明URI,只有包含相同MIME類型可是不包含URI的隱式Intent才能匹配成功
  • 若是過濾器聲明瞭URI和MIME類型(既能夠是直接設置,也能夠是從URI分析出來),只有包含相同的URI和MIME類型的隱式Intent才能匹配成功

注意:進行匹配時候必須以過濾器爲單位進行匹配,不能跨過濾器匹配。若是一個過濾器聲明瞭多個ActionCategoryData,隱式Intent包含的ActionCategoryData都能在過濾器中匹配到相應的屬性便可,也就是說過濾器中聲明的屬性是大於或者等於Intent中包含的屬性,Intent才能匹配成功

11. 其餘

       系統經過過濾器去匹配Intent,啓動相應組件,在PackageManager類中提供了一系列的查詢(queryIntentActivities()/queryIntentServices()/queryBroadcastReceivers())方法去查詢能夠處理某個Intent的組件,也提供了一系列的解析(resolveActivity()/resolveService())方法來肯定最佳啓動組件。這些方法在某些場景下是很是有用的,也能夠幫助咱們下降程序crash風險。

12. 思考

       上文中對Intent以及IntentFilter進行了詳細的講解,大多都是系統級別的處理過程。可是Intent做爲一個官方已經封裝好的信息攜帶者,咱們能夠用它來作不少事情。好比能夠寫本身的一套匹配規則,Intent僅僅做爲數據攜帶者,經過它去傳遞一些信息,實現Fragment/Activity的頁面跳轉邏輯。關於Intent的使用,我相信還有更多的用處,須要用戶一步一步的去探索。

相關文章
相關標籤/搜索