前景服務

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

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

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

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

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

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

使用者預設能關閉通知

從 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 的裝置上,工作管理員工作流程。

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

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

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

adb shell cmd activity stop-app PACKAGE_NAME

豁免資格

系統為特定類型的應用程式提供幾種豁免層級,詳情請參閱以下各節。

豁免資格因應用程式而異,而非個別程序。如果系統豁免應用程式的一個程序,則該應用程式中的其他處理程序也不受限制。

豁免於工作管理員

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

  • 系統層級應用程式
  • 安全應用程式;也就是說,具有 ROLE_EMERGENCY 角色的應用程式
  • 處於展示模式的裝置

使用者無法停止豁免

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

使用專為特定用途建立的 API,而非前景服務

在許多用途中,您可以透過平台或 Jetpack API 執行原本可能需要使用前景服務的工作。如果已有合適的用途建構 API,您應一律使用此 API,而非前景服務。專門建構的 API 通常會提供其他用途專屬功能,您必須自行建構這些功能。舉例來說, Bubbles 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