前景服務

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

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

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

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

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

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

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

從 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 版本為何,使用者都能透過通知導覽匣完成工作流程,停止執行前景服務的應用程式。這個預設用途稱為「工作管理員」,會顯示目前正在執行前景服務的應用程式清單。

這份清單的標籤為「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