Intent
是訊息物件,可用於要求其他應用程式元件的動作。雖然意圖可透過多種方式促進元件之間的通訊,但有三種基本用途:
- 開始活動
Activity
代表應用程式中的單一畫面。您可以將Intent
傳遞至startActivity()
,來啟動Activity
的新例項。Intent
會描述要啟動並傳送任何必要資料的活動。如果您想在活動結束時收到來自活動的結果,請呼叫
startActivityForResult()
。您的活動會在活動的onActivityResult()
回呼中以個別的Intent
物件接收結果。詳情請參閱「活動」指南。 - 啟動服務
Service
元件可在沒有使用者介面的情況下,在背景執行作業。在 Android 5.0 (API 級別 21) 以上版本中,您可以使用JobScheduler
啟動服務。如要進一步瞭解JobScheduler
,請參閱其API-reference documentation
。如果是 Android 5.0 (API 級別 21) 以下版本,您可以使用
Service
類別的方法啟動服務。您可以將Intent
傳遞至startService()
,以啟動服務來執行一次性作業 (例如下載檔案)。Intent
會描述用來啟動及傳送任何必要資料的服務。如果服務是以用戶端伺服器介面設計,您可以將
Intent
傳遞至bindService()
,藉此從其他元件繫結至該服務。詳情請參閱服務指南。 - 傳送廣播
廣播訊息是任何應用程式可接收的訊息。系統會針對系統事件傳送各種廣播訊息,例如系統啟動或裝置開始充電時。您可以將
Intent
傳遞至sendBroadcast()
或sendOrderedBroadcast()
,將廣播訊息傳送至其他應用程式。
本頁面的其餘部分會說明意圖的運作方式及使用方式。如需相關資訊,請參閱「與其他應用程式互動」和「分享內容」。
意圖類型
意圖分為兩種類型:
- 「明確意圖」會指定完整的
ComponentName
,用於指定哪個應用程式會滿足意圖。您通常會使用明確意圖,在自己的應用程式中啟動元件,因為您知道要啟動活動或服務的類別名稱。舉例來說,您可以因應使用者動作,在應用程式中啟動新活動,或啟動服務在背景下載檔案。 - 隱含意圖:不命名特定元件,而是宣告要執行的一般動作,讓其他應用程式的元件可以處理該動作。舉例來說,如果您想在地圖上向使用者顯示某個位置,可以使用隱含意圖要求其他有能力的應用程式在地圖上顯示指定位置。
圖 1 顯示啟動活動時使用的意圖。當 Intent
物件明確命名特定活動元件時,系統會立即啟動該元件。
使用隱含意圖時,Android 系統會找出適當的元件,先比對意圖內容與裝置上其他應用程式資訊清單檔案中宣告的意圖篩選器。如果意圖與意圖篩選器相符,系統會啟動該元件,並將 Intent
物件傳送至該元件。如果有多個意圖篩選器相容,系統會顯示對話方塊,讓使用者選擇要使用的應用程式。
意圖篩選器是應用程式資訊清單檔案中的運算式,用於指定元件要接收的意圖類型。舉例來說,只要為活動宣告意圖篩選器,其他應用程式就能直接以特定類型的意圖啟動活動。同樣地,如果您「沒有」為活動宣告任何意圖篩選器,則只能在明確意圖下啟動該活動。
注意:為確保應用程式安全無虞,請一律在啟動 Service
時使用明確意圖,且不要為服務宣告意圖篩選器。使用隱含意圖啟動服務會危害安全性,因為您無法確定哪個服務會回應意圖,而使用者也無法查看哪個服務會啟動。從 Android 5.0 (API 級別 21) 開始,如果您使用隱含意圖呼叫 bindService()
,系統會擲回例外狀況。
建立意圖
Intent
物件包含 Android 系統用來判斷要啟動的元件的資訊 (例如應接收意圖的確切元件名稱或元件類別),以及接收者元件用來正確執行操作的資訊 (例如要採取的行動和要採取行動的資料)。
Intent
包含的主要資訊如下:
- 元件名稱
- 要啟動的元件名稱。
此為選用選項,但這是讓意圖明確傳達意圖的資訊,這表示意圖只能傳送至元件名稱定義的應用程式元件。如果沒有元件名稱,意圖就「隱含」,系統會根據其他意圖資訊 (例如動作、資料和類別,如下所述) 判斷應接收意圖的元件。如果您需要在應用程式中啟動特定元件,請指定元件名稱。
注意:啟動
Service
時,請一律指定元件名稱。否則,您無法確定哪個服務會回應意圖,而使用者也無法查看哪個服務啟動。Intent
的這個欄位是ComponentName
物件,您可以使用目標元件的完整類別名稱 (包括應用程式的套件名稱) 指定,例如com.example.ExampleActivity
。您可以使用setComponent()
、setClass()
、setClassName()
或Intent
建構函式設定元件名稱。 - 動作片
- 字串,用於指定要執行的一般動作 (例如 view 或 pick)。
如果是廣播意圖,這是指發生並系統回報的動作。這項動作主要會決定意圖的其餘部分,尤其是包含在資料和額外項目中的資訊。
您可以指定自己的動作,供應用程式中的意圖使用 (或供其他應用程式用於叫用應用程式中的元件),但通常必須指定
Intent
類別或其他架構類別定義的動作常數。以下是啟動活動的一些常見動作:ACTION_VIEW
- 如有活動可以向使用者顯示的某些資訊 (例如在圖片庫應用程式中查看的相片,或是可在地圖應用程式中查看的地址),請用
startActivity()
在意圖中加入這項動作。 ACTION_SEND
- 如果您有一些資料可讓使用者透過其他應用程式 (例如電子郵件應用程式或社群媒體分享應用程式) 分享,則應將這項意圖用於
startActivity()
的意圖,這也稱為「分享」意圖。
如要進一步瞭解定義一般動作的更多常數,請參閱
Intent
類別參考資料。其他動作是在 Android 架構的其他位置定義,例如在系統「設定」應用程式中開啟特定畫面的動作,是在Settings
中定義。您可以使用
setAction()
或Intent
建構函式指定意圖的動作。如果您希望自行定義動作,請務必加入應用程式的套件名稱做為前置字串,如以下範例所示:
Kotlin
const val ACTION_TIMETRAVEL = "com.example.action.TIMETRAVEL"
Java
static final String ACTION_TIMETRAVEL = "com.example.action.TIMETRAVEL";
- 資料
- 此 URI (
Uri
物件) 會參照要對資料採取行動的資料,及/或該資料的 MIME 類型。提供的資料類型通常是由意圖動作決定。舉例來說,如果動作是ACTION_EDIT
,資料中應包含要編輯的文件 URI。建立意圖時,除了 URI 以外,通常還必須指定資料類型 (其 MIME 類型)。舉例來說,能夠顯示圖片的活動可能無法播放音訊檔案,即使 URI 格式可能相似也一樣。指定資料的 MIME 類型有助於 Android 系統尋找接收意圖的最佳元件。不過,有時系統可以從 URI 推測 MIME 類型,尤其是當資料是
content:
URI 時。content:
URI 表示資料位於裝置上,並由ContentProvider
控制,因此系統可以看到資料 MIME 類型。如果只要設定資料 URI,請呼叫
setData()
。如果只要設定 MIME 類型,請呼叫setType()
。如有需要,您可以使用setDataAndType()
明確設定兩者。注意:如要同時設定 URI 和 MIME 類型,「請勿」呼叫
setData()
和setType()
,因為這兩者都會將另一個值設為空值。請一律使用setDataAndType()
來設定 URI 和 MIME 類型。 - 類別
- 此字串包含應處理意圖的元件種類額外資訊。您可以在意圖中加入不限數量的類別說明,但大多數意圖都不需要類別。常見的類別如下:
CATEGORY_BROWSABLE
- 目標活動可讓網路瀏覽器自行啟動,以顯示連結參照的資料,例如圖片或電子郵件。
CATEGORY_LAUNCHER
- 活動是任務的初始活動,會列在系統的應用程式啟動器中。
完整類別清單請參閱
Intent
類別說明。您可以使用
addCategory()
指定類別。
上述列出的屬性 (元件名稱、動作、資料和類別) 代表意圖的定義特性。讀取這些屬性後,Android 系統就能解析應啟動的應用程式元件。不過,意圖可提供額外資訊,而不會影響其解析應用程式元件的方式。意圖也可以提供下列資訊:
- 額外內容
- 提供完成要求動作所需的額外資訊的鍵/值組合。就像某些動作會使用特定種類的資料 URI,某些動作也會使用特定額外項目。
您可以使用不同的
putExtra()
方法新增額外資料,每個方法都可接受兩個參數:鍵名稱和值。您也可以使用所有額外資料建立Bundle
物件,然後使用putExtras()
將Bundle
插入Intent
。舉例來說,建立要用
ACTION_SEND
傳送電子郵件的意圖時,您可以使用EXTRA_EMAIL
金鑰指定 to 收件者,並使用EXTRA_SUBJECT
鍵指定 subject。Intent
類別會為標準化資料類型指定多個EXTRA_*
常數。如果您需要宣告自己的額外金鑰 (針對應用程式收到的意圖),請務必加入應用程式的套件名稱做為前置字串,如以下範例所示:Kotlin
const val EXTRA_GIGAWATTS = "com.example.EXTRA_GIGAWATTS"
Java
static final String EXTRA_GIGAWATTS = "com.example.EXTRA_GIGAWATTS";
注意:傳送預期其他應用程式會收到的意圖時,請勿使用
Parcelable
或Serializable
資料。如果應用程式嘗試存取Bundle
物件中的資料,但無法存取已封裝或序列化類別,系統會引發RuntimeException
。 - 旗標
- 標記是在
Intent
類別中定義,該類別會做為意圖的中繼資料。這些旗標可能會指示 Android 系統如何啟動活動 (例如活動應屬於哪個工作),以及活動在啟動後的處理方式 (例如是否屬於近期活動清單)。詳情請參閱
setFlags()
方法。
明確意圖範例
明確意圖是用來啟動特定應用程式元件的一種,例如應用程式中的特定活動或服務。如要建立明確意圖,請定義 Intent
物件的元件名稱,所有其他意圖屬性則為選用。
舉例來說,如果您在應用程式中建構名為 DownloadService
的服務,專門用於從網路下載檔案,則可以使用下列程式碼啟動服務:
Kotlin
// Executed in an Activity, so 'this' is theContext
// The fileUrl is a string URL, such as "http://www.example.com/image.png" val downloadIntent = Intent(this, DownloadService::class.java).apply { data =Uri.parse
(fileUrl) } startService(downloadIntent)
Java
// Executed in an Activity, so 'this' is theContext
// 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
物件。因此,這個意圖會明確在應用程式中啟動 DownloadService
類別。
如要進一步瞭解如何建構及啟動服務,請參閱服務指南。
隱含意圖範例
隱含意圖可指定的動作,能在裝置上叫用任何可執行動作的應用程式。如果應用程式無法執行這項動作,使用隱含意圖就十分實用,但其他應用程式或許也能,而您希望使用者挑選要使用的應用程式。
舉例來說,如果您希望使用者與他人分享內容,請使用 ACTION_SEND
動作建立意圖,然後新增其他用於指定要共用的內容。如果您使用該意圖呼叫 startActivity()
,使用者就能選擇要透過哪個應用程式分享內容。
Kotlin
// Create the text message with a string. val sendIntent = Intent().apply { action = Intent.ACTION_SEND putExtra(Intent.EXTRA_TEXT, textMessage) type = "text/plain" } // Try to invoke the intent. try { startActivity(sendIntent) } catch (e: ActivityNotFoundException) { // Define what your app should do if no activity can handle the intent. }
Java
// 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"); // Try to invoke the intent. try { startActivity(sendIntent); } catch (ActivityNotFoundException e) { // Define what your app should do if no activity can handle the intent. }
當系統呼叫 startActivity()
時,系統會檢查所有已安裝的應用程式,判斷哪些應用程式能夠處理這類意圖 (也就是具有 ACTION_SEND
動作的意圖,並包含「文字/純文字」資料)。如果只有一個應用程式能處理,系統會立即開啟該應用程式並提供意圖。如果沒有其他應用程式能處理,您的應用程式可擷取發生的 ActivityNotFoundException
。如果多項活動接受意圖,系統會顯示對話方塊 (如圖 2 所示),讓使用者能夠選擇要使用的應用程式。
如要進一步瞭解如何啟動其他應用程式,請參閱「將使用者傳送至其他應用程式」指南。
強制執行應用程式選擇工具
如果有多個應用程式會回應隱含意圖,使用者就能選取要使用的應用程式,並將該應用程式設為該動作的預設選項。如果使用者可能會想每次都想使用同一個應用程式 (例如開啟網頁時),選取預設值的功能就非常實用,例如使用者通常偏好使用一個網路瀏覽器。
不過,如果有多個應用程式可以回應意圖,且使用者可能每次都想使用不同的應用程式,則應明確顯示選擇工具對話方塊。選擇工具對話方塊會要求使用者選取要用於該動作的應用程式 (使用者無法選取動作的預設應用程式)。舉例來說,當應用程式使用 ACTION_SEND
動作執行「分享」時,使用者可能會根據目前情況使用不同的應用程式分享,因此建議您一律使用選擇工具對話方塊,如圖 2 所示。
如要顯示選擇工具,請使用 createChooser()
建立 Intent
並傳遞至 startActivity()
,如以下範例所示。這個範例顯示的對話方塊內含應用程式清單,這些應用程式會回應傳遞至 createChooser()
方法的意圖,並使用提供的文字做為對話方塊標題。
Kotlin
val sendIntent = Intent(Intent.ACTION_SEND) ... // Always use string resources for UI text. // This says something like "Share this photo with" val title: String = resources.getString(R.string.chooser_title) // Create intent to show the chooser dialog val chooser: Intent = Intent.createChooser(sendIntent, title) // Verify the original intent will resolve to at least one activity if (sendIntent.resolveActivity(packageManager) != null) { startActivity(chooser) }
Java
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); }
偵測不安全的意圖啟動作業
應用程式可能會啟動意圖,以便在應用程式內的元件之間導覽,或代表其他應用程式執行動作。如要提昇平台安全性,Android 12 (API 級別 31) 以上版本提供偵錯功能,可在應用程式執行不安全的意圖啟動時發出警告。舉例來說,應用程式可能會以不安全的方式啟動巢狀意圖,後者是以額外意圖的形式傳遞的意圖。
如果應用程式同時執行下列這兩項動作,系統會偵測到不安全的意圖啟動作業,並發生 StrictMode 違規事項:
- 應用程式將巢狀意圖從已傳遞意圖的額外項目中拆解出來。
- 應用程式立即使用該巢狀意圖啟動應用程式元件,例如將意圖傳遞至
startActivity()
、startService()
或bindService()
。
如要進一步瞭解如何找出此情況並對應用程式進行變更,請參閱 Medium 上的 Android 巢狀意圖網誌文章。
檢查是否有不安全的意圖啟動項目
如要檢查應用程式是否啟動不安全的意圖,請在設定 VmPolicy
時呼叫 detectUnsafeIntentLaunch()
,如以下程式碼片段所示。如果應用程式偵測到 StrictMode 違規,建議您停止應用程式執行,保護潛在的機密資訊。
Kotlin
fun onCreate() { StrictMode.setVmPolicy(VmPolicy.Builder() // Other StrictMode checks that you've previously added. // ... .detectUnsafeIntentLaunch() .penaltyLog() // Consider also adding penaltyDeath() .build()) }
Java
protected void onCreate() { StrictMode.setVmPolicy(new VmPolicy.Builder() // Other StrictMode checks that you've previously added. // ... .detectUnsafeIntentLaunch() .penaltyLog() // Consider also adding penaltyDeath() .build()); }
以更負責任的方式使用意圖
為了盡可能降低不安全的意圖啟動及違反 StrictMode 的可能性,請遵循下列最佳做法。
僅複製意圖中必要的額外項目,並執行所有必要的清理和驗證。應用程式可能會將某個意圖中的額外項目複製到另一個意圖,用來啟動新元件。當應用程式呼叫 putExtras(Intent)
或 putExtras(Bundle)
時,就會發生這種情形。如果應用程式執行這些作業,請只複製接收元件預期的額外項目。如果另一個意圖 (接收副本) 會啟動未匯出的元件,請先清理並驗證額外項目,再將額外項目複製到啟動元件的意圖。
請勿在不必要的情況下匯出應用程式元件。舉例來說,如果您打算使用內部巢狀意圖啟動應用程式元件,請將該元件的 android:exported
屬性設為 false
。
使用 PendingIntent
,而非巢狀意圖。這樣一來,當另一個應用程式解壓縮其內含 Intent
的 PendingIntent
時,其他應用程式就能使用您的應用程式身分啟動 PendingIntent
。這項設定可讓其他應用程式安全地啟動您應用程式中的任何元件,包括未匯出的元件。
圖 2 的圖表顯示系統如何從 (用戶端) 應用程式將控制權傳遞給另一個 (服務) 應用程式,再傳回應用程式:
- 應用程式會建立意圖來叫用其他應用程式中的活動。在此意圖中,您將新增
PendingIntent
物件做為額外項目。此待處理意圖會叫用應用程式中的元件,因此不會匯出這個元件。 - 其他應用程式收到應用程式的意圖後,會擷取巢狀
PendingIntent
物件。 - 另一個應用程式會對
PendingIntent
物件叫用send()
方法。 - 將控制項傳回應用程式後,系統會使用應用程式的結構定義叫用待處理意圖。
圖 2.使用巢狀待處理意圖時的應用程式內通訊圖表。
接收隱含意圖
如要宣傳應用程式可接收的隱含意圖,請使用資訊清單檔案中的 <intent-filter>
元素,為每個應用程式元件宣告一或多個意圖篩選器。每個意圖篩選器都會根據意圖的動作、資料和類別,指定其接受的意圖類型。只有在意圖能夠通過您的其中一個意圖篩選器時,系統才會向應用程式元件提供隱含意圖。
注意:無論元件宣告的任何意圖篩選器為何,明確意圖一律會傳送至其目標。
應用程式元件應為每個可執行的特定工作宣告不同的篩選器。舉例來說,圖片庫應用程式中的一項活動可能會有兩個篩選器:一個用於查看圖片,另一個篩選器則用於編輯圖片。活動啟動時,系統會檢查 Intent
,並根據 Intent
中的資訊決定行為 (例如是否顯示編輯器控制項)。
每個意圖篩選器都是由應用程式資訊清單檔案中的 <intent-filter>
元素定義,巢狀結構中的對應應用程式元件 (例如 <activity>
元素)。
在每個包含 <intent-filter>
元素的應用程式元件中,明確設定 android:exported
的值。這個屬性表示其他應用程式元件是否可供其他應用程式存取。在某些情況下 (例如意圖篩選器包含 LAUNCHER
類別的活動),建議您將這項屬性設為 true
。否則,請將這項屬性設為 false
。
警告:如果應用程式中的活動、服務或廣播接收器使用意圖篩選器,且未明確設定 android:exported
的值,則應用程式無法安裝在搭載 Android 12 以上版本的裝置上。
在 <intent-filter>
中,您可以使用下列三個元素的一或多個元素來指定要接受的意圖類型:
<action>
- 在
name
屬性中宣告可接受的意圖動作。這個值必須是動作的文字字串值,而非類別常數。 <data>
- 使用一或多個屬性指定資料 URI 的各個層面 (
scheme
、host
、port
、path
) 和 MIME 類型,藉此宣告可接受的資料類型。 <category>
- 在
name
屬性中宣告系統接受的意圖類別。這個值必須是動作的文字字串值,而非類別常數。注意:如要接收隱含意圖,您「必須」在意圖篩選器中加入
CATEGORY_DEFAULT
類別。startActivity()
和startActivityForResult()
方法會將所有意圖視為皆已宣告CATEGORY_DEFAULT
類別。如果您未在意圖篩選器中宣告這個類別,系統就不會將任何隱含意圖解析至您的活動。
舉例來說,以下是具有意圖篩選器的活動宣告,可在資料類型為文字時接收 ACTION_SEND
意圖:
<activity android:name="ShareActivity" android:exported="false"> <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>
例項的篩選器。如果這麼做,您需要確認元件能夠處理這些篩選器元素的任何或所有組合。
如果您想處理多種類型的意圖,但僅限特定動作、資料和類別類型的組合,則需要建立多個意圖篩選器。
隱含意圖將意圖與這三個元素的個別意圖進行比較,藉此測試隱含意圖。意圖必須通過全部三項測試,才能傳送至元件。如果系統沒有成功比對其中一個,Android 系統就不會將意圖傳遞給元件。不過,由於元件可能有多個意圖篩選器,因此如果意圖無法通過元件的任一篩選器,就可能會依另一個篩選器篩選該元件。如要進一步瞭解系統解析意圖的方式,請參閱下方的「意圖解析」一節。
注意: 使用意圖篩選器無法以安全的方式防止其他應用程式啟動您的元件。雖然意圖篩選器會限制元件只回應特定類型的隱含意圖,但如果開發人員判定您的元件名稱,其他應用程式可能會使用明確意圖啟動您的應用程式元件。如果只有您自己的應用程式能夠啟動其中一個元件,請勿在資訊清單中宣告意圖篩選器。而應該將該元件的 exported
屬性設為 "false"
。
同樣地,為避免意外執行其他應用程式的 Service
,請一律使用明確意圖啟動自己的服務。
注意:對於所有活動,您必須在資訊清單檔案中宣告意圖篩選器。不過,您可以呼叫 registerReceiver()
,以動態方式註冊廣播接收器的篩選器。接著,即可使用 unregisterReceiver()
取消註冊接收器。這個 API 可讓應用程式在應用程式執行期間,於指定時間內監聽特定廣播。
篩選器範例
為了示範一些意圖篩選器的行為,請參考社群媒體分享應用程式的資訊清單檔案範例:
<activity android:name="MainActivity" android:exported="true"> <!-- 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" android:exported="false"> <!-- 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>
第一個活動 MainActivity
是應用程式的主要進入點,也就是使用者一開始透過啟動器圖示啟動應用程式時開啟的活動:
ACTION_MAIN
動作表示這是主要進入點,因此不需要任何意圖資料。CATEGORY_LAUNCHER
類別表示此活動的圖示應置於系統的應用程式啟動器中。如果<activity>
元素未指定帶有icon
的圖示,系統會使用<application>
元素中的圖示。
這兩者必須一起配對,活動才會顯示在應用程式啟動器中。
第二項活動 ShareActivity
旨在促進分享文字和媒體內容。雖然使用者可能會透過從 MainActivity
導覽到該活動來進入此活動,但也可以直接從另一個應用程式輸入 ShareActivity
,且該意圖會發出符合兩個意圖篩選器之一的隱含意圖。
注意:MIME 類型 application/vnd.google.panorama360+jpg
是指定全景相片的特殊資料類型,您可以使用 Google 全景 API 處理。
將意圖與其他應用程式的意圖篩選器配對
如果其他應用程式指定 Android 13 (API 級別 33) 以上版本,只有在您的意圖與其他應用程式中 <intent-filter>
元素的動作和類別相符時,才能處理應用程式的意圖。如果系統找不到相符項目,就會擲回 ActivityNotFoundException
。傳送應用程式必須處理這個例外狀況。
同樣地,如果您將應用程式更新為指定 Android 13 以上版本,那麼只有在該意圖符合應用程式宣告的 <intent-filter>
元素動作和類別時,來自外部應用程式的所有意圖才會傳送至匯出的應用程式元件。無論傳送應用程式的目標 SDK 版本為何,都會發生這種行為。
在下列情況下,系統不會強制執行意圖比對:
- 意圖已傳送至未宣告任何意圖篩選器的元件。
- 來自相同應用程式的意圖。
- 來自系統的意圖,也就是從「系統 UID」 (uid=1000) 傳送的意圖。系統應用程式包括
system_server
,以及將android:sharedUserId
設為android.uid.system
的應用程式。 - 來自根的意圖。
進一步瞭解意圖比對。
使用待處理意圖
PendingIntent
物件是 Intent
物件的包裝函式。PendingIntent
的主要用途是向外國應用程式授予權限,讓外應用程式使用內含的 Intent
,就像從應用程式本身的程序執行一樣。
待處理意圖的主要用途包括:
- 宣告使用者透過通知執行動作時要執行的意圖 (Android 系統的
NotificationManager
會執行Intent
)。 - 宣告使用者透過應用程式小工具執行操作時要執行的意圖 (主畫面應用程式會執行
Intent
)。 - 宣告要在特定未來時間執行的意圖 (Android 系統的
AlarmManager
會執行Intent
)。
每個 Intent
物件的設計都是由特定類型的應用程式元件 (Activity
、Service
或 BroadcastReceiver
) 處理,因此也必須以相同的考量建立 PendingIntent
。使用待處理意圖時,應用程式不會透過 startActivity()
等呼叫執行意圖。相反地,您必須在建立 PendingIntent
時呼叫相應的建立方法,來宣告所需的元件類型:
- 適用於啟動
Activity
的Intent
的PendingIntent.getActivity()
。 - 適用於啟動
Service
的Intent
為PendingIntent.getService()
。 - 適用於啟動
BroadcastReceiver
的Intent
為PendingIntent.getBroadcast()
。
除非您的應用程式是「接收」其他應用程式的待處理意圖,否則您需要採用上述建立 PendingIntent
的方法,可能是您需要的唯一 PendingIntent
方法。
每種方法都會接受目前的應用程式 Context
、要包裝的 Intent
,以及一或多個指定意圖使用方式的旗標 (例如意圖是否可多次使用)。
如要進一步瞭解如何使用待處理的意圖,請參閱個別用途的說明文件,例如「通知」和「應用程式小工具」API 指南。
指定可變動性
如果應用程式指定 Android 12 以上版本,您必須為應用程式建立的每個 PendingIntent
物件指定可變動性。如要宣告特定 PendingIntent
物件可變動或不可變動,請分別使用 PendingIntent.FLAG_MUTABLE
或 PendingIntent.FLAG_IMMUTABLE
標記。
如果應用程式嘗試建立 PendingIntent
物件,但未設定任何可變動標記,則系統會擲回 IllegalArgumentException
,並在 Logcat 中顯示以下訊息:
PACKAGE_NAME: Targeting S+ (version 31 and above) requires that one of \
FLAG_IMMUTABLE or FLAG_MUTABLE be specified when creating a PendingIntent.
Strongly consider using FLAG_IMMUTABLE, only use FLAG_MUTABLE if \
some functionality depends on the PendingIntent being mutable, e.g. if \
it needs to be used with inline replies or bubbles.
盡可能建立不可變動的待處理意圖
在大多數情況下,應用程式應建立不可變動的 PendingIntent
物件,如以下程式碼片段所示。如果 PendingIntent
物件不可變動,其他應用程式就無法修改意圖,調整叫用意圖的結果。
Kotlin
val pendingIntent = PendingIntent.getActivity(applicationContext, REQUEST_CODE, intent, /* flags */ PendingIntent.FLAG_IMMUTABLE)
Java
PendingIntent pendingIntent = PendingIntent.getActivity(getApplicationContext(), REQUEST_CODE, intent, /* flags */ PendingIntent.FLAG_IMMUTABLE);
不過,某些用途需要可變動的 PendingIntent
物件:
- 支援通知中的直接回覆動作。直接回覆需要變更與回覆相關聯的 PendingIntent 物件中的剪輯資料。通常,只要將
FILL_IN_CLIP_DATA
做為旗標傳遞至fillIn()
方法,即可要求這項變更。 - 使用
CarAppExtender
例項將通知與 Android Auto 架構建立關聯。 - 使用
PendingIntent
的執行個體將對話放入對話框中。可變動的PendingIntent
物件可讓系統套用正確的標記,例如FLAG_ACTIVITY_MULTIPLE_TASK
和FLAG_ACTIVITY_NEW_DOCUMENT
。 - 呼叫
requestLocationUpdates()
或類似的 API 來要求裝置位置資訊。可變動的PendingIntent
物件允許系統新增代表位置生命週期事件的意圖額外項目。這類事件包括位置發生變更,以及可供使用的供應商。 - 使用
AlarmManager
設定鬧鐘。 可變動的PendingIntent
物件允許系統新增EXTRA_ALARM_COUNT
意圖額外項目。此額外項目代表週期性鬧鐘觸發的次數。透過包含此額外項目,意圖可準確通知應用程式是否多次觸發週期性鬧鐘,例如裝置休眠時。
如果應用程式會建立可變動的 PendingIntent
物件,強烈建議您使用明確意圖並填入 ComponentName
。如此一來,每當其他應用程式叫用 PendingIntent
並將控制項傳遞回應用程式時,應用程式中相同的元件一律會啟動。
在待處理意圖中使用明確意圖
為了更妥善地定義其他應用程式如何使用應用程式的待處理意圖,請一律將待處理意圖納入明確意圖周圍。如要遵循這項最佳做法,請執行下列操作:
- 確認已設定基本意圖的動作、套件和元件欄位。
-
使用 Android 6.0 (API 級別 23) 中新增的
FLAG_IMMUTABLE
建立待處理意圖。這個標記可防止收到PendingIntent
的應用程式填入未填入的屬性。如果應用程式的minSdkVersion
為22
以下,您可以使用以下程式碼一併提供安全性和相容性:if (Build.VERSION.SDK_INT >= 23) { // Create a PendingIntent using FLAG_IMMUTABLE. } else { // Existing code that creates a PendingIntent. }
意圖解析
收到隱含意圖啟動活動時,系統會根據以下三個層面,將意圖篩選器與意圖篩選器進行比較,藉此搜尋最適合意圖的活動:
- 動作。
- 資料 (URI 和資料類型)。
- 類別。
以下各節說明系統如何根據應用程式資訊清單檔案中的意圖篩選器宣告,將意圖與適當元件進行比對。
動作測試
如要指定可接受的意圖動作,意圖篩選器可以宣告零或多個 <action>
元素,如以下範例所示:
<intent-filter> <action android:name="android.intent.action.EDIT" /> <action android:name="android.intent.action.VIEW" /> ... </intent-filter>
如要通過這個篩選器,您在 Intent
中指定的動作必須與篩選器列出的其中一項動作相符。
如果篩選器未列出任何動作,表示沒有可比對的意圖,因此所有意圖都無法通過測試。然而,如果 Intent
未指定動作,只要篩選器包含至少一個動作,就會通過測試。
類別測試
如要指定可接受的意圖類別,意圖篩選器可以宣告零或多個 <category>
元素,如以下範例所示:
<intent-filter> <category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.BROWSABLE" /> ... </intent-filter>
為確保意圖測試通過類別,Intent
中的每個類別都必須與篩選器中的類別相符。相反地,意圖篩選器宣告的類別可能會比 Intent
中指定的數量多,且 Intent
仍會傳遞。因此,無論篩選器宣告哪些類別,沒有類別的意圖一律會通過這項測試。
注意:Android 會自動將 CATEGORY_DEFAULT
類別套用至所有傳遞至 startActivity()
和 startActivityForResult()
的隱含意圖。如果您想讓活動接收隱含意圖,則必須在意圖篩選器中加入 "android.intent.category.DEFAULT"
的類別,如上一個 <intent-filter>
範例所示。
資料測試
如要指定可接受的意圖資料,意圖篩選器可以宣告零或多個 <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
:
<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 部分。例如:
- 如果篩選器只指定配置,則所有使用該配置的 URI 都符合篩選器。
- 如果篩選器指定配置和授權,但沒有指定路徑,則無論其路徑為何,具有相同配置和授權的所有 URI 都會通過篩選器。
- 如果篩選器指定配置、授權和路徑,則只有採用相同配置、權威和路徑的 URI 才能通過篩選器。
注意:路徑規格可包含萬用字元星號 (*),這樣只需要比對部分路徑名稱。
資料測試會將意圖中的 URI 和 MIME 類型,與篩選器中指定的 URI 和 MIME 類型進行比較。規則如下:
- 包含 URI 和 MIME 類型的意圖只有在篩選器未指定任何 URI 或 MIME 類型時,才能通過測試。
- 包含 URI 但不含 MIME 類型的意圖 (不是明確或來自 URI 的推論) 只會在其 URI 與篩選器的 URI 格式相符時傳遞測試,同樣地,篩選器並未指定 MIME 類型。
- 包含 MIME 類型但沒有 URI 的意圖只有在篩選器列出相同 MIME 類型且未指定 URI 格式時,才能通過測試。
- 包含 URI 和 MIME 類型的意圖 (無論是明確或可從 URI 推論的意圖),只有在該類型符合篩選器中列出的類型時,才會傳遞測試的 MIME 類型部分。如果 URI 的 URI 與篩選器中的 URI 相符,或是有
content:
或file:
URI,且篩選器未指定 URI,就會傳遞測試的 URI 部分換句話說,如果元件的篩選器「只」列出 MIME 類型,則該元件會假設其支援content:
和file:
資料。
注意:如果意圖指定 URI 或 MIME 類型,則如果 <intent-filter>
中沒有 <data>
元素,資料測試就會失敗。
這個最後的規則 (d) 反映了元件可從檔案或內容供應器取得本機資料的預期情況。因此,這些類型的篩選器只能列出資料類型,不需要明確命名 content:
和 file:
配置。以下範例說明在以下情況中,<data>
元素會指示 Android 元件可以從內容供應器取得圖片資料並顯示:
<intent-filter> <data android:mimeType="image/*" /> ... </intent-filter>
指定資料類型但非 URI 的篩選器也許是最常見的,因為最有可用的資料會由內容供應器供應。
另一個常見的設定是採用配置和資料類型的篩選器。舉例來說,如下所示的 <data>
元素會指示 Android 元件可從網路擷取影片資料,以便執行動作:
<intent-filter> <data android:scheme="http" android:mimeType="video/*" /> ... </intent-filter>
意圖比對
意圖會比對意圖篩選器來比對要啟用的目標元件,也可以探索裝置上的元件組合。舉例來說,Google Home 應用程式會透過指定 ACTION_MAIN
動作和 CATEGORY_LAUNCHER
類別的意圖篩選器找出所有活動,然後填入應用程式啟動器。根據 IntentFilter
類別的說明文件,意圖中的動作和類別必須與篩選器相符,才算成功。
您的應用程式可以按照類似 Google Home 應用程式的方式使用意圖比對功能。PackageManager
有一組 query...()
方法,可傳回可接受特定意圖的所有元件,以及一系列類似的 resolve...()
方法,用來決定回應意圖的最佳元件。舉例來說,queryIntentActivities()
會傳回所有可執行做為引數傳遞意圖的活動清單,而 queryIntentServices()
會傳回類似的服務清單。這兩種方法都不會啟動元件,只會列出能回應的元件。廣播接收器也有類似的方法 queryBroadcastReceivers()
。