Dịch vụ trên nền trước

Dịch vụ trên nền trước thực hiện các thao tác mà người dùng có thể nhận thấy.

Dịch vụ trên nền trước sẽ hiển thị thông báo trên thanh trạng thái để giúp người dùng biết rằng ứng dụng của bạn đang thực hiện một nhiệm vụ ở nền trước và đang sử dụng tài nguyên hệ thống.

Sau đây là ví dụ về ứng dụng dùng dịch vụ trên nền trước:

  • Một ứng dụng trình phát nhạc có chức năng phát nhạc trong dịch vụ trên nền trước. Thông báo có thể hiển thị bài hát hiện tại đang được phát.
  • Một ứng dụng thể dục ghi lại quãng đường chạy của một người dùng trong dịch vụ trên nền trước, sau khi nhận được quyền của người dùng đó. Thông báo có thể hiển thị quãng đường mà người dùng đã đi trong phiên thể dục hiện tại.

Chỉ sử dụng dịch vụ trên nền trước khi ứng dụng của bạn cần thực hiện một tác vụ mà người dùng có thể chú ý, ngay cả khi họ không tương tác trực tiếp với ứng dụng. Nếu hành động đó có tầm quan trọng đủ thấp để bạn muốn sử dụng thông báo có mức độ ưu tiên tối thiểu, hãy tạo tác vụ trong nền.

Tài liệu này mô tả quyền cần thiết để sử dụng dịch vụ trên nền trước, cách bắt đầu và xoá dịch vụ trên nền trước khỏi nền. Hướng dẫn này cũng mô tả cách liên kết một số trường hợp sử dụng nhất định với kiểu dịch vụ trên nền trước, cũng như các hạn chế truy cập có hiệu lực khi bạn bắt đầu dịch vụ trên nền trước qua một ứng dụng đang chạy ở chế độ nền.

Người dùng có thể đóng thông báo theo mặc định

Kể từ Android 13 (API cấp 33), theo mặc định, người dùng có thể đóng thông báo liên kết với một dịch vụ trên nền trước. Để làm vậy, người dùng thực hiện cử chỉ vuốt trên thông báo. Thông thường, thông báo sẽ không bị loại bỏ trừ phi dịch vụ trên nền trước bị dừng hoặc bị xoá khỏi nền trước.

Nếu bạn muốn người dùng không thể đóng thông báo, hãy chuyển true vào phương thức setOngoing() khi bạn tạo thông báo bằng Notification.Builder.

Những dịch vụ hiển thị thông báo ngay lập tức

Nếu dịch vụ trên nền trước có ít nhất một trong các đặc điểm sau, thì hệ thống sẽ hiển thị thông báo liên quan ngay sau khi dịch vụ bắt đầu, ngay cả trên các thiết bị chạy Android 12 trở lên:

Trên Android 13 (API cấp 33) trở lên, nếu người dùng từ chối quyền gửi thông báo, họ vẫn thấy thông báo liên quan đến các dịch vụ trên nền trước trong Task Manager (Trình quản lý tác vụ) nhưng không thấy các thông báo đó trong ngăn thông báo.

Khai báo dịch vụ trên nền trước trong tệp kê khai

Trong tệp kê khai của ứng dụng, hãy khai báo từng dịch vụ trên nền trước của ứng dụng bằng một phần tử <service>. Đối với mỗi dịch vụ, hãy sử dụng thuộc tính android:foregroundServiceType để khai báo loại công việc mà dịch vụ đó thực hiện.

Ví dụ: nếu ứng dụng của bạn tạo một dịch vụ trên nền trước có phát nhạc, thì bạn có thể khai báo dịch vụ đó như sau:

<manifest xmlns:android="http://schemas.android.com/apk/res/android" ...>

    <service
        android:name=".MyMediaPlaybackService"
        android:foregroundServiceType="mediaPlayback"
        android:exported="false">
    </service>
