Skip to content

Android Intent 与 IntentFilter - 全面详解

Intent(意图)是 Android 组件间通信的核心机制,IntentFilter 用于定义组件可以接收的 Intent 类型。


目录

  1. Intent 基础概念
  2. 显式 Intent vs 隐式 Intent
  3. IntentFilter 匹配规则
  4. PendingIntent 详解
  5. Intent Flag 常用标志位
  6. Intent 数据传递限制
  7. ClipData 传递大文件
  8. 面试考点

1. Intent 基础概念

1.1 什么是 Intent

Intent(意图)是 Android 中用于在组件之间传递消息的对象。它描述了"想要做什么",而不指定"如何实现"。

核心作用:

  • ✅ 启动 Activity
  • ✅ 启动 Service
  • ✅ 发送 BroadcastReceiver
  • ✅ 组件间数据传递
  • ✅ 实现组件解耦

1.2 Intent 的分类

kotlin
// 1. 普通 Intent(Implicit/Explicit)
val intent = Intent(this, TargetActivity::class.java)

// 2. PendingIntent(延迟执行的 Intent)
val pendingIntent = PendingIntent.getActivity(this, 0, intent, flags)

// 3. ForegroundServiceIntent(前台服务专用)
val fgIntent = Intent(this, MyService::class.java)

1.3 Intent 的组成要素

kotlin
val intent = Intent().apply {
    // 1. Action:要执行的操作
    action = Intent.ACTION_VIEW
    
    // 2. Data:操作的数据 URI
    data = Uri.parse("https://www.example.com")
    
    // 3. Type:数据的 MIME 类型
    type = "image/*"
    
    // 4. Category:额外的分类信息
    addCategory(Intent.CATEGORY_BROWSABLE)
    
    // 5. Component:目标组件(显式 Intent)
    `class` = TargetActivity::class.java
    
    // 6. Extras:附加数据
    putExtra("key", "value")
    
    // 7. Flags:标志位
    addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
    
    // 8. Package:目标包名
    `package` = "com.example"
}

2. 显式 Intent vs 隐式 Intent

2.1 核心区别

特性显式 Intent隐式 Intent
指定目标明确指定组件类名不指定具体组件
匹配方式直接启动系统匹配 IntentFilter
使用场景应用内组件调用跨应用调用、系统功能
安全性较高需要权限控制
可替代性单一目标多个应用可响应

2.2 显式 Intent(Explicit Intent)

定义:明确指定目标组件的 Intent

kotlin
// 方式 1:直接指定类(推荐)
val intent = Intent(this, TargetActivity::class.java)

// 方式 2:指定包名和类名
val intent = Intent().apply {
    `package` = "com.example"
    `class` = "com.example.TargetActivity"
}

// 方式 3:ComponentName
val component = ComponentName(this, TargetActivity::class.java)
val intent = Intent().setComponent(component)

// 启动 Activity
startActivity(intent)

// 启动 Service
startService(intent)

// 发送广播
sendBroadcast(intent)

适用场景:

kotlin
// 场景 1:应用内 Activity 跳转
class MainActivity : AppCompatActivity() {
    fun goToDetail() {
        val intent = Intent(this, DetailActivity::class.java)
        startActivity(intent)
    }
}

// 场景 2:启动本应用的 Service
class MainActivity : AppCompatActivity() {
    fun startDownload() {
        val intent = Intent(this, DownloadService::class.java)
        startService(intent)
    }
}

// 场景 3:发送应用内广播
class DataUpdater {
    fun notifyDataChanged(context: Context) {
        val intent = Intent(DATA_UPDATED_ACTION)
        context.sendBroadcast(intent)
    }
}

2.3 隐式 Intent(Implicit Intent)

定义:不指定具体组件,通过 Action/Data/Category 让系统匹配

kotlin
// 方式 1:使用系统 Action
// 打开浏览器
val webIntent = Intent(Intent.ACTION_VIEW).apply {
    data = Uri.parse("https://www.example.com")
}

// 发送短信
val smsIntent = Intent(Intent.ACTION_SENDTO).apply {
    data = Uri.parse("smsto:13800138000")
}

// 拨打电话
val callIntent = Intent(Intent.ACTION_CALL).apply {
    data = Uri.parse("tel:13800138000")
}

// 拍照
val cameraIntent = Intent(MediaStore.ACTION_IMAGE_CAPTURE)

