Service、前台服务与后台限制
前言
很多 Android 项目的后台问题,表面看是"服务没启动""保活失败""通知没显示",实际是三类边界没有分清:
- Service 不是后台线程。它是一个应用组件,默认回调仍运行在应用主线程。
- 前台服务不是保活万能钥匙。它必须有用户可感知的任务、通知、类型声明和权限前提。
- 后台限制不是单个 API 限制。从 Android 8.0 的后台服务限制,到 Android 12 的后台启动前台服务限制,再到 Android 14/15 的前台服务类型、权限和超时,系统一直在收紧"后台持续占资源"的路径。
线上常见事故通常长这样:
- 在
onStartCommand()里直接做大文件上传,结果主线程卡顿甚至 ANR。 - 用
startService()在后台启动同步任务,Android 8.0+ 设备上被系统拦截或很快停止。 - 调了
startForegroundService(),但 5 秒内没有startForeground(),应用被判定 ANR。 - Android 12+ 从后台启动前台服务,抛出
ForegroundServiceStartNotAllowedException。 - Android 14+ 只写了
android:foregroundServiceType,忘了对应权限或运行时前提。 - Android 15 上
dataSync或mediaProcessing前台服务累计运行超时,收到Service.onTimeout()后没有及时停止,最终 ANR。
本文按"API 用法 -> 系统限制 -> AOSP 调用链 -> 工程实践"的顺序重建 Service 的心智模型。版本相关结论以 2026-06-30 查阅的 Android Developers 文档和 AOSP refs/heads/main 为准;厂商系统可能有额外电量策略,但不应作为架构设计的唯一依据。
前置知识
建议先掌握:
- A019:Android 应用进程、组件与系统架构概览。
- A020:Activity 生命周期完整解析。
- A021:Activity 启动模式与任务栈。
- A023:Intent 显式、隐式与 PendingIntent。
- Kotlin 协程、通知渠道、Manifest 声明、进程优先级的基础概念。
A023 解决"如何描述一次组件请求"。本文继续回答:这个请求如果指向 Service,系统如何创建、分发、限制和回收它。
核心概念
Service 是组件,不是线程
Service 的最大误解是"它运行在后台线程"。官方 API 和 AOSP 都不是这样设计的:Service 是运行在应用进程里的组件实例,onCreate()、onStartCommand()、onBind()、onDestroy() 等回调默认在主线程执行。
所以这段代码是错误示范:
kotlin
class UploadService : Service() {
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
uploadLargeFileSynchronously() // 错误:阻塞主线程
stopSelf(startId)
return START_NOT_STICKY
}
override fun onBind(intent: Intent?): IBinder? = null
}
正确的理解是:Service 适合承载"一个不直接依附于 Activity UI 的任务入口或跨组件能力",但耗时工作仍要交给协程、线程池、WorkManager、下载器、媒体框架或专门的系统 API。
Started Service、Bound Service、Foreground Service
Service 的运行状态可以从三个维度看:
| 类型 | 入口 | 生命周期由谁决定 | 典型场景 | 常见误区 |
|---|---|---|---|---|
| Started Service | startService() / startForegroundService() |
调用方启动,服务自己或外部停止 | 短任务入口、老代码兼容、前台服务启动 | 以为多次 start 需要多次 stop |
| Bound Service | bindService() |
绑定连接数量和 flag | 同进程能力、AIDL、媒体控制、设备连接 | 以为绑定后一定在独立线程 |
| Foreground Service | startForeground() 后的 Service |
Service + 前台通知 + 类型约束 | 导航、通话、媒体播放、用户可见上传 | 当成静默保活通道 |
一个 Service 可以同时是 started 和 bound。例如音乐播放服务先被 startForegroundService() 启动并进入前台,然后 Activity 再 bindService() 控制播放。只有当 started 状态停止、所有绑定解除,并且系统没有重启策略时,Service 才会真正销毁。
返回值决定被杀后的重启语义
onStartCommand() 的返回值不是"执行成功/失败",而是进程被系统杀死后的恢复策略:
| 返回值 | 含义 | 适合场景 |
|---|---|---|
START_NOT_STICKY |
进程被杀后不自动重建,除非还有新 Intent | 一次性同步、可丢弃刷新 |
START_STICKY |
系统可重建服务,但 Intent 可能为 null | 播放、连接等需要恢复的持续状态 |
START_REDELIVER_INTENT |
重建后重新投递最后一个 Intent | 必须至少处理一次的命令,但仍要自己去重 |
START_STICKY_COMPATIBILITY |
兼容旧行为,不保证回调 | 老代码兼容,现代项目少用 |
这不是可靠任务队列。需要"网络可用时重试、进程死后继续、约束满足再执行"的任务,优先用 WorkManager;需要实时用户可见任务,再考虑前台服务。
整体架构
Service 的核心链路可以分成 App 层、Framework 客户端层和 system_server 管理层:
这张图解释了两个关键点:
- App 调用的是
ContextAPI,但真正的状态管理在 system_server 的ActiveServices和ServiceRecord。 - Service 实例创建和回调执行在应用进程的
ActivityThread,所以主线程阻塞会直接影响应用响应。
工作流程
Started Service 的调用链
一次 startService() 大致经历这些步骤:
AOSP Service.java 的类注释说明:Context.startService() 会在需要时创建 Service 并调用 onCreate(),然后调用 onStartCommand();多次 startService() 不会嵌套增加"引用计数",但会触发多次 onStartCommand()。这也是为什么服务停止应围绕任务完成状态,而不是简单地"start 了几次就 stop 几次"。
ActivityThread.handleServiceArgs() 会在应用进程内调用 Service.onStartCommand()。AOSP ActivityThread.java 在该路径里还会调用 QueuedWork.waitToFinish(),这意味着 Service 回调里的异步收尾也可能拖慢分发链路。
Bound Service 的调用链
Bound Service 更像"组件能力连接":
onBind() 返回的是 IBinder。如果同进程使用,可以返回本地 Binder 暴露 Kotlin/Java 对象能力;如果跨进程,需要 AIDL 或稳定的 Binder 协议。无论哪种形式,都不要在 Binder 调用线程或主线程里做长时间阻塞。
前台服务的启动窗口
前台服务有两步:
ContextCompat.startForegroundService(context, intent)或Context.startForegroundService(intent)。- Service 创建后尽快调用
ServiceCompat.startForeground(...)或Service.startForeground(...),展示用户可见通知并声明类型。
Android 8.0 引入 startForegroundService() 后,系统要求应用在服务创建后很短时间内调用 startForeground()。官方后台执行限制文档明确说明:系统创建服务后,应用有 5 秒时间调用 startForeground() 显示用户可见通知;否则系统会停止服务并将应用判定为 ANR。
因此,前台服务的第一条工程原则是:先展示合规通知,再做耗时初始化。
API 使用
1. 普通 started service:只做短入口
kotlin
class SyncKickService : Service() {
private val scope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
val accountId = intent?.getStringExtra(EXTRA_ACCOUNT_ID)
if (accountId == null) {
stopSelf(startId)
return START_NOT_STICKY
}
scope.launch {
try {
syncOnce(accountId)
} finally {
stopSelf(startId)
}
}
return START_NOT_STICKY
}
override fun onDestroy() {
scope.cancel()
super.onDestroy()
}
override fun onBind(intent: Intent?): IBinder? = null
private suspend fun syncOnce(accountId: String) {
// Call repository / network layer here. Keep it idempotent.
}
companion object {
private const val EXTRA_ACCOUNT_ID = "account_id"
}
}
这个例子仍然只适合短任务入口。若任务需要网络约束、充电约束、失败重试或进程死亡后继续,应该迁移到 WorkManager,而不是让 started service 常驻。
2. 前台服务:先通知,后执行
Manifest 至少需要声明 Service、前台服务权限和类型。Android 14+ 对不同前台服务类型有更细的权限和运行时前提,下面以数据同步为例:
xml
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />
<application>
<service
android:name=".sync.FileUploadService"
android:exported="false"
android:foregroundServiceType="dataSync" />
</application>
Service 内部应尽早调用 startForeground():
kotlin
class FileUploadService : Service() {
private val scope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
override fun onCreate() {
super.onCreate()
createUploadNotificationChannel()
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
val notification = buildUploadingNotification(progress = 0)
ServiceCompat.startForeground(
this,
NOTIFICATION_ID,
notification,
ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC,
)
val fileId = intent?.getStringExtra(EXTRA_FILE_ID)
if (fileId == null) {
stopSelf(startId)
return START_NOT_STICKY
}
scope.launch {
try {
uploadFile(fileId) { progress ->
updateNotification(progress)
}
} finally {
stopSelf(startId)
}
}
return START_NOT_STICKY
}
override fun onTimeout(startId: Int, fgsType: Int) {
// Android 15+ can call this for timed foreground service types.
scope.cancel("Foreground service timeout")
stopSelf(startId)
}
override fun onDestroy() {
scope.cancel()
super.onDestroy()
}
override fun onBind(intent: Intent?): IBinder? = null
private fun createUploadNotificationChannel() = Unit
private fun buildUploadingNotification(progress: Int): Notification = TODO()
private fun updateNotification(progress: Int) = Unit
private suspend fun uploadFile(fileId: String, onProgress: (Int) -> Unit) = Unit
companion object {
private const val NOTIFICATION_ID = 1001
private const val EXTRA_FILE_ID = "file_id"
}
}
调用方要处理 Android 12+ 后台启动限制:
kotlin
fun startUpload(context: Context, fileId: String) {
val intent = Intent(context, FileUploadService::class.java)
.putExtra("file_id", fileId)
try {
ContextCompat.startForegroundService(context, intent)
} catch (e: ForegroundServiceStartNotAllowedException) {
// App is background and no exemption applies. Persist intent and ask user to resume.
enqueueUploadWithWorkManager(fileId)
showUploadWillContinueNotification(context, fileId)
}
}
这里的降级策略很关键:后台启动前台服务失败,不应无限重试;要把任务转成 WorkManager、通知点击后的用户可见入口,或等应用回到前台再启动。
3. Bound Service:把连接生命周期写清楚
kotlin
class PlayerService : Service() {
private val binder = PlayerBinder()
inner class PlayerBinder : Binder() {
fun service(): PlayerService = this@PlayerService
}
override fun onBind(intent: Intent?): IBinder = binder
fun play(mediaId: String) {
// Delegate to a player component; do not block the main thread.
}
}
class PlayerController(
private val context: Context,
) {
private var service: PlayerService? = null
private val connection = object : ServiceConnection {
override fun onServiceConnected(name: ComponentName, binder: IBinder) {
service = (binder as PlayerService.PlayerBinder).service()
}
override fun onServiceDisconnected(name: ComponentName) {
service = null
}
}
fun bind() {
val intent = Intent(context, PlayerService::class.java)
context.bindService(intent, connection, Context.BIND_AUTO_CREATE)
}
fun unbind() {
service = null
context.unbindService(connection)
}
}
如果这个连接由 Activity 持有,通常在 onStart() 绑定、onStop() 解绑;如果是全局业务能力,要明确由 Application 级对象或依赖注入容器管理,避免 Activity 泄漏。
后台限制演进
Android 8.0:后台服务限制
Android 8.0 后,后台应用不能随意创建后台服务。官方后台执行限制文档给出的替代路径是:如果需要启动用户可见的前台任务,用 startForegroundService(),并在服务创建后 5 秒内调用 startForeground();否则使用 JobScheduler,现代项目通常用 WorkManager 封装这类可延迟任务。
这改变了 Service 的定位:它不再是"后台任务默认容器",而是"组件入口 + 用户可见持续任务 + 绑定能力"的组合。
Android 12:后台启动前台服务限制
Android 12 以后,目标 SDK 31+ 的应用通常不能在后台启动前台服务,除非符合官方列出的例外场景。若不符合,系统会抛出 ForegroundServiceStartNotAllowedException。
这条限制对架构影响很大:
- 后台静默上传、同步、轮询,不应临时拉起前台服务。
- 用户点击通知、显式操作、部分高优先级事件等例外场景,需要按官方条件判断。
- 代码必须能处理启动失败,而不是假设
startForegroundService()一定成功。
Android 14:前台服务类型更严格
Android 14 对前台服务类型的声明、权限和运行时前提更严格。只声明 android:foregroundServiceType="camera" 或 location" 还不够,应用还要满足对应权限和可用状态。涉及 while-in-use 权限的类型,如相机、麦克风、位置,后台启动时尤其容易失败,因为后台时应用可能并不具备对应的"正在使用时"权限上下文。
工程上应把前台服务类型当成契约,而不是标签:
| 任务 | 推荐类型 | 额外关注 |
|---|---|---|
| 导航定位 | location |
前台可见、定位权限、通知说明 |
| 语音通话 | microphone / phoneCall |
运行时权限、用户明确操作 |
| 音乐播放 | mediaPlayback |
媒体会话、通知控制 |
| 文件上传 | dataSync |
Android 15 超时、可否改 WorkManager |
| 视频处理 | mediaProcessing |
Android 15 超时、进度和取消 |
| 蓝牙设备连接 | connectedDevice |
蓝牙权限、连接状态 |
Android 15:部分前台服务类型有超时
Android 15 对 dataSync 和 mediaProcessing 等前台服务类型引入累计运行时间限制。官方超时文档说明:如果某类前台服务在 24 小时窗口内运行达到限制,再尝试启动同类型前台服务会抛出 ForegroundServiceStartNotAllowedException;系统也会通过 Service.onTimeout(int, int) 通知服务尽快停止,未及时停止可能导致 ANR。
AOSP ActiveServices.java 里也能看到相关变更开关 FGS_INTRODUCE_TIME_LIMITS,注释写明某些类型的前台服务有时间限制,超时后会回调 Service.onTimeout(int, int),服务必须在几秒内停止,否则会被判定 ANR。ServiceRecord.java 则保存了 foregroundServiceType 和短前台服务的超时时间计算。
这意味着前台服务不能再承担"无限长数据同步"的职责。上传/处理任务要做切片、进度持久化、可恢复、可取消;后台可延迟任务仍优先 WorkManager。
源码分析
Service.java:组件契约
源码路径:frameworks/base/core/java/android/app/Service.java。
关键点:
- 类注释区分 started 和 bound 两种基本使用方式。
onStartCommand()的文档说明多次startService()会产生多次回调,但停止并不是嵌套计数。startForeground()文档说明 Android Q 起可在 manifest 声明前台服务类型,Android S 起目标 SDK 31+ 调用时要满足类型约束。- Android 15 相关路径增加了
onTimeout(int, int),用于前台服务超时后的快速收尾。
Service 只是 App 进程内的对象,系统并不会替你开线程,也不会替你保证任务一定完成。
ActivityThread.java:应用进程内创建与分发
源码路径:frameworks/base/core/java/android/app/ActivityThread.java。
关键调用:
handleCreateService(...):在应用进程内通过 LoadedApk、ClassLoader 和 Instrumentation 创建 Service 实例,attach 上下文,并调用onCreate()。handleServiceArgs(...):接收 system_server 分发的启动参数,调用Service.onStartCommand(...)或onTaskRemoved(...)。handleTimeoutService(...):前台服务超时场景下回调 Service 的 timeout 处理。
这解释了为什么 Service 回调中的阻塞会影响主线程。Service 的生命周期调度和 Activity 一样,都要经过 ActivityThread 主线程消息处理。
ActiveServices.java:system_server 的 Service 状态机
源码路径:frameworks/base/services/core/java/com/android/server/am/ActiveServices.java。
关键调用:
startServiceLocked(...):处理启动请求,包括调用方、用户、权限、后台启动策略和前台服务要求。bringUpServiceLocked(...):在需要时拉起进程或创建服务。sendServiceArgsLocked(...):把 pending start 参数发到应用进程。setServiceForegroundLocked(...):处理startForeground()上报,校验通知、类型、状态并更新 ServiceRecord。
ActiveServices 是后台限制真正落地的地方。App 侧看到的是异常、ANR 或服务停止,system_server 侧维护的是一组状态、超时、权限和进程优先级决策。
ServiceRecord.java:一次服务运行的系统侧记录
源码路径:frameworks/base/services/core/java/com/android/server/am/ServiceRecord.java。
它保存了 Service 的核心状态:
foregroundServiceType:当前前台服务类型位掩码。startRequested、callStart、pendingStarts:started service 分发状态。executeNesting、executingStart:执行中状态和超时相关信息。fgDisplayTime、foregroundNoti:前台通知和展示状态。- short foreground service timeout 相关时间计算。
如果把 Service 看成 App 层对象,ServiceRecord 就是 system_server 眼中的那份运行账本。
实战案例:可恢复文件上传
假设产品要求上传巡检视频:用户点击"上传",离开页面后仍希望看到进度,但没有必要无限占用前台服务。
推荐设计:
- 用户点击上传时,把任务写入本地数据库,状态为
Queued。 - 如果 App 在前台且用户需要实时进度,启动
dataSync前台服务。 - Service 立即展示通知,并在通知里提供取消入口。
- 上传分片执行,每个分片完成后持久化进度。
- 如果
ForegroundServiceStartNotAllowedException或 Android 15 超时发生,停止前台服务,改由 WorkManager 在合适约束下继续。 - 用户再次打开页面时,从数据库恢复进度,而不是依赖 Service 内存状态。
核心状态机:
这套设计的重点不是"永远让 Service 活着",而是让任务状态独立于 Service。Service 只是用户可见阶段的执行载体。
性能优化
1. 不在主线程做耗时工作
Service 回调默认在主线程。耗时 I/O、数据库批量操作、压缩、上传、蓝牙阻塞调用都要转移到合适调度器。即使使用协程,也要确认实际执行在 Dispatchers.IO 或专用线程池,而不是在主线程里启动后继续阻塞。
2. 控制前台服务数量和时长
每个前台服务都意味着通知、进程优先级提升、电量消耗和用户注意力占用。能合并的任务合并,能切片的任务切片,能转 WorkManager 的后台任务不要占前台服务。
3. 通知更新节流
上传进度不要每个字节都刷新通知。常见策略是按百分比变化、固定时间窗口或重要状态节点更新。频繁通知更新会增加 Binder 调用、系统 UI 压力和耗电。
4. 任务幂等和去重
onStartCommand() 可能被多次调用,进程死亡后也可能重新投递 Intent。任务参数应包含稳定 ID,业务层根据 ID 去重,避免重复上传、重复扣费或重复写数据库。
5. 可观测性
为 Service 增加这些日志和指标:
- 启动来源:前台页面、通知、广播、WorkManager、系统回调。
- 启动结果:成功、FGS denied、权限缺失、超时、用户取消。
- 执行时长:总时长、前台时长、后台 fallback 时长。
- 停止原因:完成、失败、取消、超时、进程销毁。
这些指标比"服务有没有活着"更能指导优化。
常见问题
1. Service 会运行在独立进程吗?
默认不会。Service 运行在应用默认进程,除非 manifest 用 android:process 指定其他进程。即使在独立进程,回调也仍有该进程自己的主线程,不代表自动变成后台线程。
2. startService() 和 startForegroundService() 怎么选?
如果任务需要用户可见的持续执行,并且符合前台服务类型和启动条件,用 startForegroundService() 后立即 startForeground()。如果只是可延迟后台任务,优先 WorkManager。Android 8.0+ 后,后台应用不应依赖 startService() 创建长时间后台服务。
3. 前台服务通知可以隐藏吗?
不应该以隐藏通知为目标。前台服务的设计前提就是用户可感知。不同系统版本和厂商对通知展示时机有差异,但工程上应提供清晰通知、进度、取消入口和合理文案。
4. 为什么后台启动前台服务会抛异常?
Android 12+ 限制目标 SDK 31+ 应用从后台启动前台服务,除非命中特定例外。系统通过 ForegroundServiceStartNotAllowedException 把这个策略暴露给 App。正确做法是捕获异常并降级,而不是重试到成功。
5. Android 15 的 onTimeout() 要做什么?
尽快停止当前前台服务,持久化进度,释放资源,并把剩余任务转入可恢复队列。不要在 onTimeout() 里继续做长时间收尾,否则仍可能 ANR。
6. 绑定 Service 要不要手动停止?
只绑定、未 started 的 Service 在所有连接解绑后可被销毁。既 started 又 bound 的 Service,需要同时满足"停止 started 状态"和"无绑定连接"才会销毁。
7. Service 能替代 WorkManager 吗?
不能。WorkManager 解决的是可延迟、可约束、可重试、进程死亡后仍可调度的后台任务;Service 解决的是组件生命周期和用户可见持续任务。两者经常组合使用:前台可见阶段用 Service,后台恢复阶段用 WorkManager。
面试考点
基础题
- Service 和 Thread 的区别是什么?
- Started Service 与 Bound Service 的生命周期差异是什么?
onStartCommand()的几个返回值分别代表什么?- 为什么 Android 8.0 引入
startForegroundService()? - 前台服务为什么必须显示通知?
进阶题
- 多次
startService()后是否需要多次stopService()?为什么? - Android 12+ 后台启动前台服务失败应该如何降级?
- Android 14+ 前台服务类型和权限有什么关系?
- Android 15 前台服务超时对数据同步架构有什么影响?
- 如何设计一个既能显示实时进度、又能进程死亡后恢复的上传任务?
源码题
ActivityThread.handleCreateService()做了哪些事?ActivityThread.handleServiceArgs()和Service.onStartCommand()的关系是什么?ActiveServices.startServiceLocked()为什么是后台限制的重要入口?ServiceRecord.foregroundServiceType用来记录什么?- 前台服务超时为什么会回调到
Service.onTimeout()?
系统设计题
设计一个"运动轨迹记录"功能,要求:
- 用户开始运动后持续记录位置。
- 应用退到后台仍显示前台服务通知。
- Android 12+ 不从后台静默启动。
- Android 14+ 满足 location 前台服务类型和权限。
- 用户停止运动后及时释放定位、通知和服务。
- 轨迹数据进程死亡后可恢复。
合格答案应包含:用户显式启动入口、位置权限检查、foregroundServiceType="location"、前台通知、数据库持久化、异常降级、绑定 UI 控制、停止与资源释放策略。
总结
Service 的正确心智模型是:
- 它是 Android 组件,不是线程容器。
- Started、bound、foreground 是三个维度,不是互斥分类。
- 后台限制的目标是让长期资源占用回到用户可见、可解释、可取消的路径。
- 现代后台任务架构应把任务状态持久化,把前台服务作为用户可见阶段,把 WorkManager 作为可延迟恢复阶段。
写 Service 代码时,先问四个问题:这个任务是否必须现在执行?用户是否能感知?是否满足当前 Android 版本的启动条件和类型权限?进程死亡后能否恢复?如果答案不清楚,直接写一个常驻 Service 往往就是技术债的开始。
扩展阅读
- Android Developers: Services overview, developer.android.com/develop/bac...
- Android Developers: Service API reference, developer.android.com/reference/a...
- Android Developers: Foreground services overview, developer.android.com/develop/bac...
- Android Developers: Launch a foreground service, developer.android.com/develop/bac...
- Android Developers: Foreground service types, developer.android.com/develop/bac...
- Android Developers: Foreground service timeouts, developer.android.com/develop/bac...
- Android Developers: Restrictions on starting foreground services from the background, developer.android.com/develop/bac...
- Android Developers: Android 8.0 background execution limits, developer.android.com/about/versi...
- Android Developers: Android 15 behavior changes, developer.android.com/about/versi...
- AOSP:
frameworks/base/core/java/android/app/Service.java - AOSP:
frameworks/base/core/java/android/app/ActivityThread.java - AOSP:
frameworks/base/services/core/java/com/android/server/am/ActiveServices.java - AOSP:
frameworks/base/services/core/java/com/android/server/am/ServiceRecord.java - AndroidX:
androidx.core.app.ServiceCompat - 系列文章:A019 Android 应用进程、组件与系统架构概览
- 系列文章:A020 Activity 生命周期完整解析
- 系列文章:A021 Activity 启动模式与任务栈
- 系列文章:A023 Intent 显式、隐式与 PendingIntent
- 下一篇:A025 BroadcastReceiver 静态注册与动态注册 re/java/com/android/server/am/ActiveServices.java", "frameworks/base/services/core/java/com/android/server/am/ServiceRecord.java", "frameworks/base/core/java/android/content/Context.java" ] last_verified_android: "Android Developers docs checked on 2026-06-30; AOSP refs/heads/main checked on 2026-06-30" seo_title: "Service、前台服务与后台限制:生命周期、启动约束、源码调用链与性能实践" seo_description: "系统解析 Android Service、started service、bound service、foreground service、后台执行限制、Android 12+ 前台服务启动限制、Android 14+ 类型权限、Android 15 超时机制、AOSP 源码调用链、实战案例、性能优化、FAQ 和面试考点。"
Service、前台服务与后台限制
前言
很多 Android 项目的后台问题,表面看是"服务没启动""保活失败""通知没显示",实际是三类边界没有分清:
- Service 不是后台线程。它是一个应用组件,默认回调仍运行在应用主线程。
- 前台服务不是保活万能钥匙。它必须有用户可感知的任务、通知、类型声明和权限前提。
- 后台限制不是单个 API 限制。从 Android 8.0 的后台服务限制,到 Android 12 的后台启动前台服务限制,再到 Android 14/15 的前台服务类型、权限和超时,系统一直在收紧"后台持续占资源"的路径。
线上常见事故通常长这样:
- 在
onStartCommand()里直接做大文件上传,结果主线程卡顿甚至 ANR。 - 用
startService()在后台启动同步任务,Android 8.0+ 设备上被系统拦截或很快停止。 - 调了
startForegroundService(),但 5 秒内没有startForeground(),应用被判定 ANR。 - Android 12+ 从后台启动前台服务,抛出
ForegroundServiceStartNotAllowedException。 - Android 14+ 只写了
android:foregroundServiceType,忘了对应权限或运行时前提。 - Android 15 上
dataSync或mediaProcessing前台服务累计运行超时,收到Service.onTimeout()后没有及时停止,最终 ANR。
本文按"API 用法 -> 系统限制 -> AOSP 调用链 -> 工程实践"的顺序重建 Service 的心智模型。版本相关结论以 2026-06-30 查阅的 Android Developers 文档和 AOSP refs/heads/main 为准;厂商系统可能有额外电量策略,但不应作为架构设计的唯一依据。
前置知识
建议先掌握:
- A019:Android 应用进程、组件与系统架构概览。
- A020:Activity 生命周期完整解析。
- A021:Activity 启动模式与任务栈。
- A023:Intent 显式、隐式与 PendingIntent。
- Kotlin 协程、通知渠道、Manifest 声明、进程优先级的基础概念。
A023 解决"如何描述一次组件请求"。本文继续回答:这个请求如果指向 Service,系统如何创建、分发、限制和回收它。
核心概念
Service 是组件,不是线程
Service 的最大误解是"它运行在后台线程"。官方 API 和 AOSP 都不是这样设计的:Service 是运行在应用进程里的组件实例,onCreate()、onStartCommand()、onBind()、onDestroy() 等回调默认在主线程执行。
所以这段代码是错误示范:
kotlin
class UploadService : Service() {
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
uploadLargeFileSynchronously() // 错误:阻塞主线程
stopSelf(startId)
return START_NOT_STICKY
}
override fun onBind(intent: Intent?): IBinder? = null
}
正确的理解是:Service 适合承载"一个不直接依附于 Activity UI 的任务入口或跨组件能力",但耗时工作仍要交给协程、线程池、WorkManager、下载器、媒体框架或专门的系统 API。
Started Service、Bound Service、Foreground Service
Service 的运行状态可以从三个维度看:
| 类型 | 入口 | 生命周期由谁决定 | 典型场景 | 常见误区 |
|---|---|---|---|---|
| Started Service | startService() / startForegroundService() |
调用方启动,服务自己或外部停止 | 短任务入口、老代码兼容、前台服务启动 | 以为多次 start 需要多次 stop |
| Bound Service | bindService() |
绑定连接数量和 flag | 同进程能力、AIDL、媒体控制、设备连接 | 以为绑定后一定在独立线程 |
| Foreground Service | startForeground() 后的 Service |
Service + 前台通知 + 类型约束 | 导航、通话、媒体播放、用户可见上传 | 当成静默保活通道 |
一个 Service 可以同时是 started 和 bound。例如音乐播放服务先被 startForegroundService() 启动并进入前台,然后 Activity 再 bindService() 控制播放。只有当 started 状态停止、所有绑定解除,并且系统没有重启策略时,Service 才会真正销毁。
返回值决定被杀后的重启语义
onStartCommand() 的返回值不是"执行成功/失败",而是进程被系统杀死后的恢复策略:
| 返回值 | 含义 | 适合场景 |
|---|---|---|
START_NOT_STICKY |
进程被杀后不自动重建,除非还有新 Intent | 一次性同步、可丢弃刷新 |
START_STICKY |
系统可重建服务,但 Intent 可能为 null | 播放、连接等需要恢复的持续状态 |
START_REDELIVER_INTENT |
重建后重新投递最后一个 Intent | 必须至少处理一次的命令,但仍要自己去重 |
START_STICKY_COMPATIBILITY |
兼容旧行为,不保证回调 | 老代码兼容,现代项目少用 |
这不是可靠任务队列。需要"网络可用时重试、进程死后继续、约束满足再执行"的任务,优先用 WorkManager;需要实时用户可见任务,再考虑前台服务。
整体架构
Service 的核心链路可以分成 App 层、Framework 客户端层和 system_server 管理层:
这张图解释了两个关键点:
- App 调用的是
ContextAPI,但真正的状态管理在 system_server 的ActiveServices和ServiceRecord。 - Service 实例创建和回调执行在应用进程的
ActivityThread,所以主线程阻塞会直接影响应用响应。
工作流程
Started Service 的调用链
一次 startService() 大致经历这些步骤:
AOSP Service.java 的类注释说明:Context.startService() 会在需要时创建 Service 并调用 onCreate(),然后调用 onStartCommand();多次 startService() 不会嵌套增加"引用计数",但会触发多次 onStartCommand()。这也是为什么服务停止应围绕任务完成状态,而不是简单地"start 了几次就 stop 几次"。
ActivityThread.handleServiceArgs() 会在应用进程内调用 Service.onStartCommand()。AOSP ActivityThread.java 在该路径里还会调用 QueuedWork.waitToFinish(),这意味着 Service 回调里的异步收尾也可能拖慢分发链路。
Bound Service 的调用链
Bound Service 更像"组件能力连接":
onBind() 返回的是 IBinder。如果同进程使用,可以返回本地 Binder 暴露 Kotlin/Java 对象能力;如果跨进程,需要 AIDL 或稳定的 Binder 协议。无论哪种形式,都不要在 Binder 调用线程或主线程里做长时间阻塞。
前台服务的启动窗口
前台服务有两步:
ContextCompat.startForegroundService(context, intent)或Context.startForegroundService(intent)。- Service 创建后尽快调用
ServiceCompat.startForeground(...)或Service.startForeground(...),展示用户可见通知并声明类型。
Android 8.0 引入 startForegroundService() 后,系统要求应用在服务创建后很短时间内调用 startForeground()。官方后台执行限制文档明确说明:系统创建服务后,应用有 5 秒时间调用 startForeground() 显示用户可见通知;否则系统会停止服务并将应用判定为 ANR。
因此,前台服务的第一条工程原则是:先展示合规通知,再做耗时初始化。
API 使用
1. 普通 started service:只做短入口
kotlin
class SyncKickService : Service() {
private val scope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
val accountId = intent?.getStringExtra(EXTRA_ACCOUNT_ID)
if (accountId == null) {
stopSelf(startId)
return START_NOT_STICKY
}
scope.launch {
try {
syncOnce(accountId)
} finally {
stopSelf(startId)
}
}
return START_NOT_STICKY
}
override fun onDestroy() {
scope.cancel()
super.onDestroy()
}
override fun onBind(intent: Intent?): IBinder? = null
private suspend fun syncOnce(accountId: String) {
// Call repository / network layer here. Keep it idempotent.
}
companion object {
private const val EXTRA_ACCOUNT_ID = "account_id"
}
}
这个例子仍然只适合短任务入口。若任务需要网络约束、充电约束、失败重试或进程死亡后继续,应该迁移到 WorkManager,而不是让 started service 常驻。
2. 前台服务:先通知,后执行
Manifest 至少需要声明 Service、前台服务权限和类型。Android 14+ 对不同前台服务类型有更细的权限和运行时前提,下面以数据同步为例:
xml
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />
<application>
<service
android:name=".sync.FileUploadService"
android:exported="false"
android:foregroundServiceType="dataSync" />
</application>
Service 内部应尽早调用 startForeground():
kotlin
class FileUploadService : Service() {
private val scope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
override fun onCreate() {
super.onCreate()
createUploadNotificationChannel()
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
val notification = buildUploadingNotification(progress = 0)
ServiceCompat.startForeground(
this,
NOTIFICATION_ID,
notification,
ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC,
)
val fileId = intent?.getStringExtra(EXTRA_FILE_ID)
if (fileId == null) {
stopSelf(startId)
return START_NOT_STICKY
}
scope.launch {
try {
uploadFile(fileId) { progress ->
updateNotification(progress)
}
} finally {
stopSelf(startId)
}
}
return START_NOT_STICKY
}
override fun onTimeout(startId: Int, fgsType: Int) {
// Android 15+ can call this for timed foreground service types.
scope.cancel("Foreground service timeout")
stopSelf(startId)
}
override fun onDestroy() {
scope.cancel()
super.onDestroy()
}
override fun onBind(intent: Intent?): IBinder? = null
private fun createUploadNotificationChannel() = Unit
private fun buildUploadingNotification(progress: Int): Notification = TODO()
private fun updateNotification(progress: Int) = Unit
private suspend fun uploadFile(fileId: String, onProgress: (Int) -> Unit) = Unit
companion object {
private const val NOTIFICATION_ID = 1001
private const val EXTRA_FILE_ID = "file_id"
}
}
调用方要处理 Android 12+ 后台启动限制:
kotlin
fun startUpload(context: Context, fileId: String) {
val intent = Intent(context, FileUploadService::class.java)
.putExtra("file_id", fileId)
try {
ContextCompat.startForegroundService(context, intent)
} catch (e: ForegroundServiceStartNotAllowedException) {
// App is background and no exemption applies. Persist intent and ask user to resume.
enqueueUploadWithWorkManager(fileId)
showUploadWillContinueNotification(context, fileId)
}
}
这里的降级策略很关键:后台启动前台服务失败,不应无限重试;要把任务转成 WorkManager、通知点击后的用户可见入口,或等应用回到前台再启动。
3. Bound Service:把连接生命周期写清楚
kotlin
class PlayerService : Service() {
private val binder = PlayerBinder()
inner class PlayerBinder : Binder() {
fun service(): PlayerService = this@PlayerService
}
override fun onBind(intent: Intent?): IBinder = binder
fun play(mediaId: String) {
// Delegate to a player component; do not block the main thread.
}
}
class PlayerController(
private val context: Context,
) {
private var service: PlayerService? = null
private val connection = object : ServiceConnection {
override fun onServiceConnected(name: ComponentName, binder: IBinder) {
service = (binder as PlayerService.PlayerBinder).service()
}
override fun onServiceDisconnected(name: ComponentName) {
service = null
}
}
fun bind() {
val intent = Intent(context, PlayerService::class.java)
context.bindService(intent, connection, Context.BIND_AUTO_CREATE)
}
fun unbind() {
service = null
context.unbindService(connection)
}
}
如果这个连接由 Activity 持有,通常在 onStart() 绑定、onStop() 解绑;如果是全局业务能力,要明确由 Application 级对象或依赖注入容器管理,避免 Activity 泄漏。
后台限制演进
Android 8.0:后台服务限制
Android 8.0 后,后台应用不能随意创建后台服务。官方后台执行限制文档给出的替代路径是:如果需要启动用户可见的前台任务,用 startForegroundService(),并在服务创建后 5 秒内调用 startForeground();否则使用 JobScheduler,现代项目通常用 WorkManager 封装这类可延迟任务。
这改变了 Service 的定位:它不再是"后台任务默认容器",而是"组件入口 + 用户可见持续任务 + 绑定能力"的组合。
Android 12:后台启动前台服务限制
Android 12 以后,目标 SDK 31+ 的应用通常不能在后台启动前台服务,除非符合官方列出的例外场景。若不符合,系统会抛出 ForegroundServiceStartNotAllowedException。
这条限制对架构影响很大:
- 后台静默上传、同步、轮询,不应临时拉起前台服务。
- 用户点击通知、显式操作、部分高优先级事件等例外场景,需要按官方条件判断。
- 代码必须能处理启动失败,而不是假设
startForegroundService()一定成功。
Android 14:前台服务类型更严格
Android 14 对前台服务类型的声明、权限和运行时前提更严格。只声明 android:foregroundServiceType="camera" 或 location" 还不够,应用还要满足对应权限和可用状态。涉及 while-in-use 权限的类型,如相机、麦克风、位置,后台启动时尤其容易失败,因为后台时应用可能并不具备对应的"正在使用时"权限上下文。
工程上应把前台服务类型当成契约,而不是标签:
| 任务 | 推荐类型 | 额外关注 |
|---|---|---|
| 导航定位 | location |
前台可见、定位权限、通知说明 |
| 语音通话 | microphone / phoneCall |
运行时权限、用户明确操作 |
| 音乐播放 | mediaPlayback |
媒体会话、通知控制 |
| 文件上传 | dataSync |
Android 15 超时、可否改 WorkManager |
| 视频处理 | mediaProcessing |
Android 15 超时、进度和取消 |
| 蓝牙设备连接 | connectedDevice |
蓝牙权限、连接状态 |
Android 15:部分前台服务类型有超时
Android 15 对 dataSync 和 mediaProcessing 等前台服务类型引入累计运行时间限制。官方超时文档说明:如果某类前台服务在 24 小时窗口内运行达到限制,再尝试启动同类型前台服务会抛出 ForegroundServiceStartNotAllowedException;系统也会通过 Service.onTimeout(int, int) 通知服务尽快停止,未及时停止可能导致 ANR。
AOSP ActiveServices.java 里也能看到相关变更开关 FGS_INTRODUCE_TIME_LIMITS,注释写明某些类型的前台服务有时间限制,超时后会回调 Service.onTimeout(int, int),服务必须在几秒内停止,否则会被判定 ANR。ServiceRecord.java 则保存了 foregroundServiceType 和短前台服务的超时时间计算。
这意味着前台服务不能再承担"无限长数据同步"的职责。上传/处理任务要做切片、进度持久化、可恢复、可取消;后台可延迟任务仍优先 WorkManager。
源码分析
Service.java:组件契约
源码路径:frameworks/base/core/java/android/app/Service.java。
关键点:
- 类注释区分 started 和 bound 两种基本使用方式。
onStartCommand()的文档说明多次startService()会产生多次回调,但停止并不是嵌套计数。startForeground()文档说明 Android Q 起可在 manifest 声明前台服务类型,Android S 起目标 SDK 31+ 调用时要满足类型约束。- Android 15 相关路径增加了
onTimeout(int, int),用于前台服务超时后的快速收尾。
Service 只是 App 进程内的对象,系统并不会替你开线程,也不会替你保证任务一定完成。
ActivityThread.java:应用进程内创建与分发
源码路径:frameworks/base/core/java/android/app/ActivityThread.java。
关键调用:
handleCreateService(...):在应用进程内通过 LoadedApk、ClassLoader 和 Instrumentation 创建 Service 实例,attach 上下文,并调用onCreate()。handleServiceArgs(...):接收 system_server 分发的启动参数,调用Service.onStartCommand(...)或onTaskRemoved(...)。handleTimeoutService(...):前台服务超时场景下回调 Service 的 timeout 处理。
这解释了为什么 Service 回调中的阻塞会影响主线程。Service 的生命周期调度和 Activity 一样,都要经过 ActivityThread 主线程消息处理。
ActiveServices.java:system_server 的 Service 状态机
源码路径:frameworks/base/services/core/java/com/android/server/am/ActiveServices.java。
关键调用:
startServiceLocked(...):处理启动请求,包括调用方、用户、权限、后台启动策略和前台服务要求。bringUpServiceLocked(...):在需要时拉起进程或创建服务。sendServiceArgsLocked(...):把 pending start 参数发到应用进程。setServiceForegroundLocked(...):处理startForeground()上报,校验通知、类型、状态并更新 ServiceRecord。
ActiveServices 是后台限制真正落地的地方。App 侧看到的是异常、ANR 或服务停止,system_server 侧维护的是一组状态、超时、权限和进程优先级决策。
ServiceRecord.java:一次服务运行的系统侧记录
源码路径:frameworks/base/services/core/java/com/android/server/am/ServiceRecord.java。
它保存了 Service 的核心状态:
foregroundServiceType:当前前台服务类型位掩码。startRequested、callStart、pendingStarts:started service 分发状态。executeNesting、executingStart:执行中状态和超时相关信息。fgDisplayTime、foregroundNoti:前台通知和展示状态。- short foreground service timeout 相关时间计算。
如果把 Service 看成 App 层对象,ServiceRecord 就是 system_server 眼中的那份运行账本。
实战案例:可恢复文件上传
假设产品要求上传巡检视频:用户点击"上传",离开页面后仍希望看到进度,但没有必要无限占用前台服务。
推荐设计:
- 用户点击上传时,把任务写入本地数据库,状态为
Queued。 - 如果 App 在前台且用户需要实时进度,启动
dataSync前台服务。 - Service 立即展示通知,并在通知里提供取消入口。
- 上传分片执行,每个分片完成后持久化进度。
- 如果
ForegroundServiceStartNotAllowedException或 Android 15 超时发生,停止前台服务,改由 WorkManager 在合适约束下继续。 - 用户再次打开页面时,从数据库恢复进度,而不是依赖 Service 内存状态。
核心状态机:
这套设计的重点不是"永远让 Service 活着",而是让任务状态独立于 Service。Service 只是用户可见阶段的执行载体。
性能优化
1. 不在主线程做耗时工作
Service 回调默认在主线程。耗时 I/O、数据库批量操作、压缩、上传、蓝牙阻塞调用都要转移到合适调度器。即使使用协程,也要确认实际执行在 Dispatchers.IO 或专用线程池,而不是在主线程里启动后继续阻塞。
2. 控制前台服务数量和时长
每个前台服务都意味着通知、进程优先级提升、电量消耗和用户注意力占用。能合并的任务合并,能切片的任务切片,能转 WorkManager 的后台任务不要占前台服务。
3. 通知更新节流
上传进度不要每个字节都刷新通知。常见策略是按百分比变化、固定时间窗口或重要状态节点更新。频繁通知更新会增加 Binder 调用、系统 UI 压力和耗电。
4. 任务幂等和去重
onStartCommand() 可能被多次调用,进程死亡后也可能重新投递 Intent。任务参数应包含稳定 ID,业务层根据 ID 去重,避免重复上传、重复扣费或重复写数据库。
5. 可观测性
为 Service 增加这些日志和指标:
- 启动来源:前台页面、通知、广播、WorkManager、系统回调。
- 启动结果:成功、FGS denied、权限缺失、超时、用户取消。
- 执行时长:总时长、前台时长、后台 fallback 时长。
- 停止原因:完成、失败、取消、超时、进程销毁。
这些指标比"服务有没有活着"更能指导优化。
常见问题
1. Service 会运行在独立进程吗?
默认不会。Service 运行在应用默认进程,除非 manifest 用 android:process 指定其他进程。即使在独立进程,回调也仍有该进程自己的主线程,不代表自动变成后台线程。
2. startService() 和 startForegroundService() 怎么选?
如果任务需要用户可见的持续执行,并且符合前台服务类型和启动条件,用 startForegroundService() 后立即 startForeground()。如果只是可延迟后台任务,优先 WorkManager。Android 8.0+ 后,后台应用不应依赖 startService() 创建长时间后台服务。
3. 前台服务通知可以隐藏吗?
不应该以隐藏通知为目标。前台服务的设计前提就是用户可感知。不同系统版本和厂商对通知展示时机有差异,但工程上应提供清晰通知、进度、取消入口和合理文案。
4. 为什么后台启动前台服务会抛异常?
Android 12+ 限制目标 SDK 31+ 应用从后台启动前台服务,除非命中特定例外。系统通过 ForegroundServiceStartNotAllowedException 把这个策略暴露给 App。正确做法是捕获异常并降级,而不是重试到成功。
5. Android 15 的 onTimeout() 要做什么?
尽快停止当前前台服务,持久化进度,释放资源,并把剩余任务转入可恢复队列。不要在 onTimeout() 里继续做长时间收尾,否则仍可能 ANR。
6. 绑定 Service 要不要手动停止?
只绑定、未 started 的 Service 在所有连接解绑后可被销毁。既 started 又 bound 的 Service,需要同时满足"停止 started 状态"和"无绑定连接"才会销毁。
7. Service 能替代 WorkManager 吗?
不能。WorkManager 解决的是可延迟、可约束、可重试、进程死亡后仍可调度的后台任务;Service 解决的是组件生命周期和用户可见持续任务。两者经常组合使用:前台可见阶段用 Service,后台恢复阶段用 WorkManager。
面试考点
基础题
- Service 和 Thread 的区别是什么?
- Started Service 与 Bound Service 的生命周期差异是什么?
onStartCommand()的几个返回值分别代表什么?- 为什么 Android 8.0 引入
startForegroundService()? - 前台服务为什么必须显示通知?
进阶题
- 多次
startService()后是否需要多次stopService()?为什么? - Android 12+ 后台启动前台服务失败应该如何降级?
- Android 14+ 前台服务类型和权限有什么关系?
- Android 15 前台服务超时对数据同步架构有什么影响?
- 如何设计一个既能显示实时进度、又能进程死亡后恢复的上传任务?
源码题
ActivityThread.handleCreateService()做了哪些事?ActivityThread.handleServiceArgs()和Service.onStartCommand()的关系是什么?ActiveServices.startServiceLocked()为什么是后台限制的重要入口?ServiceRecord.foregroundServiceType用来记录什么?- 前台服务超时为什么会回调到
Service.onTimeout()?
系统设计题
设计一个"运动轨迹记录"功能,要求:
- 用户开始运动后持续记录位置。
- 应用退到后台仍显示前台服务通知。
- Android 12+ 不从后台静默启动。
- Android 14+ 满足 location 前台服务类型和权限。
- 用户停止运动后及时释放定位、通知和服务。
- 轨迹数据进程死亡后可恢复。
合格答案应包含:用户显式启动入口、位置权限检查、foregroundServiceType="location"、前台通知、数据库持久化、异常降级、绑定 UI 控制、停止与资源释放策略。
总结
Service 的正确心智模型是:
- 它是 Android 组件,不是线程容器。
- Started、bound、foreground 是三个维度,不是互斥分类。
- 后台限制的目标是让长期资源占用回到用户可见、可解释、可取消的路径。
- 现代后台任务架构应把任务状态持久化,把前台服务作为用户可见阶段,把 WorkManager 作为可延迟恢复阶段。
写 Service 代码时,先问四个问题:这个任务是否必须现在执行?用户是否能感知?是否满足当前 Android 版本的启动条件和类型权限?进程死亡后能否恢复?如果答案不清楚,直接写一个常驻 Service 往往就是技术债的开始。
扩展阅读
- Android Developers: Services overview, developer.android.com/develop/bac...
- Android Developers: Service API reference, developer.android.com/reference/a...
- Android Developers: Foreground services overview, developer.android.com/develop/bac...
- Android Developers: Launch a foreground service, developer.android.com/develop/bac...
- Android Developers: Foreground service types, developer.android.com/develop/bac...
- Android Developers: Foreground service timeouts, developer.android.com/develop/bac...
- Android Developers: Restrictions on starting foreground services from the background, developer.android.com/develop/bac...
- Android Developers: Android 8.0 background execution limits, developer.android.com/about/versi...
- Android Developers: Android 15 behavior changes, developer.android.com/about/versi...
- AOSP:
frameworks/base/core/java/android/app/Service.java - AOSP:
frameworks/base/core/java/android/app/ActivityThread.java - AOSP:
frameworks/base/services/core/java/com/android/server/am/ActiveServices.java - AOSP:
frameworks/base/services/core/java/com/android/server/am/ServiceRecord.java - AndroidX:
androidx.core.app.ServiceCompat - 系列文章:A019 Android 应用进程、组件与系统架构概览
- 系列文章:A020 Activity 生命周期完整解析
- 系列文章:A021 Activity 启动模式与任务栈
- 系列文章:A023 Intent 显式、隐式与 PendingIntent
- 下一篇:A025 BroadcastReceiver 静态注册与动态注册