</manifest>

Nếu có nhiều kiểu áp dụng cho dịch vụ của bạn, hãy phân tách các kiểu đó bằng toán tử |. Ví dụ: một dịch vụ sử dụng máy ảnh và micrô sẽ khai báo như sau:

android:foregroundServiceType="camera|microphone"

Yêu cầu quyền sử dụng dịch vụ trên nền trước

Các ứng dụng nhắm đến Android 9 (API cấp 28) trở lên và sử dụng các dịch vụ trên nền trước cần yêu cầu FOREGROUND_SERVICE trong tệp kê khai ứng dụng, như minh hoạ trong đoạn mã sau. Đây là quyền thông thường, do đó, hệ thống sẽ tự động cấp quyền đó cho ứng dụng yêu cầu.

Ngoài ra, nếu nhắm đến API cấp 34 trở lên, thì ứng dụng phải yêu cầu loại quyền phù hợp cho loại công việc mà dịch vụ trên nền trước sẽ thực hiện. Mỗi loại dịch vụ trên nền trước đều có một loại quyền tương ứng. Ví dụ: nếu một ứng dụng chạy một dịch vụ trên nền trước có sử dụng máy ảnh, thì bạn phải yêu cầu cả quyền FOREGROUND_SERVICEFOREGROUND_SERVICE_CAMERA. Đây đều là những quyền thông thường nên hệ thống sẽ tự động cấp những quyền đó nếu được liệt kê trong tệp kê khai.

<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>

Điều kiện tiên quyết đối với dịch vụ trên nền trước

Kể từ Android 14 (API cấp 34), khi bạn chạy một dịch vụ trên nền trước, hệ thống sẽ kiểm tra các điều kiện tiên quyết cụ thể dựa trên loại dịch vụ. Chẳng hạn nếu bạn cố gắng chạy một dịch vụ trên nền trước thuộc loại location, thì hệ thống sẽ kiểm tra để đảm bảo ứng dụng của bạn đã có quyền ACCESS_COARSE_LOCATION hoặc ACCESS_FINE_LOCATION. Nếu không, hệ thống sẽ gửi SecurityException.

Vì lý do này, bạn phải xác nhận rằng các điều kiện tiên quyết bắt buộc được đáp ứng trước khi bắt đầu dịch vụ trên nền trước. Tài liệu về loại dịch vụ trên nền trước liệt kê các điều kiện tiên quyết bắt buộc cho từng loại dịch vụ trên nền trước.

Bắt đầu dịch vụ trên nền trước

Trước khi bạn yêu cầu hệ thống chạy một dịch vụ dưới dạng dịch vụ trên nền trước, hãy khởi động chính dịch vụ đó:

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);

Bên trong dịch vụ, thường là trong onStartCommand(), bạn có thể yêu cầu dịch vụ chạy ở nền trước. Để thực hiện việc này, hãy gọi ServiceCompat.startForeground() (có trong androidx-core 1.12 trở lên). Phương thức này có các thông số sau:

Các loại này có thể là tập hợp con của các loại được khai báo trong tệp kê khai, tuỳ thuộc vào trường hợp sử dụng cụ thể. Sau đó, nếu cần thêm các loại dịch vụ khác, bạn có thể gọi lại startForeground().

Ví dụ: giả sử một ứng dụng thể dục chạy dịch vụ theo dõi đang chạy luôn cần thông tin location, nhưng có thể cần hoặc không cần phát nội dung nghe nhìn. Bạn cần khai báo cả locationmediaPlayback trong tệp kê khai. Nếu người dùng bắt đầu một lượt chạy và chỉ muốn theo dõi vị trí của mình, thì ứng dụng của bạn nên gọi startForeground() và chỉ chuyển quyền ACCESS_FINE_LOCATION. Sau đó, nếu người dùng muốn bắt đầu phát âm thanh, hãy gọi lại startForeground() và truyền tổ hợp bitwise của tất cả các loại dịch vụ trên nền trước (trong trường hợp này là ACCESS_FINE_LOCATION|FOREGROUND_SERVICE_MEDIA_PLAYBACK).