// 选择图片
val pickIntent = Intent(Intent.ACTION_PICK).apply {
    type = MediaType.IMAGE
}

// 方式 2:自定义 Action
val customIntent = Intent("com.example.CUSTOM_ACTION").apply {
    putExtra("data", "some value")
}

// 启动隐式 Intent
startActivity(webIntent)

// 或者查看有哪些应用可以处理
val resolveInfoList = packageManager.queryIntentActivities(
    webIntent,
    PackageManager.ResolveInfoFlags.of(0)
)

2.4 隐式 Intent 的系统功能

kotlin
// 1. 查看地图
val mapIntent = Intent(Intent.ACTION_VIEW).apply {
    data = Uri.parse("geo:39.9042,116.4074")
}

// 2. 设置闹钟
val alarmIntent = Intent(Intent.ACTION_SET_ALARM).apply {
    putExtra(AlarmClock.EXTRA_MESSAGE, "起床了")
    putExtra(
        AlarmClock.EXTRA_WAKEUP_TIME,
        System.currentTimeMillis() + 60 * 60 * 1000
    )
}

// 3. 分享文本
val shareTextIntent = Intent(Intent.ACTION_SEND).apply {
    type = "text/plain"
    putExtra(Intent.EXTRA_TEXT, "这是要分享的文本")
}

// 4. 分享文件
val shareFileIntent = Intent(Intent.ACTION_SEND).apply {
    type = "image/*"
    putExtra(Intent.EXTRA_STREAM, fileUri)
    addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
}

// 5. 安装应用
val installIntent = Intent(Intent.ACTION_VIEW).apply {
    data = Uri.fromFile(apkFile)
    type = "application/vnd.android.package-archive"
    addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
}

// 6. 删除应用
val uninstallIntent = Intent(Intent.ACTION_DELETE).apply {
    data = Uri.parse("package:com.example")
}

2.5 创建选择器(Chooser)

kotlin
// 问题:直接 startActivity(隐式 Intent) 会直接打开默认应用
// 解决:使用选择器让用户选择应用

// 方式 1:简单的选择器
fun shareWithChooser(context: Context, text: String) {
    val shareIntent = Intent(Intent.ACTION_SEND).apply {
        type = "text/plain"
        putExtra(Intent.EXTRA_TEXT, text)
    }
    
    val chooserIntent = Intent.createChooser(shareIntent, "选择分享方式")
    context.startActivity(chooserIntent)
}

// 方式 2:带默认应用的选择器
fun shareWithDefaultChooser(context: Context, text: String, defaultApp: ComponentName?) {
    val shareIntent = Intent(Intent.ACTION_SEND).apply {
        type = "text/plain"
        putExtra(Intent.EXTRA_TEXT, text)
    }
    
    val chooserIntent = Intent.createChooser(shareIntent, "选择分享方式")
    
    // 设置默认应用
    defaultApp?.let {
        chooserIntent.putExtra(
            Intent.EXTRA_INITIAL_INTENTS,
            arrayOf(Intent().setComponent(it))
        )
    }
    
    context.startActivity(chooserIntent)
}

// 方式 3:自定义选择器样式
fun shareWithCustomChooser(context: Context, text: String) {
    val shareIntent = Intent(Intent.ACTION_SEND).apply {
        type = "text/plain"
        putExtra(Intent.EXTRA_TEXT, text)
    }
    
    val chooserIntent = Intent.createChooser(shareIntent, "选择分享方式")
    
    // 隐藏某些应用
    chooserIntent.putExtra(
        Intent.EXTRA_EXCLUDE_COMPONENTS,
        arrayOf(ComponentName(context, UnwantedActivity::class.java))
    )
    
    context.startActivity(chooserIntent)
}

3. IntentFilter 匹配规则

3.1 IntentFilter 的作用

IntentFilter 定义了组件可以接收的 Intent 类型,系统通过 IntentFilter 来匹配 Intent 和目标组件。

3.2 匹配三要素

Intent 和 IntentFilter 的匹配需要同时满足:

  1. Action 匹配
  2. Data 匹配
  3. Category 匹配

只有三要素全部匹配,Intent 才能成功传递给目标组件。

3.3 Action 匹配

kotlin
// IntentFilter 中声明接收的 Action
val filter = IntentFilter()
filter.addAction(Intent.ACTION_VIEW)
filter.addAction(Intent.ACTION_EDIT)
filter.addAction("com.example.CUSTOM_ACTION")

