【短文】Android中的前台Service和后台Service

666 阅读6分钟

本文中“Service”与“服务”所指代的是同一概念。

Service的定义及作用

作为四大组件之一,Service在Android系统中主要用来在后台执行那些需要长期运行的、没有界面的任务。适合以下场景:

  1. 耗时任务,如文件下载上传、数据解析、本地运行算法操作图片等。
  2. 实现持续运行功能,如音乐播放、实时定位、运动时的节拍器(本质上也是音乐播放)等。
  3. 支持对外开放组件,如响应其它组件(包括远程)的请求。

为了支持其功能,Service具有以下特性:

  1. 无界面:没有UI,用户唯一能看到的是系统通知。
  2. 后台运行:可以在所有Activity关闭后继续在后台运行。
  3. 并发执行:支持异步任务以及多线程。

Service的生命周期

Service具有两种不同的启动方式,这两种启动方式下,它的生命周期也是不同的。

通过startService启动

  • Service独立运行,与Context生命周期无关,即使启动Service的组件销毁,Service仍然继续运行。
  • 需要显示调用stopService或者stopSelf来停止Service。

通过startService启动后的生命周期

  • onCreate():服务被首次创建时调用。
  • onStartCommand():每次通过 startService 启动服务时调用。
  • onDestroy():服务被停止时调用。

通过bindService启动

  • 服务与绑定它的组件生命周期相关联。当所有绑定的组件都解绑时,服务会销毁。
  • 调用者(客户端)通过 onBind() 返回的 IBinder 与服务通信。

通过bindService启动后的生命周期

  • onCreate():服务被首次创建时调用。
  • onBind():组件绑定到服务时调用,返回一个 IBinder 接口。
  • onUnbind():最后一个绑定的组件解绑时调用。
  • onDestroy():服务被销毁时调用。

Service的主要类型

从Service运行时的优先级、是否允许被回收、是否有通知、是否与客户端(调用者)有交互这几个角度,可以将Service分为普通(后台)、高优先级(前台)、绑定这几种类型。

此外,还有几种特殊的服务,可用于异步、延迟、适配电池策略等场景。

普通服务(后台服务)

  • 通过startService启动。
  • 独立运行,需要显式调用stopService来停止。
  • 执行轻量、短期的任务,如下载文件、上传数据。
  • 优先级低,在系统资源紧张时会被系统回收。
class MyService : Service() {
    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        // 执行任务
        return START_STICKY
    }
    override fun onBind(intent: Intent?): IBinder? {
        return null
    }
}

高优先级服务(前台服务)

  • 通过startForeground()启动,启动时需要传入一个Notification对象。
  • 不容易被系统杀死,适用于重要任务。
  • 适用于需要持续运行的任务,如音乐播放、持续定位。
  • 优先级高,运行时占用系统资源,需要谨慎使用,避免对内存和电池造成负担。
class MyForegroundService : Service() {
    override fun onCreate() {
        val notification = NotificationCompat.Builder(this, "channelId")
            .setContentTitle("Service Running")
            .setSmallIcon(R.drawable.ic_notification)
            .build()
        startForeground(1, notification)
    }
}

绑定服务

  • 通过bindService启动,允许客户端与Service之间双向通信。
  • 生命周期与客户端相关。
  • 适用于需要交互的功能,如音乐播放、TTS。
class MyBoundService : Service() {
    private val binder = LocalBinder()
    inner class LocalBinder : Binder() {
        fun getService(): MyBoundService = this@MyBoundService
    }
    override fun onBind(intent: Intent?): IBinder {
        return binder
    }
}

特殊服务

JobIntentService

用于处理异步任务,自动适配不同 Android 版本的后台策略。

JobScheduler

用于延迟执行任务或满足特定条件时执行。

WorkManager

推荐用于替代后台任务,支持约束条件和持久性。

常见问题与解决方法

前台Service通知无法关闭

前台Service要求必须有与之对应的通知(Notification),如果通知被移除,会导致系统停止服务。

对 Android 8.0 以下版本,可以利用系统漏洞隐藏通知,但这并不推荐,可能会带来不稳定性。

Android 12+ 提供了 NotificationManager.IMPORTANCE_MIN,可以进一步降低通知可见性。

从用户体验的角度考虑,建议提供“关闭通知”的选项,但关闭通知时需要告知用户,服务可能被停止。

后台Service被系统杀死

Android 系统从 Android 8.0 开始加强了后台服务的限制,禁止普通后台服务长期运行。后台服务运行时可能因资源不足、权限问题或电池优化而被系统杀死。

Android 官方推荐使用 WorkManager 处理后台任务,它能适配电池优化和后台限制。

适配Doze模式

Doze 模式 是从 Android 6.0 (API 23) 开始引入的一种电池优化机制,旨在减少设备在不活动时的电量消耗。

Doze 模式的工作原理

当设备处于非活跃状态时(例如屏幕关闭且设备未被使用),系统会进入 Doze 模式,限制某些后台活动以节省电量。

Doze 模式的触发条件

  1. 设备处于静止状态(没有明显的移动或操作)。
  2. 屏幕关闭且未插入电源。
  3. 没有运行高优先级的前台任务。

Doze 模式的限制

在 Doze 模式下,系统会限制以下活动:

  1. 网络访问:应用无法自由地访问网络。
  2. 后台任务调度:后台服务(例如定时任务或轮询任务)受到严格限制。
  3. AlarmManager:普通闹钟不会触发,只有高优先级任务(如 setExactAndAllowWhileIdle())可以运行。
  4. 同步和作业调度:JobScheduler 和 SyncAdapter 的任务可能被推迟。

对需要长时间运行的任务,需设置 setExactAndAllowWhileIdle() 以确保任务在 Doze 模式下触发。

WorkManager与AlarmManager

特性WorkManagerAlarmManager
任务类型适用于需要灵活调度的后台任务,允许延迟或条件触发。适用于需要精确时间触发的任务,无论设备是否处于活动状态。
系统支持自动适配 Android 系统优化机制(如 Doze 模式、App Standby)。在 Doze 模式下,普通闹钟任务会被延迟,除非使用 setExactAndAllowWhileIdle()
任务持久化支持任务持久化,任务配置存储在数据库中,即使设备重启或应用被杀死也能恢复。默认不支持任务持久化,设备重启后需重新设置任务。
任务约束支持多种条件(如网络连接、电池状态、存储空间等),任务在条件满足时执行。不支持复杂约束,只能根据时间触发。
使用复杂度更高级,适合处理复杂任务调度逻辑。更简单,适合处理精确时间任务。
触发频率限制周期性任务的最小间隔为 15 分钟(API 限制)。无间隔限制,可以高频率触发任务。

选择 WorkManager 的场景

  • 任务不要求精确触发时间。
  • 需要任务持久化和条件约束(如网络状态、电池电量)。
  • 任务与 Android 高版本优化机制(如 Doze 模式)兼容性强。

选择 AlarmManager 的场景

  • 需要精确触发时间。
  • 任务不依赖复杂条件。
  • 对电池优化模式有更高控制要求。