Service 基础知识
Service 和 Activity 一样都是 Android 的组件。它们最大的区别就是 service 没有 UI 而 Activity 有。
Service 默认运行在当前进程的主线程,这点和 activity 是一样的。因此 Service 要么会在配置处设置其他进程,要么是在 Service 代码中启动新的线程。
Service在清单文件中的声明
xml
<service
android:name=".MyCustomService"
android:exported="true"
android:enabled="true"
android:process=":my_service_process"
android:isolatedProcess="true"
android:permission="com.example.permission.START_MY_SERVICE"/>
- android:exported:代表是否能被其他应用隐式调用,其默认值是由service中有无intent-filter决定的,如果有intent-filter,默认值为true,否则为false。为false的情况下,即使有intent-filter匹配,也无法打开,即无法被其他应用隐式调用。
- android:name:对应Service类名,必选
- android:permission:是权限声明
- android:process:是否需要在单独的进程中运行,当设置为android:process=":remote"时,代表Service在单独的进程中运行。注意":"很重要,它的意思是指要在当前进程名称前面附加上当前的包名,所以"remote"和":remote"不是同一个意思,前者的进程名称为:remote,而后者的进程名称为:App-packageName:remote。
- android:isolatedProcess :设置 true 意味着,服务会在一个特殊的进程下运行,这个进程与系统其他进程分开且没有自己的权限。与其通信的唯一途径是通过服务的API(bind and start)。
- android:enabled:是否可以被系统实例化,默认为 true因为父标签 也有 enable 属性,所以必须两个都为默认值 true 的情况下服务才会被激活,否则不会激活。
Service 的种类
Service 有三种:
- 后台服务
- 绑定服务
- 前台服务
其中后台服务,是通过 startService 启动的,除非调用 stopSelf 或者 stopService ,否则该服务会一直运行。绑定服务,是通过 bindService 启动的,当绑定的数量为 0 时,该服务会自动停止。 如果调用bindService()方法前服务已经被绑定,多次版调用bindService()方法并不会导致多次创建服务及绑定(也就是说onCreate()和onBind()方法并不会被多次调用)。多次调用 startService 会报错
- 先启动再绑定
我们也可以使用 bindService 来绑定已经启动的服务。此时停止服务需要调用 unbindService 和 stopService。
- 先绑定再启动
如果当前Service实例先以绑定状态运行,然后再以启动状态运行,那么绑定服务将会转为启动服务运行,这时如果之前绑定的宿主(Activity)被销毁了,也不会影响服务的运行,服务还是会一直运行下去,指定收到调用停止服务或者内存不足时才会销毁该服务。
生命周期

其中 onStartCommand 的返回值有:
- START_NOT_STICKY:如果系统在 onStartCommand() 返回后终止服务,请不要重新创建服务,除非有待传递的待处理 intent。这是最安全的选项,可避免在不需要时运行服务,以及在应用可以简单地重启所有未完成的作业时运行服务。
- START_STICKY:如果系统在 onStartCommand() 返回后终止服务,请重新创建服务并调用 onStartCommand(),但不要重新传递上一个 intent。相反,除非有待处理的 intent 用于启动服务,否则系统会使用 null intent 调用 onStartCommand()。在这种情况下,系统会传递这些 intent。这适用于未执行命令但无限期运行并等待作业的媒体播放器(或类似服务)。
- START_REDELIVER_INTENT:如果系统在 onStartCommand() 返回后终止服务,请重新创建服务,并使用传递给服务的最后一个 intent 调用 onStartCommand()。系统会依次传送所有待处理 intent。这适用于正在积极执行应立即恢复的作业的服务,例如下载文件。
Android 5 到 16 期间后台服务的变更
应用满足以下任一条件即视为前台应用:
- 它具有可见的 Activity,无论 Activity 处于启动还是暂停状态。
- 它具有前台服务。
- 另一个前台应用已关联到该应用(不管是通过绑定到其中一个服务,还是通过使用其中一个 content >provider)。例如,如果其他应用绑定到该应用的以下任一项,则该应用处于前台:
- IME
- 壁纸服务
- 通知侦听器
- 语音或文本服务 如果以上所有条件均不满足,应用即视为后台应用。
Android 5 开始,使用隐式 intent 启动服务,系统会抛出异常。这时,我们可以设置应用的包名来解决
scss
final Intent serviceIntent=new Intent(); serviceIntent.setAction("com.android.ForegroundService");
serviceIntent.setPackage(getPackageName());//设置应用的包名
startService(serviceIntent);
Android 8开始则对后台服务进行限制。当应用进入后台时,会有几分钟时间来创建和使用服务。该段时间结束后,应用会被视为处于空闲状态。此时,系统会停止应用的后台服务,就像应用已调用这些服务的 Service.stopSelf() 方法一样。
android 8 时, IntentService 被废弃,推荐使用 workmanager代替
Android 9 则限制了后台应用访问麦克风、摄像头、传感器等
前台服务
kotlin
// 启动服务
val intent = Intent()
context.startForegroundService
// startForeground 来把服务提升到前台
class MyCameraService: Service() {
private fun startForeground1() {
try {
val notification = NotificationCompat.Builder(this, "CHANNEL_ID")
// Create the notification to display while the service is running
.build()
startForeground(
/* id = */ 100, // Cannot be 0
/* notification = */ notification,
/* foregroundServiceType = */
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
ServiceInfo.FOREGROUND_SERVICE_TYPE_CAMERA
} else {
0
},
)
} catch (e: Exception) {
}
}
}
stopSelf 和 stopService 会停止服务。如需从前台移除服务,请从服务内部调用 stopForeground(int)。此方法接受一个布尔值,用于指示是否也移除状态栏通知。该服务会继续运行,但不再是前台服务。
前台服务的限制
Android 9,使用前台服务前需要请求 FOREGROUND_SERVICE 权限
Android 10,引入了 foregroundServiceType XML 清单属性,为前台服务定义对应的服务类型。比如 dataSync 是指从网络下载文件;mediaPlayback 是指播放音乐等。同时要求前台服务使用位置信息时,必须声明服务类型为 location
Android 11,当前台服务使用摄像头和麦克风时,应用必须声明其类型为 camera 或者 microphone
Android 12,应用在后台运行时,不能启动前台服务
Android 14 强制要求所有的前台服务声明其类型。除此之外,还需要设置权限类型,比如如果应用启动使用相机的前台服务,您必须同时请求 FOREGROUND_SERVICE 和 FOREGROUND_SERVICE_CAMERA 权限。
Android 15 则限制了不同类型的前台服务的运行时长限制。比如:系统允许 dataSync 和 mediaProcessing 前台服务在 24 小时内总共运行 6 小时,之后系统会调用正在运行的服务的 Service.onTimeout(int, int) 方法,这时有几秒钟的时间来调用 Service.stopSelf()。当系统调用 Service.onTimeout() 时,该服务不再被视为前台服务。如果服务未调用 Service.stopSelf(),系统会抛出内部异常。用户将应用置于前台,计时器会重置,应用可使用 6 小时。