前景服務

前景服務執行的作業可為使用者察覺。

前景服務會顯示狀態列通知,讓使用者瞭解應用程式正在前景執行工作及消耗系統資源。

以下是使用前景服務的應用程式範例:

  • 音樂播放器應用程式在前景服務中播放音樂,並透過通知顯示目前播放的歌曲。
  • 健身應用程式先取得使用者授予的權限,然後在前景服務中記錄跑步情況。通知可能會顯示使用者在目前健身工作階段中移動的距離。

只有在應用程式需要執行使用者可察覺的工作時,才能使用前景服務,就算使用者並未直接與應用程式互動也一樣。如果動作的重要性不高,您可以改為使用最低優先順序的通知,建立背景工作

本文說明使用前景服務所需的權限,以及如何啟動前景服務並從背景移除。此外,這篇文章也說明如何將特定用途與前景服務類型建立關聯,以及從背景執行的應用程式啟動前景服務時,會生效的存取限制。

使用者可以根據預設關閉通知

從 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 版本為何,使用者都能透過通知導覽匣完成工作流程,停止執行前景服務的應用程式。這個可用性元素稱為「Task Manager」,會顯示目前執行前景服務的應用程式清單。

這份清單的標籤為「Active apps」。每個應用程式旁邊都有一個「停止」按鈕。圖 1 說明執行 Android 13 的裝置上的工作管理員工作流程。

當使用者在「工作管理員」中按下應用程式旁的「停止」按鈕時,系統會執行以下動作:

  • 系統會從記憶體中移除應用程式。因此,整個應用程式都會停止,而非僅限於執行中的前景服務。
  • 系統會移除應用程式的活動回溯堆疊。
  • 所有媒體播放都會停止。
  • 系統會移除與前景服務相關聯的通知。
  • 您的應用程式仍會保留在歷史記錄中。
  • 已排定的工作會在預定時間執行。
  • 鬧鐘會在預定時間或時間回溯期響鈴。

如要測試應用程式在使用者停止應用程式時和之後的行為是否符合預期,請在終端機視窗中執行下列 ADB 指令:

adb shell cmd activity stop-app PACKAGE_NAME

豁免資格

系統針對特定類型的應用程式提供多種豁免情形,詳情請參閱下文。

豁免條款是針對應用程式,而非針對程序。如果系統豁免應用程式中的一個程序,該應用程式中的所有其他程序也會豁免。

不顯示在工作管理員中的豁免條件

以下應用程式可執行前景服務,且不會顯示在工作管理員中:

使用者無法停止的例外狀況

當下列類型的應用程式執行前景服務時,這些服務會顯示在「工作管理員」中,但應用程式名稱旁沒有「停止」按鈕,使用者無法輕觸該按鈕:

使用專用 API 而非前景服務

在許多用途中,您可以使用平台或 Jetpack API 執行前景服務的其他工作。如果有適合的專用 API,您幾乎應該一律使用該 API,而非使用前景服務。專用 API 通常會提供額外的用途專屬功能,否則您必須自行建構這些功能。舉例來說, Bubbles API 會為需要實作聊天氣泡功能的訊息應用程式,處理複雜的 UI 邏輯。

前景服務類型的說明文件列出了可取代前景服務的替代方案。

從背景啟動前景服務的限制

應用程式若指定 Android 12 以上版本,就無法在背景執行時啟動前景服務 (少數特殊情況除外)。如果應用程式在背景執行時嘗試啟動前景服務,且前景服務不符合其中一個例外情況,系統會擲回 ForegroundServiceStartNotAllowedException

此外,如果應用程式想要啟動需要使用中權限 (例如人體感應器、相機、麥克風或位置資訊權限) 的前景服務,則即使應用程式符合背景啟動限制的豁免情況之一,也無法在背景執行時建立服務。詳情請參閱「啟動需要使用中權限的前景服務的限制」一節。

不受背景啟動限制的豁免條件

在下列情況下,即使應用程式在背景執行,也能啟動前景服務:

啟動需要使用中權限的前景服務的限制

在 Android 14 (API 級別 34) 以上版本中,如果您要啟動需要使用中權限的前景服務,請注意以下特殊情況。

如果應用程式指定 Android 14 以上版本,則在您建立前景服務時,作業系統會進行檢查,確保應用程式擁有該服務類型的所有適當權限。舉例來說,當您建立類型為 microphone 的前景服務時,作業系統會驗證應用程式目前是否具有 RECORD_AUDIO 權限。如果您沒有該權限,系統會擲回 SecurityException

對於使用中的權限,這可能會導致問題。如果應用程式具備「使用中」權限,則只有在前景運作時才具備這項權限。也就是說,如果應用程式處於背景執行狀態,並且嘗試建立相機、位置或麥克風類型的前景服務,系統會發現應用程式「目前」沒有必要的權限,並擲回 SecurityException

同樣地,如果應用程式處於背景狀態,且建立需要 BODY_SENSORS 權限的健康服務,應用程式目前沒有該權限,系統會擲回例外狀況。(如果是需要不同權限的健康照護服務 (例如 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