Intent Mechanism Deep Dive
An Intent is the "message carrier" for communication between Android components. Understanding Intent is the key to mastering the core decoupling mechanism of Android components.
The Essence of Intent
An Intent is a messaging object that can carry:
- Action: What needs to be done, e.g.,
Intent.ACTION_VIEW - Data: What the action operates on, e.g.,
Uri.parse("tel:12345") - Category: Additional classification, e.g.,
Intent.CATEGORY_LAUNCHER - Extra: Key-value pairs for supplementary data
- ComponentName: Explicitly designates the target component (mandatory for explicit Intents)
- Flags: Control behavior, e.g.,
FLAG_ACTIVITY_NEW_TASK
Explicit Intent vs. Implicit Intent
Explicit Intent
Explicitly designates the target component, ensuring a direct hit without requiring the system to perform matching calculations:
// Intra-app navigation: Highly recommended
val intent = Intent(this, DetailActivity::class.java)
intent.putExtra("id", 42)
startActivity(intent)
// Cross-app navigation (knowing package and class name): Poor security posture, generally not recommended
val intent = Intent().apply {
component = ComponentName("com.other.app", "com.other.app.ShareActivity")
}
startActivity(intent)
Implicit Intent
Does not designate a target; the system resolves it via IntentFilter matching. Architected for cross-application orchestration:
// Open a webpage (System prompts user to select a browser)
val intent = Intent(Intent.ACTION_VIEW, Uri.parse("https://example.com"))
startActivity(intent)
// Dial a phone number
val intent = Intent(Intent.ACTION_DIAL, Uri.parse("tel:12345678"))
startActivity(intent)
// Share text data
val intent = Intent(Intent.ACTION_SEND).apply {
type = "text/plain"
putExtra(Intent.EXTRA_TEXT, "Content to share")
}
startActivity(Intent.createChooser(intent, "Select sharing method"))
IntentFilter Matching Rules
Components declare <intent-filter> blocks in AndroidManifest.xml to broadcast which implicit Intents they are capable of handling:
<activity android:name=".ViewImageActivity">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="image/*" />
</intent-filter>
</activity>
Matching rules: An implicit Intent must successfully pass the Action, Category, and Data tests simultaneously to route to the component:
| Element | Matching Rule |
|---|---|
| Action | The Intent's Action must perfectly match at least one Action listed in the filter. |
| Category | Every Category within the Intent must exist in the filter; implicit startActivity automatically injects CATEGORY_DEFAULT. |
| Data | URI scheme/host/path and MIME type must map correctly (ignored if not specified in the Intent). |
Verifying capability prior to launch (Android 11+ requires <queries> declarations):
if (intent.resolveActivity(packageManager) != null) {
startActivity(intent)
} else {
Toast.makeText(this, "No application can handle this action", Toast.LENGTH_SHORT).show()
}
PendingIntent: The Delegated Execution
A PendingIntent is an encapsulation of an Intent, granting a foreign application (like the OS, Notification Manager, or Widget host) the authority to execute that Intent in the future as if it were your application.
Architectural Use Cases
- Click actions for Push Notifications
AlarmManagerscheduled executionsWorkManagercompletion callbacks- Interactive elements on Desktop Widgets
Instantiation Patterns
// 1. Construct the foundational Intent
val intent = Intent(context, DetailActivity::class.java).apply {
putExtra("notification_id", 42)
}
// 2. Encapsulate into a PendingIntent
val pendingIntent = PendingIntent.getActivity(
context,
REQUEST_CODE, // Differentiates unique PendingIntents
intent,
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)
// 3. Mount to a Notification
val notification = NotificationCompat.Builder(context, CHANNEL_ID)
.setContentTitle("New Message")
.setContentIntent(pendingIntent) // Fires upon notification click
.setAutoCancel(true)
.build()
PendingIntent FLAG Deep Dive
| Flag | System Behavior |
|---|---|
FLAG_CANCEL_CURRENT |
Annihilates the existing PendingIntent and fabricates a new one. |
FLAG_UPDATE_CURRENT |
Retains the existing PendingIntent, but overwrites the Extra payload data. |
FLAG_NO_CREATE |
Returns null if it doesn't exist; strictly avoids creation. |
FLAG_ONE_SHOT |
Single-use burn capability; invalidates immediately upon execution. |
FLAG_IMMUTABLE |
Recommended Android 12+ standard: The encapsulated Intent cannot be mutated by the receiving entity (Maximum Security). |
FLAG_MUTABLE |
Permits the receiving entity to mutate the Intent payload (Required for specific APIs like Bubbles). |
Starting from Android 12 (API 31), explicit declaration of either
FLAG_IMMUTABLEorFLAG_MUTABLEis a rigid requirement when instantiating a PendingIntent. Failure to do so results in a fatalIllegalArgumentException.
Security Attack Vectors & Defenses
A PendingIntent inherently executes under your application's UID and permission scope. If you construct a PendingIntent wrapping an empty/blank Intent and hand it to a third-party application, the recipient can inject arbitrary payloads and component targets, executing them masquerading as your app. This is the infamous PendingIntent Hijacking vulnerability.
// ❌ CRITICAL VULNERABILITY: Blank Intent combined with MUTABLE flag
val pendingIntent = PendingIntent.getActivity(context, 0, Intent(), FLAG_MUTABLE)
// ✅ SECURE ARCHITECTURE: Explicit ComponentName combined with IMMUTABLE flag
val intent = Intent(context, MyActivity::class.java)
val pendingIntent = PendingIntent.getActivity(context, 0, intent, FLAG_IMMUTABLE)
Engineering Edge Cases & Visibility Restrictions
Implicit Intent Resolution Flow
The OS kernel scans the Manifests of all installed packages to locate components declaring compatible IntentFilter blocks. The Intent must survive the Action, Category, and Data validation gauntlet. If the computation yields multiple valid targets, the OS surface displays a Chooser dialog, forcing the user to resolve the ambiguity.
The Semantic Gulf Between Intent and PendingIntent
An Intent is an immediate execution command requiring an active Context to dispatch. A PendingIntent is an asynchronous token of delegated authority, capable of persisting and executing long after your application's process has been terminated by the OS.
Forcing the Resolution Chooser
When multiple applications can handle an Intent, you can programmatically override default user preferences and force the disambiguation UI by wrapping the Intent via Intent.createChooser(). If absolute targeting is required, the ComponentName must be explicitly defined.
Android 11 Package Visibility (API 30+) Limitations
Starting with Android 11, the OS enforces a strict "Package Visibility" sandbox. By default, your app can only "see" a highly restricted subset of other packages (e.g., system apps, or apps you installed). If your architecture relies on implicit Intents to trigger specific external applications, you must explicitly whitelist the target intent signatures within a <queries> block in your Manifest:
<queries>
<!-- Explicitly declare the implicit Intent signatures your app needs to query -->
<intent>
<action android:name="android.intent.action.VIEW" />
<data android:scheme="https" />
</intent>
</queries>