Dưới đây là ví dụ về cách khởi chạy một dịch vụ máy ảnh trên nền trước:

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)
            }
            // ...
        }
    }

    //...
}

Xoá dịch vụ khỏi nền trước

Để xoá dịch vụ khỏi nền trước, hãy gọi stopForeground(). Phương thức này lấy một giá trị boolean cho biết liệu có xoá thông báo trên thanh trạng thái hay không. Xin lưu ý rằng dịch vụ sẽ tiếp tục chạy.

Nếu bạn dừng dịch vụ trong khi chạy ở nền trước, thì thông báo của dịch vụ đó sẽ bị xoá.

Xử lý việc dừng những ứng dụng chạy dịch vụ trên nền trước do người dùng yêu cầu

Ở cuối ngăn thông báo là một nút cho biết số lượng ứng dụng hiện đang chạy trong nền. Khi bạn nhấn vào nút này, một hộp thoại sẽ xuất hiện và tên của các ứng dụng sẽ xuất hiện. Nút Dừng
    nằm ở bên phải của mỗi ứng dụng
Hình 1. Quy trình công việc của Trình quản lý tác vụ trên các thiết bị chạy Android 13 trở lên.

Kể từ Android 13 (API cấp 33), người dùng có thể hoàn tất quy trình công việc qua ngăn thông báo để dừng một ứng dụng có dịch vụ trên nền trước đang hoạt động, bất kể phiên bản SDK mục tiêu của ứng dụng đó là gì. Thành phần này có tên là Task Manager (Trình quản lý tác vụ) cho thấy danh sách các ứng dụng hiện đang chạy dịch vụ trên nền trước.

Danh sách này được gắn nhãn Ứng dụng đang hoạt động. Bên cạnh mỗi ứng dụng là nút Stop (Dừng). Hình 1 minh hoạ quy trình làm việc của Trình quản lý tác vụ trên thiết bị chạy Android 13.

Khi người dùng nhấn nút Stop (Dừng) bên cạnh ứng dụng của bạn trong Task Manager, các hành động sau đây sẽ xảy ra:

  • Hệ thống sẽ xoá ứng dụng của bạn khỏi bộ nhớ. Do đó, toàn bộ ứng dụng sẽ ngừng hoạt động, chứ không chỉ dịch vụ trên nền trước đang chạy.
  • Hệ thống sẽ xoá ngăn xếp lui cho hoạt động của ứng dụng.
  • Mọi quá trình phát nội dung nghe nhìn sẽ dừng.
  • Thông báo liên kết với dịch vụ trên nền trước sẽ bị xoá.
  • Ứng dụng của bạn vẫn xuất hiện trong nhật ký.
  • Các công việc đã lên lịch sẽ thực thi theo thời gian đã lên lịch.
  • Chuông báo sẽ kêu vào thời gian hoặc khung thời gian đã lên lịch.

Để kiểm thử xem ứng dụng của bạn có hoạt động như dự kiến trong khi và sau khi người dùng dừng ứng dụng hay không, hãy chạy lệnh ADB sau đây trong một cửa sổ dòng lệnh:

adb shell cmd activity stop-app PACKAGE_NAME

Miễn trừ

Hệ thống đưa ra nhiều cấp độ miễn trừ cho một số loại ứng dụng như mô tả trong phần sau.

Các trường hợp miễn trừ được áp dụng theo từng ứng dụng, chứ không phải theo quy trình. Nếu hệ thống miễn trừ một quy trình trong ứng dụng, thì mọi quy trình khác trong ứng dụng đó cũng được miễn trừ.

Miễn xuất hiện trong Trình quản lý tác vụ