// Intent 中指定 Action
val intent = Intent("com.example.CUSTOM_ACTION")

// 匹配成功

Manifest 中声明:

xml
<activity android:name=".MyActivity" android:exported="true">
    <intent-filter>
        <action android:name="android.intent.action.VIEW" />
        <action android:name="android.intent.action.SEND" />
        <action android:name="com.example.CUSTOM_ACTION" />
    </intent-filter>
</activity>

3.4 Data 匹配

Data 匹配包含两个部分:SchemeHost/Path

kotlin
// 1. 匹配 URI
val filter = IntentFilter()
filter.addDataScheme("http")
filter.addDataScheme("https")

// 匹配:http://example.com, https://example.com

// 2. 匹配 MIME Type
filter.addDataMimeType("image/*")
filter.addDataMimeType("text/plain")

// 匹配:image/jpeg, text/plain

// 3. 完整的 URI 匹配
val filter = IntentFilter()
filter.setDataScheme("http")
filter.addDataHost("example.com")
filter.addDataPathPrefix("/api/")

// 匹配:http://example.com/api/users

Manifest 中声明 Data 匹配:

xml
<activity android:name=".BrowserActivity" android:exported="true">
    <intent-filter>
        <action android:name="android.intent.action.VIEW" />
        <category android:name="android.intent.category.BROWSABLE" />
        
        <!-- 匹配 HTTP/HTTPS URL -->
        <data android:scheme="http" />
        <data android:scheme="https" />
        
        <!-- 匹配特定域名 -->
        <data android:host="example.com" />
        <data android:host="www.example.com" />
        
        <!-- 匹配路径 -->
        <data android:pathPrefix="/articles/" />
        
        <!-- 或简写 -->
        <data android:scheme="https" android:host="example.com" android:pathPrefix="/articles/" />
    </intent-filter>
</activity>

Data 匹配规则详解:

┌──────────────────────────────────────────┐
│        Data 匹配优先级(从高到低)        │
├──────────────────────────────────────────┤
│ 1. scheme + host + port + path           │
│ 2. scheme + host + path                  │
│ 3. scheme + host                         │
│ 4. scheme + path                         │
│ 5. scheme                                │
│ 6. MIME type                             │
└──────────────────────────────────────────┘

示例:
Intent: content://com.example.provider/users/123

IntentFilter 1:
  <data android:scheme="content" android:host="com.example.provider" />
  ✓ 匹配成功(scheme + host)

IntentFilter 2:
  <data android:scheme="content" android:host="com.example.provider" android:pathPrefix="/users/" />
  ✓ 匹配成功(更精确)

IntentFilter 3:
  <data android:scheme="content" />
  ✓ 匹配成功(只匹配 scheme)

3.5 Category 匹配

kotlin
// 标准 Category
val filter = IntentFilter()
filter.addCategory(Intent.CATEGORY_DEFAULT)  // 默认添加
filter.addCategory(Intent.CATEGORY_BROWSABLE) // 可浏览
filter.addCategory(Intent.CATEGORY_LAUNCHER)  // 启动器

// Intent 必须包含 IntentFilter 声明的所有 Category
val intent = Intent(Intent.ACTION_VIEW).apply {
    addCategory(Intent.CATEGORY_BROWSABLE)
}

常用 Category:

Category说明使用场景
CATEGORY_DEFAULT默认类别所有 Intent 自动添加
CATEGORY_BROWSABLE可浏览可被浏览器打开的 URL
CATEGORY_LAUNCHER启动器显示在主屏幕的应用入口
CATEGORY_APP_BROWSER浏览器应用浏览器类应用
CATEGORY_APP_BROWSER邮件应用邮件客户端

Manifest 示例:

xml
<!-- 应用主入口 -->
<activity android:name=".MainActivity" android:exported="true">
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
</activity>

<!-- 可被浏览器打开的页面 -->
<activity android:name=".ArticleActivity" android:exported="true">
    <intent-filter>
        <action android:name="android.intent.action.VIEW" />
        <category android:name="android.intent.category.DEFAULT" />
        <category android:name="android.intent.category.BROWSABLE" />
        <data android:scheme="https" android:host="example.com" android:pathPrefix="/article/" />
    </intent-filter>
</activity>

