Service 与进程
本教程共 100 篇 · 第 34 篇 · 更新于 2026-07-28 · 约 9 分钟阅读
34. Service 与进程
本节目标:理解 Service 跟进程的关系、进程优先级怎么定、为什么「保活」越来越难、多进程服务怎么用、现代后台任务该选什么方案。
Service 跑在哪个进程
默认情况下,应用的所有组件(Activity、Service、Receiver、Provider)跑在同一个进程里,进程名就是包名。Service 默认也在这个主进程的主线程上跑。
想让 Service 跑在独立进程,Manifest 里声明 android:process:
<service
android:name=".RemoteService"
android:process=":remote" />
:remote(前面带冒号):私有进程,其他应用进不来,进程名是包名:remote。remote(不带冒号):全局进程,理论上其他应用用相同sharedUserId能进(少用,已不推荐)。
为什么用多进程
多进程有几个场景:
- 隔离崩溃:某个 Service 容易崩,放独立进程崩了不影响主进程。
- 独立内存:WebView、地图 SDK 吃内存大,放独立进程能单独回收。
- 利用多进程保活:互相拉起(受限,下面讲)。
- 跑 SDK 要求独立进程:某些 SDK(如 X5 WebView)要求独立进程。
Warning
- 多进程代价大:每个进程有自己的 ART 实例、内存开销翻倍、跨进程通信(IPC)要用 AIDL 或 Messenger,复杂度上升。没强需求别用。
跨进程通信
Service 在独立进程,跟主进程通信要用 IPC:
AIDL
定义 .aidl 接口文件,系统生成 Stub,跨进程调用像调本地方法:
// IRemoteService.aidl
interface IRemoteService {
int getPid();
void doSomething(String param);
}
Service 端实现 Stub,客户端 bindService 后拿到接口代理。
Messenger
基于 Handler 的轻量 IPC,串行处理消息,适合简单场景:
class MessengerService : Service() {
private val messenger = Messenger(IncomingHandler())
override fun onBind(intent: Intent?): IBinder = messenger.binder
private inner class IncomingHandler : Handler() {
override fun handleMessage(msg: Message) {
when (msg.what) {
MSG_SAY_HELLO -> Toast.makeText(this@MessengerService, "Hello", Toast.LENGTH_SHORT).show()
}
}
}
}
Tip
- 单进程内的 Service 跟 Activity 通信用普通 Binder 就行,别上 AIDL/Messenger。只有跨进程才需要 IPC。
进程优先级
Android 按进程重要性决定杀谁,从高到低:
- 前台进程:正在跟用户交互的 Activity、绑定它的 Service、调
startForeground的 Service。 - 可见进程:可见但不在前台(如被对话框遮住)的 Activity。
- 服务进程:
startService启动但不是前台的 Service。 - 缓存进程:后台的 Activity(按了 Home),随时可杀。
Service 影响进程优先级:
- 前台服务:进程升为前台进程,几乎不被杀。
- 绑定到前台 Activity 的 Service:进程升为可见进程。
- 启动式后台 Service:进程是服务进程,可能被杀。
- 没有任何活跃组件:进程是缓存进程,最容易被杀。
Note
- 系统杀进程时,Service 所在进程被杀,Service 也死。
onStartCommand返回START_STICKY时,系统会在资源恢复后重启 Service(但 intent 为 null)。START_NOT_STICKY不重启。
保活:越来越难的历史
以前(Android 5 以前)应用有各种保活手段:双进程互相拉起、监听系统广播拉起、1 像素 Activity 保活等。Google 逐年封堵:
| 版本 | 限制 |
|---|---|
| Android 6 | Doze 模式,后台网络受限 |
| Android 7 | 移除 CONNECTIVITY_CHANGE 等广播,后台拍照受限 |
| Android 8 | 后台不能 startService,后台定位受限 |
| Android 9 | 后台不能麦克风/相机/传感器 |
| Android 12 | 后台不能启动前台服务(除豁免) |
| Android 13 | 通知要运行时权限,后台限制更严 |
| Android 14 | 前台服务必须声明类型,类型跟权限绑定 |
Warning
- 现在没有可靠的保活方案。任何「让应用永远不死」的尝试都会被系统制裁,而且损害用户体验和电池。别花时间研究保活,把精力放在「按需启动、用完即停」的正确姿势上。
正确的后台任务方案
Google 官方按任务特性推荐方案:
| 任务特性 | 推荐方案 |
|---|---|
| 用户触发、需立即完成、可感知 | 前台服务(如音乐播放) |
| 用户触发、需立即完成、不需感知 | WorkManager 加急工作(Expedited) |
| 可延迟、需保证执行 | WorkManager 普通工作 |
| 定时周期任务 | WorkManager 周期任务 |
| 网络条件触发 | WorkManager 约束 |
| 精确时间触发 | AlarmManager(仅闹钟类应用) |
| 后台下载大文件 | DownloadManager 或 WorkManager |
| 推送唤醒 | FCM(国内用厂商推送) |
WorkManager 优势
- 系统调度,自动适应 Doze、电池优化。
- 应用重启后任务仍能执行(持久化)。
- 支持约束(网络、电量、充电)。
- 支持链式、周期任务。
- API 31+ 自动转为加急工作,替代部分前台服务场景。
第 75 章详细讲 WorkManager。
FCM / 厂商推送
需要服务器主动唤醒应用(如新消息),用推送:
- 海外:Firebase Cloud Messaging(FCM),收到高优先级 FCM 能短暂启动 Service。
- 国内:用各厂商推送(华为、小米、OPPO、vivo),它们有系统级推送通道,应用死了也能收到。
Service 进程的常见问题
1. Service 被杀后状态丢失
Service 跑在进程里,进程被杀 Service 也死。重要数据要持久化(数据库、文件),别只存内存。
2. onTaskRemoved 不停止
用户从最近任务划掉应用,默认 Service 仍跑(如果是启动式)。可以在 onTaskRemoved 里停止:
override fun onTaskRemoved(rootIntent: Intent?) {
// 用户划掉应用
stopSelf()
super.onTaskRemoved(rootIntent)
}
但 Android 12+ 后台限制下,划掉应用后 Service 很快被杀。
3. 启动式 Service 重复启动
startService 多次调用会多次触发 onStartCommand,但 Service 实例只有一个。要处理重复 Intent 的情况(如正在下载又点了下载)。
4. 绑定未解绑导致泄漏
bindService 后忘了 unbindService,Activity 销毁时崩溃或泄漏。配对使用,在 onStop 解绑。
Compose 时代的 Service
Compose 单 Activity 架构下,Service 仍然是 Service,跟 Compose 没直接关系。Activity 跟 Service 通信:
- 用
bindService拿 Binder,在 Composable 里通过 ViewModel 或状态流观察 Service 数据。 - Service 用
StateFlow/LiveData暴露状态,ViewModel 收集后给 Compose。
class MusicViewModel(private val app: Application) : AndroidViewModel(app) {
private var musicService: MusicService? = null
private val _playbackState = MutableStateFlow(PlaybackState())
val playbackState = _playbackState.asStateFlow()
private val connection = object : ServiceConnection {
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
musicService = (service as MusicService.LocalBinder).getService()
// 观察 Service 状态
viewModelScope.launch {
musicService?.state?.collect { _playbackState.value = it }
}
}
override fun onServiceDisconnected(name: ComponentName?) { musicService = null }
}
fun bind() {
Intent(app, MusicService::class.java).also {
app.bindService(it, connection, Context.BIND_AUTO_CREATE)
}
}
override fun onCleared() {
app.unbindService(connection)
}
}
小结
Service 默认跑在主进程主线程,可声明 android:process 跑独立进程。进程优先级:前台服务 > 绑定前台 Activity 的服务 > 启动式后台服务。保活在现代 Android 几乎不可能,别尝试。正确后台方案:可感知任务用前台服务,不可感知用 WorkManager,服务器唤醒用 FCM/厂商推送。跨进程通信用 AIDL 或 Messenger。第 6 部分结束,下一部分学广播接收器。