Các ứng dụng sau có thể chạy dịch vụ trên nền trước và hoàn toàn không xuất hiện trong Trình quản lý tác vụ:

Miễn trừ bị người dùng chặn

Khi các loại ứng dụng sau chạy một dịch vụ trên nền trước, chúng sẽ xuất hiện trong Trình quản lý tác vụ nhưng không có nút Stop (Dừng) bên cạnh tên ứng dụng để người dùng nhấn vào:

Dùng các API được thiết kế riêng thay vì dịch vụ trên nền trước

Trong nhiều trường hợp sử dụng, bạn có thể dùng các API nền tảng hoặc Jetpack API để thực hiện công việc mà có thể bạn lại dùng dịch vụ trên nền trước. Nếu có một API được xây dựng cho mục đích phù hợp, thì hầu như bạn luôn nên sử dụng API đó thay vì sử dụng dịch vụ trên nền trước. Các API được tạo theo mục đích thường cung cấp thêm các tính năng cụ thể cho trường hợp sử dụng mà lẽ ra bạn phải tự xây dựng. Ví dụ: API bong bóng xử lý logic giao diện người dùng phức tạp cho các ứng dụng nhắn tin cần triển khai các tính năng bong bóng trò chuyện.

Tài liệu về các loại dịch vụ trên nền trước liệt kê các phương án thay thế phù hợp nên sử dụng thay vì dịch vụ trên nền trước.

Quy định hạn chế khi bắt đầu dịch vụ trên nền trước từ chế độ nền

Các ứng dụng nhắm đến Android 12 trở lên không thể bắt đầu dịch vụ trên nền trước trong khi đang chạy ở chế độ nền, ngoại trừ một vài trường hợp đặc biệt. Nếu một ứng dụng cố gắng bắt đầu một dịch vụ trên nền trước trong khi ứng dụng đó chạy ở chế độ nền và dịch vụ đó trên nền trước không đáp ứng được một trong những trường hợp ngoại lệ, thì hệ thống sẽ gửi ra một ForegroundServiceStartNotAllowedException.

Ngoài ra, nếu muốn chạy một dịch vụ trên nền trước cần quyền khi đang sử dụng (ví dụ: quyền cảm biến cơ thể, máy ảnh, micrô hoặc thông tin vị trí), thì ứng dụng đó không thể tạo dịch vụ đó khi ứng dụng chạy ở chế độ nền, ngay cả khi ứng dụng thuộc một trong các trường hợp miễn trừ khỏi các hạn chế về khởi động ở chế độ nền. Lý do của việc này được giải thích trong phần Hạn chế khi bắt đầu các dịch vụ trên nền trước cần quyền trong khi sử dụng.

Miễn khỏi các hạn chế khi khởi động ở chế độ nền

Trong các trường hợp sau, ứng dụng của bạn có thể bắt đầu các dịch vụ trên nền trước ngay cả khi ứng dụng đó chạy ở chế độ nền:

Quy định hạn chế khi bắt đầu các dịch vụ trên nền trước cần có quyền khi sử dụng

Trên Android 14 (API cấp 34) trở lên, có một số trường hợp đặc biệt mà bạn cần lưu ý nếu đang bắt đầu một dịch vụ trên nền trước và cần có quyền trong khi sử dụng.

Nếu ứng dụng của bạn nhắm đến Android 14 trở lên, thì hệ điều hành sẽ kiểm tra khi bạn tạo dịch vụ trên nền trước để đảm bảo ứng dụng có tất cả các quyền thích hợp cho loại dịch vụ đó. Ví dụ: khi bạn tạo một dịch vụ trên nền trước thuộc loại micrô, hệ điều hành sẽ xác minh rằng ứng dụng của bạn hiện có quyền RECORD_AUDIO. Nếu bạn không có quyền đó, hệ thống sẽ gửi một SecurityException.

