首页 / Android 入门教程 / Service 与进程

Android 入门教程

Service 与进程

本教程共 100 篇 · 第 34 篇 · 更新于 2026-07-28 · 约 9 分钟阅读

AndroidAndroid 入门教程Service进程进程优先级保活多进程WorkManager

34. Service 与进程

本节目标:理解 Service 跟进程的关系、进程优先级怎么定、为什么「保活」越来越难、多进程服务怎么用、现代后台任务该选什么方案。

Service 跑在哪个进程

默认情况下,应用的所有组件(Activity、Service、Receiver、Provider)跑在同一个进程里,进程名就是包名。Service 默认也在这个主进程的主线程上跑。

想让 Service 跑在独立进程,Manifest 里声明 android:process

<service
    android:name=".RemoteService"
    android:process=":remote" />
  • :remote(前面带冒号):私有进程,其他应用进不来,进程名是 包名:remote
  • remote(不带冒号):全局进程,理论上其他应用用相同 sharedUserId 能进(少用,已不推荐)。

为什么用多进程

多进程有几个场景:

  1. 隔离崩溃:某个 Service 容易崩,放独立进程崩了不影响主进程。
  2. 独立内存:WebView、地图 SDK 吃内存大,放独立进程能单独回收。
  3. 利用多进程保活:互相拉起(受限,下面讲)。
  4. 跑 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 按进程重要性决定杀谁,从高到低:

  1. 前台进程:正在跟用户交互的 Activity、绑定它的 Service、调 startForeground 的 Service。
  2. 可见进程:可见但不在前台(如被对话框遮住)的 Activity。
  3. 服务进程startService 启动但不是前台的 Service。
  4. 缓存进程:后台的 Activity(按了 Home),随时可杀。

Service 影响进程优先级:

  • 前台服务:进程升为前台进程,几乎不被杀。
  • 绑定到前台 Activity 的 Service:进程升为可见进程。
  • 启动式后台 Service:进程是服务进程,可能被杀。
  • 没有任何活跃组件:进程是缓存进程,最容易被杀。
Note
  • 系统杀进程时,Service 所在进程被杀,Service 也死。onStartCommand 返回 START_STICKY 时,系统会在资源恢复后重启 Service(但 intent 为 null)。START_NOT_STICKY 不重启。

保活:越来越难的历史

以前(Android 5 以前)应用有各种保活手段:双进程互相拉起、监听系统广播拉起、1 像素 Activity 保活等。Google 逐年封堵:

版本限制
Android 6Doze 模式,后台网络受限
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 部分结束,下一部分学广播接收器。