意圖和意圖篩選器

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 物件明確命名特定活動元件時,系統會立即啟動該元件。

圖 1. 隱含意圖如何透過系統傳送,以啟動其他活動:[1] 活動 A 會建立含有動作說明的 Intent,並將其傳遞至 startActivity()[2] Android 系統會搜尋所有應用程式,找出符合意圖的意圖篩選器。找到相符項目後,[3]系統會透過呼叫 onCreate() 方法並傳遞 Intent,啟動相符的活動 (活動 B)。

使用隱含意圖時,Android 系統會比較意圖內容與裝置上其他應用程式資訊清單檔案中宣告的意圖篩選器,藉此找出要啟動的適當元件。如果意圖與意圖篩選器相符,系統會啟動該元件,並傳遞 Intent 物件。如果有多個意圖篩選器相容,系統會顯示對話方塊,讓使用者選擇要使用的應用程式。

意圖篩選器是應用程式資訊清單檔案中的運算式,可指定元件想要接收的意圖類型。舉例來說,您可以為活動宣告意圖篩選器,讓其他應用程式直接透過特定類型的意圖啟動您的活動。同樣地,如果您沒有為活動宣告任何意圖篩選器,則只能使用明確意圖啟動活動。

注意:為確保應用程式安全無虞,啟動 Service 時請一律使用明確意圖,且不要宣告服務的意圖篩選器。使用隱含意圖啟動服務會危害安全性,因為您無法確定哪個服務會回應意圖,而使用者也無法查看哪個服務會啟動。自 Android 5.0 (API 級別 21) 起,如果您使用隱含意圖呼叫 bindService(),系統會擲回例外狀況。

建構意圖

Intent 物件會攜帶 Android 系統用於判斷要啟動哪個元件 (例如應接收意圖的確切元件名稱或元件類別) 的資訊,以及收件者元件用於正確執行動作的資訊 (例如要採取的動作和要採取的資料)。

Intent 中包含的主要資訊如下:

元件名稱
要啟動的元件名稱。

這項資訊為選用項目,但卻是讓意圖「明確」的關鍵資訊,意即意圖應只傳送至由元件名稱定義的應用程式元件。如果沒有元件名稱,意圖就會是隱含的,系統會根據其他意圖資訊 (例如動作、資料和類別,請參閱下文) 決定應由哪個元件接收意圖。如果您需要啟動應用程式中的特定元件,則應該指定元件名稱。

注意:啟動 Service 時,請一律指定元件名稱。否則,您無法確定哪個服務會回應意圖,而使用者也無法查看哪個服務會啟動。

Intent 的這個欄位是 ComponentName 物件,您可以使用目標元件的完整類別名稱 (包括應用程式的套件名稱,例如 com.example.ExampleActivity) 加以指定。您可以使用 setComponent()setClass()setClassName()Intent 建構函式設定元件名稱。

動態
指定要執行的一般動作 (例如 viewpick) 的字串。

如為廣播意圖,這是指系統回報的動作。動作主要會決定意圖的其餘部分的結構,尤其是資料和其他項目中的資訊。

您可以指定自己的動作,供應用程式中的意圖使用 (或供其他應用程式用於叫用應用程式中的元件),但通常可指定由 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";
資料
參照要處理的資料和/或該資料的 MIME 類型的 URI (Uri 物件)。提供的資料類型通常取決於意圖的動作。舉例來說,如果動作是 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()Intent 中插入 Bundle

舉例來說,如果您要使用 ACTION_SEND 建立意圖來傳送電子郵件,可以使用 EXTRA_EMAIL 鍵指定收件者,並使用 EXTRA_SUBJECT 鍵指定主旨

Intent 類別會為標準化資料類型指定許多 EXTRA_* 常數。如果您需要宣告自己的額外鍵 (用於應用程式收到的意圖),請務必加入應用程式的套件名稱做為前置字元,如以下範例所示:

Kotlin

const val EXTRA_GIGAWATTS = "com.example.EXTRA_GIGAWATTS"

Java

static final String EXTRA_GIGAWATTS = "com.example.EXTRA_GIGAWATTS";

