前台服务

前台服务用于执行用户可察觉的操作。

前台服务需要显示状态栏通知,让用户知道您的应用正在前台执行任务并消耗系统资源。

使用前台服务的应用示例包括:

  • 一款音乐播放器应用,用于在前台服务中播放音乐。通知可能会显示当前正在播放的歌曲。
  • 一款健身应用,在获得用户许可后,可在前台服务中记录用户跑步数据。通知可能会显示用户在当前健身活动期间行进的距离。

仅当应用需要执行可被用户注意到的任务时(即使该任务未直接与应用交互),才使用前台服务。如果操作不是特别重要,因而您希望使用最低优先级通知,请改为创建后台任务

本文档介绍了使用前台服务所需的权限,以及如何启动前台服务并将其从后台移除。此外,还介绍了如何将某些用例与前台服务类型相关联,以及从在后台运行的应用启动前台服务时生效的访问限制。

默认情况下,用户可以关闭通知

从 Android 13(API 级别 33)开始,用户默认可以关闭与前台服务相关联的通知。为此,用户需要对通知执行滑动手势。传统上,除非前台服务停止或从前台移除,否则系统不会关闭通知。

如果您希望用户不可关闭该通知,请在使用 Notification.Builder 创建通知时将 true 传入 setOngoing() 方法。

立即显示通知的服务

如果某项前台服务至少具有以下特征之一,则系统会在服务启动后立即显示相关通知,即使在搭载 Android 12 或更高版本的设备上也是如此:

在 Android 13(API 级别 33)或更高版本中,如果用户拒绝授予通知权限,他们仍会在任务管理器中看到与前台服务相关的通知,但不会在抽屉式通知栏中看到这些通知。

在清单中声明前台服务

在应用清单中,使用 <service> 元素声明应用的每个前台服务。对于每项服务,请使用 android:foregroundServiceType 属性声明该服务执行的工作类型。

例如,如果您的应用创建了用于播放音乐的前台服务,您可以按如下方式声明该服务:

<manifest xmlns:android="http://schemas.android.com/apk/res/android" ...>
  <application ...>

    <service
        android:name=".MyMediaPlaybackService"
        android:foregroundServiceType="mediaPlayback"
        android:exported="false">
    </service>
  </application>
</manifest>

如果您的服务适用于多种类型,请使用 | 运算符将它们分隔开来。例如,使用摄像头和麦克风的服务会按如下方式声明:

android:foregroundServiceType="camera|microphone"

请求前台服务权限

如果应用以 Android 9(API 级别 28)或更高版本为目标平台并使用前台服务,则需要在应用清单中请求 FOREGROUND_SERVICE,如以下代码段所示。这是普通权限,因此,系统会自动为请求权限的应用授予此权限。

此外,如果应用以 API 级别 34 或更高级别为目标平台,则必须针对前台服务将要执行的工作类型请求适当的权限类型。每种前台服务类型都有对应的权限类型。例如,如果应用启动使用相机的前台服务,您必须同时请求 FOREGROUND_SERVICEFOREGROUND_SERVICE_CAMERA 权限。这些都是普通权限,因此,如果清单中列出了这些权限,系统会自动授予这些权限。

<manifest xmlns:android="http://schemas.android.com/apk/res/android" ...>

    <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
    <uses-permission android:name="android.permission.FOREGROUND_SERVICE_CAMERA"/>

    <application ...>
        ...
    </application>
</manifest>

前台服务前提条件

从 Android 14(API 级别 34)开始,当您启动前台服务时,系统会根据服务类型检查是否满足特定前提条件。例如,如果您尝试启动 location 类型的前台服务,系统会进行检查以确保您的应用已具有 ACCESS_COARSE_LOCATIONACCESS_FINE_LOCATION 权限。如果没有,系统会抛出 SecurityException

因此,您必须先确认已满足所需的前提条件,然后再启动前台服务。前台服务类型文档列出了每种前台服务类型的必需前提条件。

启动前台服务

在请求系统将服务作为前台服务运行之前,请先启动服务本身:

Kotlin

val intent = Intent(...) // Build the intent for the service
context.startForegroundService(intent)

Java

Context context = getApplicationContext();
Intent intent = new Intent(...); // Build the intent for the service
context.startForegroundService(intent);

在服务内(通常在 onStartCommand() 中),您可以请求服务在前台运行。为此,请调用 ServiceCompat.startForeground()(适用于 androidx-core 1.12 及更高版本)。此方法采用以下参数:

这些类型可能是清单中声明的类型的一部分,具体取决于具体用例。然后,如果您需要添加更多服务类型,可以再次调用 startForeground()