3.6 优先级(Priority)

xml
<!-- 优先级范围:-1000 到 1000 -->
<!-- 数值越高,优先级越高 -->

<intent-filter android:priority="999">
    <action android:name="android.intent.action.VIEW" />
    <data android:scheme="https" />
</intent-filter>

<intent-filter android:priority="100">
    <action android:name="android.intent.action.VIEW" />
    <data android:scheme="https" />
</intent-filter>

3.7 完整匹配示例

xml
<!-- 定义一个完整的 IntentFilter -->
<activity android:name=".CustomProtocolActivity" android:exported="true">
    <intent-filter android:priority="999">
        <!-- Action -->
        <action android:name="android.intent.action.VIEW" />
        
        <!-- Categories -->
        <category android:name="android.intent.category.DEFAULT" />
        <category android:name="android.intent.category.BROWSABLE" />
        
        <!-- Data -->
        <data android:scheme="myapp" />
        <data android:host://user/profile/123
    </intent-filter>
</activity>

<!-- 匹配的 Intent -->
val intent = Intent(Intent.ACTION_VIEW).apply {
    addCategory(Intent.CATEGORY_BROWSABLE)
    data = Uri.parse("myapp://user/profile/123")
}

3.8 动态注册 IntentFilter

kotlin
class MainActivity : AppCompatActivity() {
    
    private val receiver = object : BroadcastReceiver() {
        override fun onReceive(context: Context, intent: Intent) {
            // 处理广播
        }
    }
    
    override fun onResume() {
        super.onResume()
        
        // 创建 IntentFilter
        val filter = IntentFilter().apply {
            addAction(Intent.ACTION_BATTERY_CHANGED)
            addAction("com.example.CUSTOM_ACTION")
        }
        
        // 注册接收器
        registerReceiver(receiver, filter)
    }
    
    override fun onPause() {
        super.onPause()
        unregisterReceiver(receiver)
    }
}

4. PendingIntent 详解

4.1 什么是 PendingIntent

PendingIntent(待定 Intent)是一个 Intent 的包装器,它允许其他应用以你的应用身份执行预定义的 Intent 操作。

核心特点:

  • ✅ 延迟执行 Intent
  • ✅ 授权其他应用使用
  • ✅ 保留原始 Intent 的权限
  • ⚠️ 需要注意安全性

4.2 使用场景

kotlin
// 场景 1:Notification 点击跳转
val intent = Intent(context, TargetActivity::class.java)
val pendingIntent = PendingIntent.getActivity(
    context,
    0,
    intent,
    PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)

val notification = NotificationCompat.Builder(context, CHANNEL_ID)
    .setContentIntent(pendingIntent)
    .build()

// 场景 2:AlarmManager 定时任务
val intent = Intent(context, BootReceiver::class.java)
val pendingIntent = PendingIntent.getBroadcast(
    context,
    0,
    intent,
    PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)

alarmManager.set(
    AlarmManager.RTC_WAKEUP,
    triggerTime,
    pendingIntent
)

// 场景 3:Service 远程调用
val intent = Intent(context, MyService::class.java)
val pendingIntent = PendingIntent.getService(
    context,
    0,
    intent,
    PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)

4.3 三种类型

1. getActivity() - 启动 Activity

kotlin
val pendingIntent = PendingIntent.getActivity(
    context,
    requestCode,
    intent,
    flags
)

// 使用:Notification、Dialog 等

2. getBroadcast() - 发送广播

kotlin
val pendingIntent = PendingIntent.getBroadcast(
    context,
    requestCode,
    intent,
    flags
)

// 使用:AlarmManager、Notification 等

3. getService() - 启动 Service

kotlin
val pendingIntent = PendingIntent.getService(
    context,
    requestCode,
    intent,
    flags
)

// 使用:JobScheduler、其他应用启动服务等

4.4 RequestCode 的作用

kotlin
// RequestCode 用于区分同一 Context 创建的多个 PendingIntent

// 错误示例:相同的 requestCode 会导致 PendingIntent 覆盖
val intent1 = Intent(context, ActivityA::class.java)
val pendingIntent1 = PendingIntent.getActivity(context, 0, intent1, flags)

val intent2 = Intent(context, ActivityB::class.java)
val pendingIntent2 = PendingIntent.getActivity(context, 0, intent2, flags)

// pendingIntent2 会覆盖 pendingIntent1

