前景服務

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

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

以下列舉使用前景服務的應用程式:

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

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

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

使用者可以預設關閉通知

從 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" ...>

    <service
        android:name=".MyMediaPlaybackService"
        android:foregroundServiceType="mediaPlayback"
        android:exported="false">
    </service>
</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 =
            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 {
        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 的裝置上的工作管理員工作流程。

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

  • 系統會從記憶體中移除您的應用程式。因此,整個應用程式都會停止運作,而不只是執行中的前景服務。
  • 系統會移除應用程式的活動返回堆疊。
  • 所有媒體都會停止播放。
  • 系統會移除與前景服務相關聯的通知。
  • 應用程式會留存在歷史記錄中。
  • 已排定的工作會在排定的時間執行。
  • 鬧鐘會在預定時間或時間範圍響起。

如要測試應用程式在使用者停止應用程式期間和之後,應用程式是否正常運作,請在終端機視窗中執行下列 ADB 指令:

adb shell cmd activity stop-app PACKAGE_NAME

豁免資格

系統為特定類型的應用程式提供數個豁免等級,以下各節說明。

豁免資格是依應用程式區分,而非依程序。如果系統排除應用程式中的某個程序,則該應用程式中的所有其他程序也同樣具有豁免權。

不必在工作管理員中顯示豁免項目

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

使用者無法暫停的豁免資格

下列類型的應用程式執行前景服務時,會顯示在工作管理員中,但使用者輕觸的應用程式名稱旁將沒有「Stop」按鈕,以供使用者輕觸:

使用專門建構的 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_BACKGROUND 權限的健康服務,表示應用程式目前沒有該權限,且系統會擲回例外狀況。(如果健康服務需要其他權限 (例如 ACTIVITY_RECOGNITION),則不適用此做法)。呼叫 ContextCompat.checkSelfPermission() 並「不會」防止這個問題。如果應用程式擁有使用期間權限,且會呼叫 checkSelfPermission() 以檢查其是否具備該項權限,那麼即使應用程式在背景執行,此方法也會傳回 PERMISSION_GRANTED。方法傳回 PERMISSION_GRANTED 時,會顯示「您的應用程式在使用期間具備這項權限。」

因此,如果您的前景服務需要使用期間權限,就必須在應用程式有可見活動時呼叫 Context.startForegroundService()Context.bindService(),除非該服務屬於定義的豁免項目

在使用期間權限限制時不受限制

在某些情況下,即使應用程式在 背景執行時已啟動前景服務,該功能仍可在應用程式在前景執行時存取位置資訊、相機和麥克風資訊 (「使用期間」)。

在這類情況下,locationACCESS_BACKGROUND_LOCATION

以下清單列出下列情況:

  • 系統元件會啟動服務。
  • 服務會先與應用程式小工具互動。
  • 服務會先與通知互動。
  • 服務以 PendingIntent 啟動,可從另一個可見的應用程式傳送。
  • 服務是由應用程式在裝置擁有者模式下執行的裝置政策控制器啟動。
  • 服務會從提供 VoiceInteractionService 的應用程式啟動。
  • 服務啟動的應用程式為具備 START_ACTIVITIES_FROM_BACKGROUND 特殊權限的應用程式。
確認應用程式受影響的服務

測試應用程式時,請啟動其前景服務。如果啟動的服務限制了位置、麥克風和相機的存取權,Logcat 中會顯示以下訊息:

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