例如,假设一款健身应用运行一项始终需要 location 信息但可能不需要播放媒体的跑步跟踪器服务。您需要在清单中同时声明 locationmediaPlayback。如果用户开始跑步,并且只希望跟踪其位置信息,您的应用应调用 startForeground() 并仅传递 ACCESS_FINE_LOCATION 权限。然后,如果用户想要开始播放音频,请再次调用 startForeground() 并传递所有前台服务类型的按位组合(在本例中为 ACCESS_FINE_LOCATION|FOREGROUND_SERVICE_MEDIA_PLAYBACK)。

以下是启动相机前台服务的示例:

Kotlin

class MyCameraService: Service() {

  private fun startForeground() {
    // Before starting the service as foreground check that the app has the
    // appropriate runtime permissions. In this case, verify that the user has
    // granted the CAMERA permission.
    val cameraPermission =
            PermissionChecker.checkSelfPermission(this, Manifest.permission.CAMERA)
    if (cameraPermission != PermissionChecker.PERMISSION_GRANTED) {
        // Without camera permissions the service cannot run in the foreground
        // Consider informing user or updating your app UI if visible.
        stopSelf()
        return
    }

    try {
        val notification = NotificationCompat.Builder(this, "CHANNEL_ID")
            // Create the notification to display while the service is running
            .build()
        ServiceCompat.startForeground(
            /* service = */ this,
            /* 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) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S
                && e is ForegroundServiceStartNotAllowedException) {
            // App not in a valid state to start foreground service
            // (e.g. started from bg)
        }
        // ...
    }
  }
}

Java

public class MyCameraService extends Service {

    private void startForeground() {
        // Before starting the service as foreground check that the app has the
        // appropriate runtime permissions. In this case, verify that the user
        // has granted the CAMERA permission.
        int cameraPermission =
            ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA);
        if (cameraPermission == PackageManager.PERMISSION_DENIED) {
            // Without camera permissions the service cannot run in the
            // foreground. Consider informing user or updating your app UI if
            // visible.
            stopSelf();
            return;
        }

        try {
            Notification notification =
                new NotificationCompat.Builder(this, "CHANNEL_ID")
                    // Create the notification to display while the service
                    // is running
                    .build();
            int type = 0;
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
                type = ServiceInfo.FOREGROUND_SERVICE_TYPE_CAMERA;
            }
            ServiceCompat.startForeground(
                    /* service = */ this,
                    /* id = */ 100, // Cannot be 0
                    /* notification = */ notification,
                    /* foregroundServiceType = */ type
            );
        } catch (Exception e) {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S &&
                    e instanceof ForegroundServiceStartNotAllowedException
            ) {
                // App not in a valid state to start foreground service
                // (e.g started from bg)
            }
            // ...
        }
    }

    //...
}

从前台移除服务

如需从前台移除服务,请调用 stopForeground()。此方法采用布尔值,指示是否需同时移除状态栏通知。请注意,该服务会继续运行。

如果您在前台运行时停止服务,系统会移除其通知。

处理用户发起的停止运行前台服务的应用的行为

抽屉式通知栏的底部有一个按钮,用于指示当前在后台运行的应用数量。按此按钮时,系统会显示一个对话框,其中会列出不同应用的名称。每个应用的右侧都有一个“停止”按钮
图 1. 搭载 Android 13 或更高版本的设备上的任务管理器工作流程。

从 Android 13(API 级别 33)开始,无论应用采用何种目标 SDK 版本,用户都可以通过抽屉式通知栏完成工作流,以停止具有持续前台服务的应用。此功能称为任务管理器,会显示当前正在运行前台服务的应用列表。

此列表的标签为使用中的应用。每个应用旁边都有一个停止按钮。图 1 说明了搭载 Android 13 的设备上的任务管理器工作流程。

当用户在任务管理器中按您应用旁边的停止按钮时,系统会执行以下操作:

  • 系统会将您的应用从内存中移除。因此,您的整个应用都会停止,而不仅仅是正在运行的前台服务。
  • 系统会移除应用的 activity 返回堆栈。
  • 所有媒体播放都会停止。
  • 系统会移除与前台服务关联的通知。
  • 您的应用仍会保留在历史记录中。
  • 预定作业会在预定的时间执行。
  • 闹钟会在设定的时间或时间段响铃。

如需测试应用在用户停止应用期间和之后的行为是否符合预期,请在终端窗口中运行以下 ADB 命令:

adb shell cmd activity stop-app PACKAGE_NAME

豁免

系统会针对特定类型的应用提供几种级别的豁免,下面几部分将介绍这些豁免。

