Chạy dịch vụ trên nền trước

Có hai bước để chạy một dịch vụ nền trước từ ứng dụng của bạn. Trước tiên, bạn phải bắt đầu dịch vụ bằng cách gọi context.startForegroundService(). Sau đó, hãy yêu cầu dịch vụ gọi ServiceCompat.startForeground() để tự nâng cấp thành dịch vụ nền trước.

Điều kiện tiên quyết

Tuỳ thuộc vào cấp độ API mà ứng dụng của bạn nhắm đến, sẽ có một số quy định hạn chế về thời điểm ứng dụng có thể chạy dịch vụ nền trước.

  • Các ứng dụng nhắm đến Android 12 (cấp độ API 31) trở lên không được phép bắt đầu dịch vụ nền trước khi ứng dụng đang chạy ở chế độ nền, ngoại trừ một số trường hợp ngoại lệ cụ thể. Để biết thêm thông tin và thông tin về các trường hợp ngoại lệ đối với quy tắc này, hãy xem phần Các quy định hạn chế khi khởi động dịch vụ nền trước từ nền.

  • Các ứng dụng nhắm đến Android 14 (cấp độ API 34) trở lên phải yêu cầu các quyền thích hợp cho loại dịch vụ nền trước. Khi ứng dụng cố gắng quảng bá một dịch vụ lên nền trước, hệ thống sẽ kiểm tra các quyền thích hợp và gửi SecurityException nếu ứng dụng thiếu bất kỳ quyền nào. Ví dụ: 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. Tài liệu về loại dịch vụ nền trước liệt kê các điều kiện tiên quyết bắt buộc đối với từng loại dịch vụ nền trước.

Ra mắt dịch vụ

Để chạy một dịch vụ nền trước, trước tiên, bạn phải chạy dịch vụ đó dưới dạng một dịch vụ thông thường (không phải dịch vụ nền trước):

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

Các điểm chính về mã

  • Đoạn mã này sẽ khởi chạy một dịch vụ. Tuy nhiên, dịch vụ này chưa chạy trên nền trước. Bên trong chính dịch vụ, bạn cần gọi ServiceCompat.startForeground() để chuyển dịch vụ đó thành dịch vụ trên nền trước.

Quảng bá dịch vụ lên nền trước

Sau khi dịch vụ chạy, bạn cần gọi ServiceCompat.startForeground() để yêu cầu dịch vụ chạy trên nền trước. Thông thường, bạn sẽ gọi phương thức này trong phương thức onStartCommand() của dịch vụ.

ServiceCompat.startForeground() nhận các tham số sau:

Các loại dịch vụ nền trước mà bạn truyền đến startForeground() phải là một 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 theo dõi thể chất chạy dịch vụ theo dõi hoạt động chạy bộ 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ả location và mediaPlayback trong tệp kê khai. Nếu người dùng bắt đầu chạy và chỉ muốn theo dõi vị trí của họ, thì ứng dụng của bạn phải gọi startForeground() và chỉ truyề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ụ nền trước (trong trường hợp này là ACCESS_FINE_LOCATION|FOREGROUND_SERVICE_MEDIA_PLAYBACK).

Ví dụ sau đây cho thấy mã mà dịch vụ máy ảnh sẽ sử dụng để tự nâng cấp lên dịch vụ 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 =
            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)
            }
            // ...
        }
    }

    //...
}

Các điểm chính về mã

  • Ứng dụng đã khai báo trong tệp kê khai rằng ứng dụng cần có quyền CAMERA. Tuy nhiên, ứng dụng cũng phải kiểm tra trong thời gian chạy để đảm bảo người dùng đã cấp quyền đó. Nếu ứng dụng không thực sự có quyền chính xác, thì ứng dụng đó sẽ cho người dùng biết về vấn đề.
  • Các loại dịch vụ nền trước khác nhau được giới thiệu cùng với các phiên bản khác nhau của Nền tảng Android. Mã này kiểm tra phiên bản Android đang chạy và yêu cầu các quyền thích hợp.
  • Mã này kiểm tra ForegroundServiceStartNotAllowedException trong trường hợp mã đang cố gắng khởi động một dịch vụ nền trước trong một tình huống không được phép (ví dụ: nếu mã đang cố gắng đưa dịch vụ lên nền trước trong khi ứng dụng đang ở chế độ nền).