// 正确示例:使用不同的 requestCode
val pendingIntent1 = PendingIntent.getActivity(context, 1001, intent1, flags)
val pendingIntent2 = PendingIntent.getActivity(context, 1002, intent2, flags)

4.5 Flags 标志位

重要 Flags:

kotlin
// 1. FLAG_UPDATE_CURRENT(最常用)
// 如果 PendingIntent 已存在,更新 Intent,保留原始 flags
PendingIntent.FLAG_UPDATE_CURRENT

// 2. FLAG_ONE_SHOT
// 使用一次后自动失效
PendingIntent.FLAG_ONE_SHOT

// 3. FLAG_NO_CREATE
// 如果 PendingIntent 不存在,返回 null
PendingIntent.FLAG_NO_CREATE

// 4. FLAG_CANCEL_CURRENT
// 如果 PendingIntent 已存在,先取消再创建
PendingIntent.FLAG_CANCEL_CURRENT

// 5. FLAG_IMMUTABLE(安全性必需)
// 创建的 PendingIntent 不可变
PendingIntent.FLAG_IMMUTABLE

// 6. FLAG_MUTABLE(Android 12+)
// 可变的 PendingIntent(用于可编辑的通知)
PendingIntent.FLAG_MUTABLE

组合使用示例:

kotlin
// 标准用法(推荐)
val pendingIntent = PendingIntent.getActivity(
    context,
    0,
    intent,
    PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)

// Android 12+ 可编辑通知
val pendingIntent = PendingIntent.getActivity(
    context,
    0,
    intent,
    PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE
)

4.6 安全风险

风险 1:PendingIntent 劫持

kotlin
// ❌ 危险:使用 FLAG_UPDATE_CURRENT 且未设置 FLAG_IMMUTABLE
val intent = Intent(context, VulnerableActivity::class.java)
val pendingIntent = PendingIntent.getActivity(
    context,
    0,
    intent,
    PendingIntent.FLAG_UPDATE_CURRENT
)

// 其他应用可以修改这个 PendingIntent 的 Intent
// 可能导致跳转到恶意应用

// ✅ 安全:添加 FLAG_IMMUTABLE
val pendingIntent = PendingIntent.getActivity(
    context,
    0,
    intent,
    PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)

风险 2:重复触发

kotlin
// ❌ 危险:相同的 requestCode 可能导致重复触发
fun sendNotification(context: Context) {
    val intent = Intent(context, Receiver::class.java)
    val pendingIntent = PendingIntent.getBroadcast(
        context,
        0,  // 固定 requestCode
        intent,
        PendingIntent.FLAG_UPDATE_CURRENT
    )
    // 多次调用会使用同一个 PendingIntent
}

// ✅ 安全:使用动态 requestCode
fun sendNotification(context: Context, notificationId: Int) {
    val intent = Intent(context, Receiver::class.java)
    val pendingIntent = PendingIntent.getBroadcast(
        context,
        notificationId,  // 使用 notificationId 作为 requestCode
        intent,
        PendingIntent.FLAG_UPDATE_CURRENT
    )
}

风险 3:组件暴露

kotlin
// ❌ 危险:exported=true 且无权限控制的组件
<receiver android:name=".Receiver" android:exported="true" />

// ✅ 安全:添加权限控制
<receiver 
    android:name=".Receiver" 
    android:exported="true"
    android:permission="com.example.MY_PERMISSION" />

4.7 Android 版本兼容性

kotlin
// Android 12+ 必须指定 immutable/mutable
fun createPendingIntent(context: Context, intent: Intent, requestCode: Int): PendingIntent {
    return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
        PendingIntent.getActivity(
            context,
            requestCode,
            intent,
            PendingIntent.FLAG_IMMUTABLE
        )
    } else {
        PendingIntent.getActivity(
            context,
            requestCode,
            intent,
            0
        )
    }
}

// 通用工具方法
fun getSafePendingIntent(
    context: Context,
    requestCode: Int,
    intent: Intent,
    isMutable: Boolean = false,
    flags: Int = PendingIntent.FLAG_UPDATE_CURRENT
): PendingIntent {
    val immutableFlag = if (isMutable) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
            PendingIntent.FLAG_MUTABLE
        } else {
            0
        }
    } else {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
            PendingIntent.FLAG_IMMUTABLE
        } else {
            0
        }
    }
    
    return PendingIntent.getActivity(
        context,
        requestCode,
        intent,
        flags or immutableFlag
    )
}

