Android -- Intent 和 Intent 過濾器
Intent 是一個消息傳遞對象,您可以使用它從其他應用組件請求操作。儘管 Intent 可以通過多種方式促進組件之間的通信,但其基本用例主要包括以下三個:
- 啓動 Activity:
Activity 表示應用中的一個屏幕。通過將 Intent 傳遞給 startActivity(),您可以啓動新的 Activity 實例。Intent 描述了要啓動的 Activity,並攜帶了任何必要的數據。
如果您希望在 Activity 完成後收到結果,請調用 startActivityForResult()。在 Activity 的 onActivityResult() 回調中,您的 Activity 將結果作爲單獨的 Intent 對象接收。 - 啓動服務:
Service 是一個不使用用戶界面而在後臺執行操作的組件。通過將 Intent 傳遞給 startService(),您可以啓動服務執行一次性操作(例如,下載文件)。Intent 描述了要啓動的服務,並攜帶了任何必要的數據。
如果服務旨在使用客戶端-服務器接口,則通過將 Intent 傳遞給 bindService(),您可以從其他組件綁定到此服務。 - 傳遞廣播:
廣播是任何應用均可接收的消息。系統將針對系統事件(例如:系統啓動或設備開始充電時)傳遞各種廣播。通過將 Intent 傳遞給 sendBroadcast()、sendOrderedBroadcast() 或 sendStickyBroadcast(),您可以將廣播傳遞給其他應用。
Intent 類型
- 顯式 Intent:按名稱(完全限定類名)指定要啓動的組件。 通常,您會在自己的應用中使用顯式 Intent 來啓動組件,這是因爲您知道要啓動的 Activity 或服務的類名。例如,啓動新 Activity 以響應用戶操作,或者啓動服務以在後臺下載文件。
- 隱式 Intent :不會指定特定的組件,而是聲明要執行的常規操作,從而允許其他應用中的組件來處理它。 例如,如需在地圖上向用戶顯示位置,則可以使用隱式 Intent,請求另一具有此功能的應用在地圖上顯示指定的位置。
創建隱式 Intent 時,Android 系統通過將 Intent 的內容與在設備上其他應用的清單文件中聲明的 Intent 過濾器進行比較,從而找到要啓動的相應組件。 如果 Intent 與 Intent 過濾器匹配,則系統將啓動該組件,並向其傳遞 Intent 對象。 如果多個 Intent 過濾器兼容,則系統會顯示一個對話框,支持用戶選取要使用的應用。
Intent 過濾器是應用清單文件中的一個表達式,它指定該組件要接收的 Intent 類型。 例如,通過爲 Activity 聲明 Intent 過濾器,您可以使其他應用能夠直接使用某一特定類型的 Intent 啓動 Activity。同樣,如果您沒有爲 Activity 聲明任何 Intent 過濾器,則 Activity 只能通過顯式 Intent 啓動。
構建 Intent
Intent 中包含的主要信息如下:
這是可選項,但也是構建顯式 Intent 的一項重要信息,這意味着 Intent 應當僅傳遞給由組件名稱定義的應用組件。 如果沒有組件名稱,則 Intent 是隱式的,且系統將根據其他 Intent 信息(例如,以下所述的操作、數據和類別)決定哪個組件應當接收 Intent。 因此,如需在應用中啓動特定的組件,則應指定該組件的名稱。
注:啓動 Service 時,您應始終指定組件名稱。 否則,您無法確定哪項服務會響應 Intent,且用戶無法看到哪項服務已啓動。
Intent 的這一字段是一個 ComponentName 對象,您可以使用目標組件的完全限定類名指定此對象,其中包括應用的軟件包名稱。 例如, com.example.ExampleActivity。您可以使用 setComponent()、setClass()、setClassName() 或 Intent 構造函數設置組件名稱。
指定要執行的通用操作(例如,“查看”或“選取”)的字符串。
您可以指定自己的操作,供 Intent 在您的應用內使用(或者供其他應用在您的應用中調用組件)。但是,您通常應該使用由 Intent 類或其他框架類定義的操作常量。以下是一些用於啓動 Activity 的常見操作:
ACTION_VIEW
如果您擁有一些某項 Activity 可向用戶顯示的信息(例如,要使用圖庫應用查看的照片;或者要使用地圖應用查看的地址),請使用 Intent 將此操作與 startActivity() 結合使用。
ACTION_SEND
這也稱爲“共享”Intent。如果您擁有一些用戶可通過其他應用(例如,電子郵件應用或社交共享應用)共享的數據,則應使用 Intent 將此操作與 startActivity() 結合使用。
有關更多定義通用操作的常量,請參閱 Intent 類參考文檔。其他操作在 Android 框架中的其他位置定義。例如,對於在系統的設置應用中打開特定屏幕的操作,將在 Settings 類中定義。
您可以使用 setAction() 或 Intent 構造函數爲 Intent 指定操作。
如果定義自己的操作,請確保將應用的軟件包名稱作爲前綴。 例如:
static final String ACTION_TIMETRAVEL = "com.example.action.TIMETRAVEL";
數據
引用待操作數據和/或該數據 MIME 類型的 URI(Uri 對象)。提供的數據類型通常由 Intent 的操作決定。例如,如果操作是 ACTION_EDIT,則數據應包含待編輯文檔的 URI。
創建 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 類型。
一個包含應處理 Intent 組件類型的附加信息的字符串。 您可以將任意數量的類別描述放入一個 Intent 中,但大多數 Intent 均不需要類別。 以下是一些常見類別:
CATEGORY_BROWSABLE
目標 Activity 允許本身通過網絡瀏覽器啓動,以顯示鏈接引用的數據,如圖像或電子郵件。
CATEGORY_LAUNCHER
該 Activity 是任務的初始 Activity,在系統的應用啓動器中列出。
有關類別的完整列表,請參閱 Intent 類描述。
您可以使用 addCategory() 指定類別。
以上列出的這些屬性(組件名稱、操作、數據和類別)表示 Intent 的既定特徵。 通過讀取這些屬性,Android 系統能夠解析應當啓動哪個應用組件。
但是,Intent 也有可能會一些攜帶不影響其如何解析爲應用組件的信息。 Intent 還可以提供:
攜帶完成請求操作所需的附加信息的鍵值對。正如某些操作使用特定類型的數據 URI 一樣,有些操作也使用特定的 extra。
例如,使用 ACTION_SEND 創建用於發送電子郵件的 Intent 時,可以使用 EXTRA_EMAIL 鍵指定“目標”收件人,並使用 EXTRA_SUBJECT 鍵指定“主題”。
Intent 類將爲標準化的數據類型指定多個 EXTRA_* 常量。如需聲明自己的 extra 鍵(對於應用接收的 Intent),請確保將應用的軟件包名稱作爲前綴。 例如:
static final String EXTRA_GIGAWATTS = "com.example.EXTRA_GIGAWATTS";
標誌
在 Intent 類中定義的、充當 Intent 元數據的標誌。 標誌可以指示 Android 系統如何啓動 Activity(例如,Activity 應屬於哪個任務),以及啓動之後如何處理(例如,它是否屬於最近的 Activity 列表)。
如需瞭解詳細信息,請參閱Intent.setFlags() 方法。
顯式 Intent 示例
例如,如果在應用中構建了一個名爲 DownloadService、旨在從網頁下載文件的服務,則可使用以下代碼啓動該服務:
// Executed in an Activity, so 'this' is the Context
// The fileUrl is a string URL, such as "http://www.example.com/image.png"
Intent downloadIntent = new Intent(this, DownloadService.class);
downloadIntent.setData(Uri.parse(fileUrl));
startService(downloadIntent);
Intent(Context, Class) 構造函數分別爲應用和組件提供 Context 和 Class 對象。因此,此 Intent 將顯式啓動該應用中的 DownloadService 類。隱式 Intent 示例
例如,如果您希望用戶與他人共享您的內容,請使用 ACTION_SEND 操作創建 Intent,並添加指定共享內容的 extra。 使用該 Intent 調用 startActivity() 時,用戶可以選取共享內容所使用的應用。
注意:用戶可能沒有任何應用處理您發送到 startActivity() 的隱式 Intent。如果出現這種情況,則調用將會失敗,且應用會崩潰。要驗證 Activity 是否會接收 Intent,請對 Intent 對象調用 resolveActivity()。如果結果爲非空,則至少有一個應用能夠處理該 Intent,且可以安全調用 startActivity()。 如果結果爲空,則不應使用該 Intent。如有可能,您應停用發出該 Intent 的功能。
// Create the text message with a string
Intent sendIntent = new Intent();
sendIntent.setAction(Intent.ACTION_SEND);
sendIntent.putExtra(Intent.EXTRA_TEXT, textMessage);
sendIntent.setType("text/plain");
// Verify that the intent will resolve to an activity
if (sendIntent.resolveActivity(getPackageManager()) != null) {
startActivity(sendIntent);
}
注:在這種情況下,系統並沒有使用 URI,但已聲明 Intent 的數據類型,用於指定 extra 攜帶的內容。調用 startActivity() 時,系統將檢查已安裝的所有應用,確定哪些應用能夠處理這種 Intent(即:含 ACTION_SEND 操作並攜帶“text/plain”數據的 Intent )。 如果只有一個應用能夠處理,則該應用將立即打開併爲其提供 Intent。 如果多個 Activity 接受 Intent,則系統將顯示一個對話框,使用戶能夠選取要使用的應用。
強制使用應用選擇器
但是,如果多個應用可以響應 Intent,且用戶可能希望每次使用不同的應用,則應採用顯式方式顯示選擇器對話框。 選擇器對話框每次都會要求用戶選擇用於操作的應用(用戶無法爲該操作選擇默認應用)。 例如,當應用使用 ACTION_SEND 操作執行“共享”時,用戶根據目前的狀況可能需要使用另一不同的應用,因此應當始終使用選擇器對話框,如圖 2 中所示。
要顯示選擇器,請使用 createChooser() 創建 Intent,並將其傳遞給 startActivity()。例如:
Intent sendIntent = new Intent(Intent.ACTION_SEND);
...
// Always use string resources for UI text.
// This says something like "Share this photo with"
String title = getResources().getString(R.string.chooser_title);
// Create intent to show the chooser dialog
Intent chooser = Intent.createChooser(sendIntent, title);
// Verify the original intent will resolve to at least one activity
if (sendIntent.resolveActivity(getPackageManager()) != null) {
startActivity(chooser);
}
這將顯示一個對話框,其中有響應傳遞給 createChooser() 方法的 Intent 的應用列表,並且將提供的文本用作對話框標題。接收隱式 Intent
注:顯式 Intent 始終會傳遞給其目標,無論組件聲明的 Intent 過濾器如何均是如此。
應用組件應當爲自身可執行的每個獨特作業聲明單獨的過濾器。例如,圖像庫應用中的一個 Activity 可能會有兩個過濾器,分別用於查看圖像和編輯圖像。 當 Activity 啓動時,它將檢查 Intent 並根據 Intent 中的信息決定具體的行爲(例如,是否顯示編輯器控件)。
每個 Intent 過濾器均由應用清單文件中的 <intent-filter> 元素定義,並嵌套在相應的應用組件(例如,<activity> 元素)中。 在 <intent-filter> 內部,您可以使用以下三個元素中的一個或多個指定要接受的 Intent 類型:
<action>
在 name 屬性中,聲明接受的 Intent 操作。該值必須是操作的文本字符串值,而不是類常量。
<data>
使用一個或多個指定數據 URI 各個方面(scheme、host、port、path 等)和 MIME 類型的屬性,聲明接受的數據類型。
<category>
在 name 屬性中,聲明接受的 Intent 類別。該值必須是操作的文本字符串值,而不是類常量。
注:爲了接收隱式 Intent,必須將 CATEGORY_DEFAULT 類別包括在 Intent 過濾器中。 方法 startActivity() 和 startActivityForResult() 將按照已申明 CATEGORY_DEFAULT 類別的方式處理所有 Intent。 如果未在 Intent 過濾器中聲明此類別,則隱式 Intent 不會解析爲您的 Activity。
例如,以下是一個使用包含 Intent 過濾器的 Activity 聲明,當數據類型爲文本時,系統將接收 ACTION_SEND 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> 實例的過濾器。創建時,僅需確定組件能夠處理這些過濾器元素的任何及所有組合即可。如需僅以操作、數據和類別類型的特定組合來處理多種 Intent,則需創建多個 Intent 過濾器。
限制對組件的訪問
使用 Intent 過濾器時,無法安全地防止其他應用啓動組件。 儘管 Intent 過濾器將組件限制爲僅響應特定類型的隱式 Intent,但如果開發者確定您的組件名稱,則其他應用有可能通過使用顯式 Intent 啓動您的應用組件。如果必須確保只有您自己的應用才能啓動您的某一組件,請針對該組件將exported屬性設置爲 "false"。
系統通過將 Intent 與所有這三個元素進行比較,根據過濾器測試隱式 Intent。 隱式 Intent 若要傳遞給組件,必須通過所有這三項測試。如果 Intent 甚至無法匹配其中任何一項測試,則 Android 系統不會將其傳遞給組件。 但是,由於一個組件可能有多個 Intent 過濾器,因此未能通過某一組件過濾器的 Intent 可能會通過另一過濾器。如需瞭解有關係統如何解析 Intent 的詳細信息,請參閱下文的 Intent 解析部分。
注意:爲了避免無意中運行不同應用的 Service,請始終使用顯式 Intent 啓動您自己的服務,且不必爲該服務聲明 Intent 過濾器。
注:對於所有 Activity,您必須在清單文件中聲明 Intent 過濾器。但是,廣播接收器的過濾器可以通過調用 registerReceiver() 動態註冊。 稍後,您可以使用 unregisterReceiver() 註銷該接收器。這樣一來,應用便可僅在應用運行時的某一指定時間段內偵聽特定的廣播。
過濾器示例
<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>
第一個 Activity MainActivity 是應用的主要入口點。當用戶最初使用啓動器圖標啓動應用時,該 Activity 將打開:- ACTION_MAIN 操作指示這是主要入口點,且不要求輸入任何 Intent 數據。
- CATEGORY_LAUNCHER 類別指示此 Activity 的圖標應放入系統的應用啓動器。 如果 <activity> 元素未使用 icon 指定圖標,則系統將使用 <application> 元素中的圖標。
第二個 Activity ShareActivity 旨在便於共享文本和媒體內容。 儘管用戶可以通過從 MainActivity 導航進入此 Activity,但也可以從發出隱式 Intent(與兩個 Intent 過濾器之一匹配)的另一應用中直接進入 ShareActivity。
注:MIME 類型 application/vnd.google.panorama360+jpg 是一個指定全景照片的特殊數據類型,您可以使用 Google panorama API 對其進行處理。
使用待定 Intent
PendingIntent 對象是 Intent 對象的包裝器。PendingIntent 的主要目的是授權外部應用使用包含的 Intent,就像是它從您應用本身的進程中執行的一樣。
待定 Intent 的主要用例包括:
- 聲明用戶使用您的通知執行操作時所要執行的 Intent(Android 系統的 NotificationManager 執行 Intent)。
- 聲明用戶使用您的 應用小部件執行操作時要執行的 Intent(主屏幕應用執行 Intent)。
- 聲明未來某一特定時間要執行的 Intent(Android 系統的 AlarmManager 執行 Intent)。
- PendingIntent.getActivity(),適用於啓動 Activity 的 Intent。
- PendingIntent.getService(),適用於啓動 Service 的 Intent。
- PendingIntent.getBroadcast(),適用於啓動 BroadcastReceiver 的 Intent。
每種方法均會提取當前的應用 Context、您要包裝的 Intent 以及一個或多個指定應如何使用該 Intent 的標誌(例如,是否可以多次使用該 Intent)。
Intent 解析
- Intent 操作
- Intent 數據(URI 和數據類型)
- Intent 類別
操作測試
<intent-filter>
<action android:name="android.intent.action.EDIT" />
<action android:name="android.intent.action.VIEW" />
...
</intent-filter>
要通過此過濾器,您在 Intent 中指定的操作必須與過濾器中列出的某一操作匹配。如果該過濾器未列出任何操作,則 Intent 沒有任何匹配項,因此所有 Intent 均無法通過測試。 但是,如果 Intent 未指定操作,則會通過測試(只要過濾器至少包含一個操作)。
類別測試
<intent-filter>
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
...
</intent-filter>
若要使 Intent 通過類別測試,則 Intent 中的每個類別均必須與過濾器中的類別匹配。反之則未必然,Intent 過濾器聲明的類別可以超出 Intent 中指定的數量,且 Intent 仍會通過測試。 因此,不含類別的 Intent 應當始終會通過此測試,無論過濾器中聲明何種類別均是如此。注:Android 會自動將 CATEGORY_DEFAULT 類別應用於傳遞給 startActivity() 和 startActivityForResult() 的所有隱式 Intent。因此,如需 Activity 接收隱式 Intent,則必須將 "android.intent.category.DEFAULT" 的類別包括在其 Intent 過濾器中(如上文的 <intent-filter> 示例所示)。
數據測試
<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 屬性:<scheme>://<host>:<port>/<path>
例如:
content://com.example.project:200/folder/subfolder/etc
在此 URI 中,架構是 content,主機是 com.example.project,端口是 200,路徑是 folder/subfolder/etc。
在 <data> 元素中,上述每個屬性均爲可選,但存在線性依賴關係:
- 如果未指定架構,則會忽略主機。
- 如果未指定主機,則會忽略端口。
- 如果未指定架構和主機,則會忽略路徑。
- 如果過濾器僅指定架構,則具有該架構的所有 URI 均與該過濾器匹配。
- 如果過濾器指定架構和權限,但未指定路徑,則具有相同架構和權限的所有 URI 都會通過過濾器,無論其路徑如何均是如此。
- 如果過濾器指定架構、權限和路徑,則僅具有相同架構、權限和路徑的 URI 纔會通過過濾器。
數據測試會將 Intent 中的 URI 和 MIME 類型與過濾器中指定的 URI 和 MIME 類型進行比較。 規則如下:
- 僅當過濾器未指定任何 URI 或 MIME 類型時,不含 URI 和 MIME 類型的 Intent 纔會通過測試。
- 對於包含 URI 但不含 MIME 類型(既未顯式聲明,也無法通過 URI 推斷得出)的 Intent,僅當其 URI 與過濾器的 URI 格式匹配、且過濾器同樣未指定 MIME 類型時,纔會通過測試。
- 僅當過濾器列出相同的 MIME 類型且未指定 URI 格式時,包含 MIME 類型、但不含 URI 的 Intent 纔會通過測試。
- 僅當 MIME 類型與過濾器中列出的類型匹配時,同時包含 URI 類型和 MIME 類型(通過顯式聲明,或可以通過 URI 推斷得出)的 Intent 纔會通過測試的 MIME 類型部分。 如果 Intent 的 URI 與過濾器中的 URI 匹配,或者如果 Intent 具有 content: 或 file: URI 且過濾器未指定 URI,則 Intent 會通過測試的 URI 部分。 換言之,如果過濾器只是列出 MIME 類型,則假定組件支持 content: 和 file: 數據。
<intent-filter>
<data android:mimeType="image/*" />
...
</intent-filter>
由於大部分可用數據均由內容提供商分發,因此指定數據類型(而非 URI)的過濾器也許最爲常見。另一常見的配置是具有架構和數據類型的過濾器。例如,下文中的 <data> 元素向 Android 指出,組件可從網絡中檢索視頻數據以執行操作:
<intent-filter>
<data android:scheme="http" android:type="video/*" />
...
</intent-filter>
Intent 匹配
您的應用可以採用類似的方式使用 Intent 匹配。PackageManager 提供了一整套 query...() 方法來返回所有能夠接受特定 Intent 的組件。此外,它還提供了一系列類似的 resolve...() 方法來確定響應 Intent 的最佳組件。 例如,queryIntentActivities() 將返回能夠執行那些作爲參數傳遞的 Intent 的所有 Activity 列表,而 queryIntentServices() 則可返回類似的服務列表。這兩種方法均不會激活組件,而只是列出能夠響應的組件。 對於廣播接收器,有一種類似的方法: queryBroadcastReceivers()。