注意:傳送其他應用程式要接收的意圖時,請勿使用 ParcelableSerializable 資料。如果應用程式嘗試存取 Bundle 物件中的資料,但無法存取分割或序列化的類別,系統會擲回 RuntimeException

標記
旗標是在 Intent 類別中定義,作用是做為意圖的中繼資料。標記可指示 Android 系統如何啟動活動 (例如活動應屬於哪個工作),以及如何在啟動後處理活動 (例如活動是否屬於近期活動清單)。

詳情請參閱 setFlags() 方法。

明確意圖範例

明確意圖是用來啟動特定應用程式元件 (例如應用程式中的特定活動或服務)。如要建立明確意圖,請為 Intent 物件定義元件名稱,其他所有意圖屬性皆為選用屬性。

舉例來說,如果您在應用程式中建構名為 DownloadService 的服務,用於從網路下載檔案,您可以使用以下程式碼啟動服務:

Kotlin

// Executed in an Activity, so 'this' is the Context
// 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 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 物件。因此,這個意圖會明確啟動應用程式中的 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 動作且攜帶「text/plain」資料的意圖)。如果只有一個應用程式可以處理,系統會立即開啟該應用程式並提供意圖。如果沒有其他應用程式可以處理,您的應用程式可以擷取發生的 ActivityNotFoundException。如果有多個活動接受意圖,系統會顯示對話方塊 (如圖 2 所示),讓使用者選擇要使用的應用程式。

如要進一步瞭解如何啟動其他應用程式,請參閱將使用者傳送至其他應用程式的指南。

圖 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) 以上版本提供偵錯功能,可在應用程式執行不安全的意圖啟動作業時發出警告。舉例來說,您的應用程式可能會執行巢狀意圖的 unsafe launch,這是在其他意圖中以額外項目傳遞的意圖。

如果應用程式同時執行下列兩項動作,系統會偵測到不安全的意圖啟動作業,並發生 StrictMode 違規情形:

  1. 應用程式將巢狀意圖從已傳遞意圖的額外項目中拆解出來。
  2. 應用程式立即使用該巢狀意圖啟動應用程式元件,例如將意圖傳遞至 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,而非巢狀意圖。這樣一來,當其他應用程式解析所含 IntentPendingIntent 時,該應用程式就能使用您應用程式的身分啟動 PendingIntent。這項設定可讓其他應用程式在您的應用程式中安全地啟動任何元件,包括未匯出的元件。

圖 2 中的圖表顯示系統如何將控制權從您的 (用戶端) 應用程式傳遞至其他 (服務) 應用程式,然後再傳回您的應用程式:

  1. 您的應用程式會建立意圖,以便在其他應用程式中叫用活動。在該意圖中,您會將 PendingIntent 物件新增為額外項目。這個待處理意圖會在應用程式中叫用元件,但該元件並未匯出。
  2. 收到應用程式的意圖後,其他應用程式會擷取巢狀 PendingIntent 物件。
  3. 另一個應用程式對 PendingIntent 物件叫用 send() 方法。
  4. 將控制權傳回應用程式後,系統會使用應用程式的內容叫用待處理的意圖。

圖 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 的各種層面 (schemehostportpath) 和 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() 取消註冊接收器。這樣做可讓應用程式在執行期間,只監聽指定時間範圍內的廣播訊息。

篩選器示例

為了示範部分意圖篩選器行為,以下是社交分享應用程式資訊清單檔案的範例:

<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 的權限,就如同從應用程式本身的程序執行一樣。

待處理意圖的主要用途包括:

就像每個 Intent 物件都設計為由特定類型的應用程式元件 (ActivityServiceBroadcastReceiver) 處理一樣,您也必須以相同的考量來建立 PendingIntent。使用待處理意圖時,應用程式不會透過 startActivity() 等呼叫執行意圖。相反地,您必須在建立 PendingIntent 時,透過呼叫相應的建立者方法來宣告預期的元件類型:

除非應用程式會從其他應用程式「接收」待處理意圖,否則上述建立 PendingIntent 的方法可能是您需要的唯一 PendingIntent 方法。