5. Intent Flag 常用标志位

5.1 Activity Flag 分类

任务栈控制 Flag:

kotlin
// 1. FLAG_ACTIVITY_NEW_TASK
// 在新任务栈中启动 Activity
val intent = Intent(this, TargetActivity::class.java).apply {
    addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
}
// 使用场景:Service 启动 Activity、Notification 点击跳转

// 2. FLAG_ACTIVITY_SINGLE_TOP
// 如果栈顶已是该 Activity,复用栈顶
addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP)

// 3. FLAG_ACTIVITY_CLEAR_TOP
// 清除目标 Activity 下方的所有 Activity
addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)

// 4. FLAG_ACTIVITY_CLEAR_TASK
// 清除整个任务栈
addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)

// 5. FLAG_ACTIVITY_MULTIPLE_TASK
// 允许创建多个相同任务
addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK)

生命周期控制 Flag:

kotlin
// 6. FLAG_ACTIVITY_NO_HISTORY
// Activity 不使用后不在历史记录中保留
addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY)

// 7. FLAG_ACTIVITY_CLEAR_TOP
// 清除栈中目标 Activity 上方的所有 Activity
addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)

// 8. FLAG_ACTIVITY_FORWARD_RESULT
// 将结果转发给下一个 Activity
addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT)

UI 控制 Flag:

kotlin
// 9. FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
// 不从最近任务列表中显示
addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS)

// 10. FLAG_ACTIVITY_NEW_DOCUMENT
// 作为新文档打开(Android 7.0+)
addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT)

// 11. FLAG_ACTIVITY_TASK_ON_HOME
// 将任务移到主屏幕
addFlags(Intent.FLAG_ACTIVITY_TASK_ON_HOME)

5.2 Flag 组合使用

场景 1:从 Notification 启动 Activity

kotlin
val intent = Intent(this, MainActivity::class.java).apply {
    // 必须:在新任务栈中启动
    addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
    // 可选:如果栈顶是 MainActivity,复用
    addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP)
    // 可选:清除栈中其他 Activity
    addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
}

场景 2:登录页跳转(清除历史)

kotlin
val intent = Intent(this, HomeActivity::class.java).apply {
    // 在新任务栈中启动
    addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
    // 清除整个任务栈
    addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)
}
startActivity(intent)
// 现在只能从 HomeActivity 开始,无法返回登录页

场景 3:查看大图(不保留在历史中)

kotlin
val intent = Intent(this, ImageViewActivity::class.java).apply {
    // 不保留在历史记录中
    addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY)
    // 用户退出后立即清除
    addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS)
}

场景 4:SingleTask + CLEAR_TOP

kotlin
// 当目标 Activity 的启动模式为 SingleTask 时
val intent = Intent(this, MainActivity::class.java).apply {
    addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
}
// 效果:如果 MainActivity 已存在,清除其上方所有 Activity,并调用 onNewIntent()

5.3 Flag 使用注意事项

kotlin
// ⚠️ 注意 1:FLAG_ACTIVITY_NEW_TASK 的副作用
// 使用 NEW_TASK 时,Activity 会在新的任务栈中启动
// 可能导致用户按 Home 键后回到原任务栈

// ⚠️ 注意 2:FLAG_ACTIVITY_CLEAR_TASK 需要配合 NEW_TASK
val intent = Intent(this, MainActivity::class.java).apply {
    // ❌ 错误:单独使用 CLEAR_TASK 无效
    addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)
    
    // ✅ 正确:配合 NEW_TASK 使用
    addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK)
}

// ⚠️ 注意 3:Flag 的组合顺序
// 某些 Flag 的组合会产生特定效果
addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK)

6. Intent 数据传递限制

6.1 1MB 限制

问题:Intent 传递数据有大约 1MB 的限制(TransactionTooLargeException)

kotlin
// ❌ 错误:传递大数据
class LargeDataActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        
        val largeData = intent.getStringExtra("large_data")  // 可能导致崩溃
    }
}

// 错误信息:
// android.os.TransactionTooLargeException: 
//   data clip too large, parcel size: 10485760 bytes

6.2 安全的传递方式

方案 1:使用文件传递

