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

Có hai bước để chạy dịch vụ trên nền trước từ ứng dụng. 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ự quảng bá 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, sẽ có một số hạn chế về thời điểm ứng dụng có thể chạy 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 khởi động dịch vụ trên nền trước trong khi ứng dụng đang chạy ở chế độ nền, ngoại trừ một vài 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 Hạn chế khi khởi động dịch vụ trên nền trước từ nền.

  • Các ứ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 phù hợp cho loại dịch vụ trên nền trước. Khi ứng dụng cố gắng đẩy 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.

Chạy một dịch vụ

Để chạy một dịch vụ trên 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 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 sẽ 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. Bên trong chính dịch vụ, bạn cần gọi ServiceCompat.startForeground() để quảng bá dịch vụ đó thành một 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() nhận 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 một dịch vụ theo dõi 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 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à dịch vụ máy ảnh sẽ sử dụng để tự quảng bá lên 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 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 đó phải cho người dùng biết về vấn đề.
  • Các loại dịch vụ trên nền trước khác nhau được giới thiệu với các phiên bản nền tảng Android khác nhau. 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 sẽ kiểm tra ForegroundServiceStartNotAllowedException trong trường hợp mã đó đang cố gắng khởi động một dịch vụ trên 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 quảng bá dịch vụ lên nền trước trong khi ứng dụng đang chạy ở chế độ nền).