前台服务

前台服务执行用户可以注意到的操作。

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

以下是一些使用前台服务的应用示例

  • 一个在前台服务中播放音乐的音乐播放器应用。通知可能会显示正在播放的当前歌曲。
  • 一个在获得用户许可后在前台服务中记录用户跑步情况的健身应用。通知可能会显示用户在当前健身课程中行驶的距离。

仅当您的应用需要执行用户可以注意到的任务(即使他们没有直接与应用交互)时,才使用前台服务。如果操作的重要性较低,您希望使用最低优先级的通知,请改用后台任务

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

用户可以默认关闭通知

从 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()。此方法采用一个布尔值,指示是否也删除状态栏通知。请注意,服务将继续运行。

如果在服务在前台运行时停止它,则会将其通知删除。

处理用户启动的停止运行前台服务的应用

At the bottom of the notification drawer is a button that indicates the
    number of apps that are currently running in the background. When you press
    this button, a dialog appears, which lists the names of different apps. The
    Stop button is to the right of each app
图 1. 在运行 Android 13 或更高版本的设备上的任务管理器工作流程。

从 Android 13(API 级别 33)开始,用户可以完成从通知抽屉停止具有正在进行的前台服务的应用的工作流程,而不管该应用的目标 SDK 版本如何。此功能称为任务管理器,它显示了当前正在运行前台服务的应用列表。

此列表标记为活动应用。每个应用旁边都有一个停止按钮。图 1 说明了在运行 Android 13 的设备上的任务管理器工作流程。

当用户在任务管理器中按下应用旁边的停止按钮时,会发生以下操作

  • 系统会从内存中删除您的应用。因此,您的整个应用都会停止,而不仅仅是正在运行的前台服务。
  • 系统会删除应用的活动返回堆栈。
  • 任何媒体播放都会停止。
  • 与前台服务关联的通知将被删除。
  • 您的应用将保留在历史记录中。
  • 计划作业将在其计划时间执行。
  • 闹钟将在其计划时间或时间窗口响起。

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

adb shell cmd activity stop-app PACKAGE_NAME

豁免

系统为某些类型的应用提供了几个级别的豁免,以下各部分对此进行了说明。

豁免是针对每个应用的,而不是针对每个进程的。如果系统为应用中的一个进程提供了豁免,则该应用中的所有其他进程也将获得豁免。

完全豁免出现在任务管理器中

以下应用可以运行前台服务,并且根本不会出现在任务管理器中

豁免被用户停止

当以下类型的应用运行前台服务时,它们会出现在任务管理器中,但用户无法点击应用名称旁边的停止按钮

使用专用 API 而不是前台服务

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

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

从后台启动前台服务的限制

面向 Android 12 或更高版本的应用,不能在应用运行于后台时启动前台服务,除非属于少数特殊情况。如果应用尝试在后台运行时启动前台服务,且该前台服务不满足任何例外情况,系统将抛出ForegroundServiceStartNotAllowedException

此外,如果应用想要启动需要正在使用权限的前台服务(例如,人体传感器、摄像头、麦克风或位置权限),即使应用属于后台启动限制的例外情况之一,它也不能在应用处于后台时创建该服务。原因在需要正在使用权限的前台服务的启动限制部分进行了说明。

后台启动限制的例外情况

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

需要正在使用权限的前台服务的启动限制

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

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

对于正在使用权限,这会导致潜在的问题。如果应用具有正在使用权限,则它仅在处于前台时才具有该权限。这意味着如果应用处于后台,并且尝试创建类型为摄像头、位置或麦克风的前台服务,系统会发现应用当前不具有所需的权限,并抛出SecurityException

类似地,如果应用处于后台并创建需要BODY_SENSORS_BACKGROUND权限的健康服务,则应用当前不具有该权限,系统将抛出异常。(如果它是需要不同权限的健康服务,例如ACTIVITY_RECOGNITION,则不适用。)调用PermissionChecker.checkSelfPermission()不会解决此问题。如果应用具有正在使用权限,并且调用checkSelfPermission()来检查是否具有该权限,则即使应用处于后台,该方法也会返回PERMISSION_GRANTED。当方法返回PERMISSION_GRANTED时,表示“应用在使用时具有此权限”。

因此,如果前台服务需要正在使用权限,则必须在应用具有可见活动时调用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