kotlin
// 发送方
fun sendLargeData(context: Context, data: String) {
    // 写入临时文件
    val file = File(context.cacheDir, "large_data.txt")
    file.writeText(data)
    
    val intent = Intent(context, TargetActivity::class.java)
    intent.putExtra("file_path", file.absolutePath)
    startActivity(intent)
}

// 接收方
class TargetActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        
        val filePath = intent.getStringExtra("file_path")
        val data = File(filePath).readText()
        
        // 使用完后删除
        File(filePath).delete()
    }
}

方案 2:使用 ContentProvider

kotlin
// 发送方
fun sendLargeData(context: Context, data: List<String>) {
    // 将数据存储到 ContentProvider
    val uri = context.contentResolver.insert(
        TempDataContract.CONTENT_URI,
        ContentValues().apply {
            put(TempDataContract.DATA, Json.encode(data))
            put(TempDataContract.CREATED_AT, System.currentTimeMillis())
        }
    )
    
    val intent = Intent(context, TargetActivity::class.java)
    intent.data = uri
    startActivity(intent)
}

// 接收方
class TargetActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        
        val uri = intent.data
        val cursor = contentResolver.query(uri, null, null, null, null)
        cursor?.use {
            if (it.moveToFirst()) {
                val data = it.getString(it.getColumnIndexOrThrow(TempDataContract.DATA))
                // 处理数据
            }
        }
    }
}

方案 3:使用 SharedPreferences

kotlin
// 发送方
fun sendLargeData(context: Context, data: String) {
    // 写入 SharedPreferences
    val prefs = context.getSharedPreferences("temp_data", Context.MODE_PRIVATE)
    prefs.edit().putString("large_data", data).apply()
    
    val intent = Intent(context, TargetActivity::class.java)
    intent.putExtra("has_data", true)
    startActivity(intent)
}

// 接收方
class TargetActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        
        val prefs = getSharedPreferences("temp_data", Context.MODE_PRIVATE)
        val data = prefs.getString("large_data", null)
        
        // 清除数据
        prefs.edit().remove("large_data").apply()
    }
}

方案 4:使用 ViewModel(应用内)

kotlin
// 定义共享 ViewModel
class TempDataViewModel : ViewModel() {
    private var _data: String? = null
    val data: String? get() = _data
    
    fun setData(data: String) {
        _data = data
    }
    
    fun clearData() {
        _data = null
    }
}

// 发送方
class SenderActivity : AppCompatActivity() {
    private val viewModel: TempDataViewModel by viewModels()
    
    fun sendToTarget() {
        viewModel.setData(largeData)
        
        val intent = Intent(this, TargetActivity::class.java)
        startActivity(intent)
    }
}

// 接收方
class TargetActivity : AppCompatActivity() {
    private val viewModel: TempDataViewModel by viewModels()
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        
        val data = viewModel.data
        viewModel.clearData()
    }
}

6.3 Intent 能传递的数据类型

kotlin
// 基本类型
intent.putExtra("int", 123)
intent.putExtra("long", 123L)
intent.putExtra("float", 3.14f)
intent.putExtra("double", 3.14)
intent.putExtra("boolean", true)
intent.putExtra("char", 'A')

// 字符串
intent.putExtra("string", "text")
intent.putStringArrayListExtra("strings", arrayListOf("a", "b"))

// 可序列化的对象
intent.putExtra("serializable", UserSerializable(name, age))
intent.putSerializableExtras("users", HashSet(arrayOf(user1, user2)))

// Parcelable(推荐)
intent.putExtra("parcelable", UserParcelable(name, age))
intent.putParcelableArrayListExtra("users", arrayListOf(user1, user2))

// Bundle
intent.putExtra("bundle", bundle)

// ClipData(拖拽、剪贴板)
intent.clipData = ClipData.newUri(...)

7. ClipData 传递大文件

7.1 什么是 ClipData

ClipData 用于封装多个 Uri,支持拖拽、剪贴板和数据共享。

7.2 使用 ClipData 传递文件

kotlin
// 方式 1:单个文件
fun shareSingleFile(context: Context, fileUri: Uri) {
    val clipData = ClipData.newUri(
        context,
        "File Share",
        fileUri
    )
    
    val intent = Intent(Intent.ACTION_SEND).apply {
        type = "application/*"
        putExtra(Intent.EXTRA_STREAM, fileUri)
        clipData = clipData
        addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
    }
    
    startActivity(Intent.createChooser(intent, "分享文件"))
}