Đối với các quyền khi đang được sử dụng, việc này có thể gây ra vấn đề tiềm ẩn. Nếu có quyền khi đang sử dụng, ứng dụng của bạn sẽ chỉ có quyền đó khi đang ở nền trước. Điều này có nghĩa là nếu ứng dụng của bạn chạy trong chế độ nền và cố gắng tạo một dịch vụ trên nền trước thuộc loại máy ảnh, vị trí hoặc micrô, thì hệ thống sẽ thấy rằng ứng dụng hiện không có các quyền cần thiết và sẽ gửi ra một SecurityException.

Tương tự, nếu ứng dụng của bạn chạy ở chế độ nền và tạo một dịch vụ sức khoẻ cần có quyền BODY_SENSORS_BACKGROUND, thì ứng dụng hiện không có quyền đó và hệ thống sẽ gửi ra một trường hợp ngoại lệ. (Điều này không áp dụng nếu đó là một dịch vụ sức khoẻ cần có các quyền khác, chẳng hạn như ACTIVITY_RECOGNITION.) Việc gọi ContextCompat.checkSelfPermission() không ngăn chặn sự cố này. Nếu ứng dụng của bạn có quyền khi đang sử dụng và gọi checkSelfPermission() để kiểm tra xem có quyền đó hay không, thì phương thức này sẽ trả về PERMISSION_GRANTED ngay cả khi ứng dụng chạy ở chế độ nền. Khi phương thức này trả về PERMISSION_GRANTED, có thông báo cho biết "ứng dụng của bạn có quyền này trong khi ứng dụng đang được sử dụng".

Vì lý do này, nếu dịch vụ trên nền trước cần quyền khi đang sử dụng, thì bạn phải gọi Context.startForegroundService() hoặc Context.bindService() trong khi ứng dụng của bạn có một hoạt động hiển thị, trừ phi dịch vụ đó thuộc một trong các trường hợp miễn trừ đã xác định.

Miễn khỏi các hạn chế đối với quyền khi đang sử dụng

Trong một số trường hợp, ngay cả khi một dịch vụ trên nền trước được khởi động trong khi ứng dụng chạy ở chế độ nền, ứng dụng đó vẫn có thể truy cập vào thông tin vị trí, máy ảnh và micrô trong khi ứng dụng đó chạy ở nền trước ("trong khi sử dụng").

Trong những trường hợp tương tự, nếu dịch vụ khai báo một loại dịch vụ trên nền trướclocation và được bắt đầu bởi một ứng dụng có quyền ACCESS_BACKGROUND_LOCATION, thì dịch vụ này có thể truy cập thông tin vị trí mọi lúc, ngay cả khi ứng dụng đó chạy trong nền.

Danh sách sau đây chứa các trường hợp sau:

  • Một thành phần hệ thống sẽ khởi động dịch vụ.
  • Dịch vụ bắt đầu bằng cách tương tác với các tiện ích ứng dụng.
  • Dịch vụ bắt đầu bằng cách tương tác với một thông báo.
  • Dịch vụ bắt đầu dưới dạng PendingIntent được gửi từ một ứng dụng khác hiển thị.
  • Dịch vụ bắt đầu bằng một ứng dụng là trình kiểm soát chính sách thiết bị chạy ở chế độ chủ sở hữu thiết bị.
  • Dịch vụ này bắt đầu bằng một ứng dụng cung cấp VoiceInteractionService.
  • Dịch vụ này bắt đầu bởi một ứng dụng có quyền đặc quyền START_ACTIVITIES_FROM_BACKGROUND.
Xác định những dịch vụ bị ảnh hưởng trong ứng dụng của bạn

Khi kiểm thử ứng dụng, hãy bắt đầu các dịch vụ trên nền trước. Nếu một dịch vụ đã bắt đầu hạn chế quyền truy cập vào thông tin vị trí, micrô và máy ảnh, thì thông báo sau sẽ xuất hiện trong Logcat:

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