每個方法都會使用目前的應用程式 Context、您要包裝的 Intent,以及一或多個指定意圖使用方式的標記 (例如是否可重複使用該意圖)。

如要進一步瞭解如何使用待處理意圖,請參閱各個用途的說明文件,例如 通知應用程式小工具 API 指南。

指定可變動性

如果應用程式指定 Android 12 或以上的版本,您必須指定應用程式所建立每個 PendingIntent 物件的可變動性。如要宣告特定 PendingIntent 物件是否可變動,請分別使用 PendingIntent.FLAG_MUTABLEPendingIntent.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_TASKFLAG_ACTIVITY_NEW_DOCUMENT
  • 呼叫 requestLocationUpdates() 或類似 API 來要求裝置位置資訊。可變動的 PendingIntent 物件可讓系統新增意圖額外項目,代表位置生命週期事件。這些事件包括位置變更和供應者可用。
  • 使用 AlarmManager 排定鬧鐘。可變動的 PendingIntent 物件可讓系統新增 EXTRA_ALARM_COUNT 意圖額外項目。這個額外值代表重複鬧鐘觸發的次數。包含此額外資訊後,意圖就能準確通知應用程式,重複鬧鐘是否已觸發多次,例如在裝置處於休眠狀態時。

如果應用程式會建立可變動的 PendingIntent 物件,強烈建議您使用明確意圖並填入 ComponentName。這樣一來,只要其他應用程式叫用 PendingIntent 並將控制權傳回您的應用程式,應用程式中就會一律啟動相同的元件。

在待處理意圖中使用明確意圖

如要進一步定義其他應用程式如何使用您應用程式的待處理意圖,請務必在明確意圖中包裝待處理意圖。如要遵循這項最佳做法,請執行下列操作:

  1. 確認已設定基本意圖的動作、套件和元件欄位。
  2. 使用 Android 6.0 (API 級別 23) 中新增的 FLAG_IMMUTABLE 建立待處理意圖。這個標記可防止收到 PendingIntent 的應用程式填入未填寫的屬性。如果應用程式的 minSdkVersion22 以下,您可以使用下列程式碼,同時提供安全性和相容性:

    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 的每個部分都是個別屬性:schemehostportpath

<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 類型進行比較。規則如下:

  1. 如果篩選器未指定任何 URI 或 MIME 類型,且意圖既不包含 URI 也不包含 MIME 類型,則該意圖會通過測試。
  2. 如果意圖包含 URI,但沒有 MIME 類型 (從 URI 中無法明確或推斷),只有在 URI 與篩選器的 URI 格式相符,且篩選器同樣未指定 MIME 類型時,才能通過測試。
  3. 如果意圖包含 MIME 類型但不包含 URI,只有在篩選器列出相同的 MIME 類型且未指定 URI 格式時,才能通過測試。
  4. 如果意圖同時包含 URI 和 MIME 類型 (不論是明確提供或可從 URI 推斷),只有在該類型與篩選器中列出的類型相符時,才能通過測試的 MIME 類型部分。如果該 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>

意圖比對

意圖會與意圖篩選器進行比對,不僅用於找出要啟用的目標元件,還可用於找出裝置上的一組元件。舉例來說,Home 應用程式會找出所有含有意圖篩選器的活動,並指定 ACTION_MAIN 動作和 CATEGORY_LAUNCHER 類別,藉此填入應用程式啟動器。如同 IntentFilter 類別的說明文件所述,只有在意圖中的動作和類別與篩選器相符時,才能成功比對。

您的應用程式可以使用意圖比對的方式,類似於 Google Home 應用程式的功能。PackageManager 包含一組 query...() 方法,可傳回所有可接受特定意圖的元件,以及一系列類似的 resolve...() 方法,可決定回應意圖的最佳元件。舉例來說,queryIntentActivities() 會傳回可執行以引數形式傳遞的意圖的所有活動清單,而 queryIntentServices() 會傳回類似的服務清單。這兩種方法都不會啟用元件,只會列出可回應的元件。廣播接收器也有類似的方法 queryBroadcastReceivers()