// 方式 2:多个文件
fun shareMultipleFiles(context: Context, fileUris: List<Uri>) {
    val clipData = ClipData.newUri(
        context,
        "File Share",
        fileUris[0]  // 第一个 URI
    )
    
    // 添加其他 URI
    for (i in 1 until fileUris.size) {
        clipData.addItem(
            ClipData.Item(fileUris[i]).apply {
                icon = context.getDrawable(R.drawable.ic_file)
            }
        )
    }
    
    val intent = Intent(Intent.ACTION_SEND).apply {
        type = "application/*"
        clipData = clipData
        addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
    }
    
    startActivity(Intent.createChooser(intent, "分享文件"))
}

7.3 接收 ClipData

kotlin
class ClipDataReceiverActivity : AppCompatActivity() {
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        
        val clipData = intent.clipData
        clipData?.let {
            for (i in 0 until it.itemCount) {
                val item = it.getItemAt(i)
                
                // 获取 URI
                val uri = item.uri
                val text = item.text
                
                // 处理数据
                processItem(uri, text)
            }
        }
    }
    
    private fun processItem(uri: Uri?, text: String?) {
        // 处理文件
    }
}

7.4 拖拽功能

kotlin
// 设置拖拽
class DraggableImageView(context: Context) : ImageView(context) {
    
    init {
        isClickable = true
        isLongClickable = true
    }
    
    override fun onLongClick(v: View): Boolean {
        val clipData = ClipData.newUri(
            context,
            "Image Drag",
            imageUri
        )
        
        val dragShadowBuilder = View.DragShadowBuilder(this)
        
        startDragAndDrop(
            clipData,
            DragShadowBuilder(this),
            null,
            0
        )
        
        return true
    }
}

// 接收拖拽
class DropTargetActivity : AppCompatActivity() {
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        
        val dropZone = findViewById<View>(R.id.drop_zone)
        
        dropZone.setOnDragListener { v, event ->
            when (event.action) {
                DragEvent.ACTION_DROP -> {
                    val clipData = event.clipData
                    // 处理接收的数据
                    true
                }
                DragEvent.ACTION_DRAG_ENTERED -> true
                else -> false
            }
        }
    }
}

8. 面试考点

8.1 基础题

Q1: Intent 的作用是什么?

A: Intent 是 Android 组件间通信的机制,用于启动 Activity、Service、发送广播,以及传递数据。

Q2: 显式 Intent 和隐式 Intent 的区别?

A:

  • 显式 Intent:明确指定目标组件,用于应用内调用
  • 隐式 Intent:通过 Action/Data/Category 让系统匹配,用于跨应用调用

Q3: IntentFilter 的匹配规则?

A: IntentFilter 通过 Action、Data、Category 三要素匹配 Intent,三者必须全部匹配。

8.2 进阶题

Q4: PendingIntent 的作用和类型?

A:

  • 作用:延迟执行 Intent,授权其他应用使用
  • 类型:getActivity()、getBroadcast()、getService()

Q5: PendingIntent 的安全风险?

A:

  • PendingIntent 劫持:其他应用修改 Intent
  • 重复触发:相同的 requestCode
  • 解决方案:使用 FLAG_IMMUTABLE、动态 requestCode

Q6: Intent 传递数据的限制?

A: Intent 传递数据约 1MB 限制,超过会抛出 TransactionTooLargeException。

8.3 高级题

Q7: 如何传递大文件?

A:

  • 使用文件路径 + 临时文件
  • 使用 ContentProvider
  • 使用 ClipData 传递 Uri
  • 使用 SharedPreferences/ViewModel

Q8: FLAG_ACTIVITY_NEW_TASK 的作用和副作用?

A:

  • 作用:在新任务栈中启动 Activity
  • 副作用:可能导致任务栈混乱

Q9: 如何防止 PendingIntent 劫持?

A:

  • 使用 FLAG_IMMUTABLE
  • 验证目标组件
  • 使用动态 requestCode

总结

Intent 是 Android 组件通信的核心,掌握要点:

  1. 区分显式和隐式 Intent 的适用场景
  2. 理解 IntentFilter 的匹配规则
  3. 注意 PendingIntent 的安全风险
  4. 合理使用 Intent Flag 控制任务栈
  5. 避免 Intent 传递大数据
  6. 使用 ClipData 传递文件