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

Có 2 bước để chạy một dịch vụ trên 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 để dịch vụ gọi ServiceCompat.startForeground() để tự chuyển thành dịch vụ trên 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, có một số hạn chế về thời điểm ứng dụng có thể chạy một dịch vụ trên nền trước.

  • Các ứng dụng nhắm đến Android 12 (API cấp 31) trở lên không được phép bắt đầu một dịch vụ trên nền trước trong khi ứng dụng ở chế độ nền, ngoại trừ một số trường hợp 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 bài viết Các hạn chế khi khởi động dịch vụ trên nền trước từ nền.

  • Những ứng dụng nhắm đến Android 14 (API cấp 34) trở lên phải yêu cầu các quyền thích hợp cho loại dịch vụ trên nền trước. Khi ứng dụng cố gắng chuyển 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 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, 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ụ trên 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ụ trên nền trước.

Ra mắt dịch vụ

Để khởi chạy một dịch vụ trên nền trước, trước tiên, bạn phải khở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ụ trên 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 khởi chạy một dịch vụ. Tuy nhiên, dịch vụ này chưa chạy ở nền trước. Trong chính dịch vụ này, bạn cần gọi ServiceCompat.startForeground() để chuyển dịch vụ thành dịch vụ trên nền trước.

Đưa một dịch vụ lên nền trước

Sau khi một dịch vụ đang chạy, bạn cần gọi ServiceCompat.startForeground() để yêu cầu dịch vụ chạy ở 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() lấy các thông số sau:

Các loại dịch vụ trên nền trước mà bạn truyền đến startForeground() 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 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ả locationmediaPlayback 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 nên 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 theo bit 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).

Ví dụ sau đây cho thấy mã mà một dịch vụ camera sẽ dùng để tự quảng bá thành dịch vụ 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 =
            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 thực sự không có các quyền chính xác, ứng dụng phải cho người dùng biết về vấn đề này.
  • Các loại dịch vụ trên nền trước khác nhau đã được ra mắt 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 mà ứng dụng đ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 ứng dụng đang cố gắng khởi động một dịch vụ trên nền trước trong tình huống không được phép (ví dụ: nếu ứng dụng đang cố gắng chuyển dịch vụ lên nền trước trong khi ứng dụng đang ở chế độ nền).