豁免针对的是应用而不是进程。如果系统豁免了应用中的一个进程,则该应用中的所有其他进程也会被豁免。

完全不会显示在任务管理器中

以下应用可以运行前台服务,而完全不会显示在任务管理器中:

免于被用户停止

当以下类型的应用运行前台服务时,它们会显示在任务管理器中,但应用名称旁边没有供用户点按的停止按钮:

使用专为特定用途构建的 API 代替前台服务

对于许多用例,您可以使用平台 API 或 Jetpack API 来执行您原本可能使用前台服务执行的工作。如果有适合的专用 API,您几乎总是应使用该 API,而不是使用前台服务。专用 API 通常提供其他特定于用例的功能,否则您将不得不自行构建这些功能。例如, Bubbles API 可为需要实现聊天气泡功能的消息应用处理复杂的界面逻辑。

前台服务类型文档列出了可替代前台服务的良好替代方案。

与从后台启动前台服务相关的限制

以 Android 12 或更高版本为目标平台的应用无法在后台运行时启动前台服务,但少数特殊情况除外。如果应用在后台运行时尝试启动前台服务,且前台服务不符合任何特殊情况,系统会抛出 ForegroundServiceStartNotAllowedException

此外,如果应用想要启动需要使用时权限(例如身体传感器、摄像头、麦克风或位置信息权限)的前台服务,则无法在应用处于后台时创建该服务,即使应用属于后台启动限制的某项例外情况也不例外。有关原因,请参阅对启动需要仅在使用时授予权限的前台服务部分的限制。

不受后台启动限制

在以下情况下,即使您的应用在后台运行,也可以启动前台服务:

对需要在使用时权限的前台服务启动施加的限制

在 Android 14(API 级别 34)或更高版本中,如果您要启动需要“使用时”权限的前台服务,则需要注意一些特殊情况。

如果您的应用以 Android 14 或更高版本为目标平台,操作系统会在您创建前台服务时进行检查,以确保您的应用拥有该服务类型的所有适当权限。例如,当您创建类型为麦克风的前台服务时,操作系统会验证您的应用当前是否具有 RECORD_AUDIO 权限。如果您没有此权限,系统会抛出 SecurityException

对于“使用时”权限,这可能会导致潜在问题。如果您的应用具有“在使用时”权限,则只有在前台运行时才拥有该权限。这意味着,如果您的应用位于后台,并尝试创建相机、位置信息或麦克风类型的前台服务,系统会发现您的应用当前不具备所需的权限,并抛出 SecurityException

同样,如果您的应用在后台运行,并且创建了需要 BODY_SENSORS 权限的健康服务,那么该应用目前没有该权限,并且系统会抛出异常。(如果是需要其他权限(例如 ACTIVITY_RECOGNITION)的健康服务,则不适用。)调用 PermissionChecker.checkSelfPermission() 不会防止此问题。如果您的应用具有使用中权限,并调用 checkSelfPermission() 来检查它是否具有该权限,即使应用在后台运行,该方法也会返回 PERMISSION_GRANTED。当该方法返回 PERMISSION_GRANTED 时,表示“您的应用在使用期间拥有此权限”。

因此,如果您的前台服务需要“在使用时”权限,您必须在应用具有可见 activity 时调用 Context.startForegroundService()Context.bindService(),除非该服务属于定义的豁免情况之一。

对在使用时权限限制的豁免

在某些情况下,即使在应用 在后台运行时启动前台服务,当应用在前台运行时(即在使用时),仍然可以访问位置信息、摄像头和麦克风信息。

在这些情况下,如果服务声明的前台服务类型location 并且由具有 ACCESS_BACKGROUND_LOCATION 权限的应用启动,则该服务可以始终访问位置信息,即使应用在后台运行也是如此。

以下列表包含这些情况:

  • 系统组件启动该服务。
  • 该服务通过与应用微件进行交互启动。
  • 该服务通过与通知进行交互启动。
  • 该服务作为从其他可见应用发送的 PendingIntent 开始。
  • 该服务由作为在设备所有者模式下运行的设备政策控制器的应用启动。
  • 该服务由提供 VoiceInteractionService 的应用启动。
  • 该服务由具有 START_ACTIVITIES_FROM_BACKGROUND 特权的应用启动。
确定应用中的哪些服务受到影响

测试您的应用时,请启动其前台服务。如果启动的服务对位置信息、麦克风和摄像头的访问受到限制,Logcat 中就会显示以下消息:

Foreground service started from background can not have \
location/camera/microphone access: service SERVICE_NAME