Hỗ trợ trình thực thi (Worker) chạy trong thời gian dài

WorkManager tích hợp sẵn dịch vụ hỗ trợ cho các trình thực thi chạy trong thời gian dài. Trong những trường hợp như vậy, WorkManager có thể ra tín hiệu cho hệ điều hành rằng cần duy trì quy trình này ở trạng thái hoạt động (nếu có thể) trong khi thực thi công việc này. Những Worker này có thể chạy lâu hơn 10 phút. Các trường hợp sử dụng mẫu cho tính năng mới này bao gồm tải lên hoặc tải xuống hàng loạt (không thể chia nhỏ), phân tích mô hình học máy cục bộ hoặc công việc quan trọng với người dùng ứng dụng.

WorkManager nâng cao sẽ thay bạn quản lý và chạy dịch vụ trên nền trước để thực thi WorkRequest, đồng thời hiển thị một thông báo có thể định cấu hình.

ListenableWorker hiện đã hỗ trợ API setForegroundAsync()CoroutineWorker hỗ trợ việc tạm ngưng API setForeground(). Các API này cho phép nhà phát triển chỉ định rằng WorkRequest này là quan trọng (ở góc độ người dùng) hay hoạt động lâu dài.

Bắt đầu bằng2.3.0-alpha03, WorkManager còn cho phép bạn tạo một PendingIntent (có thể dùng để huỷ worker mà không phải đăng ký thành phần Android mới bằng API createCancelPendingIntent()). Phương thức này đặc biệt hữu ích khi dùng với các API setForegroundAsync() hoặc setForeground() (dùng để thêm một hành động thông báo để huỷ Worker).

Tạo và quản lý trình thực thi chạy trong thời gian dài

Bạn sẽ dùng một phương pháp không quá khác biệt, tuỳ thuộc vào việc bạn đang lập trình trong Kotlin hay Java.

Kotlin

Nhà phát triển Kotlin nên sử dụng CoroutineWorker. Thay vì sử dụng setForegroundAsync(), bạn có thể sử dụng phiên bản tạm ngưng setForeground().

class DownloadWorker(context: Context, parameters: WorkerParameters) :
   CoroutineWorker(context, parameters) {

   private val notificationManager =
       context.getSystemService(Context.NOTIFICATION_SERVICE) as
               NotificationManager

   override suspend fun doWork(): Result {
       val inputUrl = inputData.getString(KEY_INPUT_URL)
                      ?: return Result.failure()
       val outputFile = inputData.getString(KEY_OUTPUT_FILE_NAME)
                      ?: return Result.failure()
       // Mark the Worker as important
       val progress = "Starting Download"
       setForeground(createForegroundInfo(progress))
       download(inputUrl, outputFile)
       return Result.success()
   }

   private fun download(inputUrl: String, outputFile: String) {
       // Downloads a file and updates bytes read
       // Calls setForeground() periodically when it needs to update
       // the ongoing Notification
   }
   // Creates an instance of ForegroundInfo which can be used to update the
   // ongoing notification.
   private fun createForegroundInfo(progress: String): ForegroundInfo {
       val id = applicationContext.getString(R.string.notification_channel_id)
       val title = applicationContext.getString(R.string.notification_title)
       val cancel = applicationContext.getString(R.string.cancel_download)
       // This PendingIntent can be used to cancel the worker
       val intent = WorkManager.getInstance(applicationContext)
               .createCancelPendingIntent(getId())

       // Create a Notification channel if necessary
       if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
           createChannel()
       }

       val notification = NotificationCompat.Builder(applicationContext, id)
           .setContentTitle(title)
           .setTicker(title)
           .setContentText(progress)
           .setSmallIcon(R.drawable.ic_work_notification)
           .setOngoing(true)
           // Add the cancel action to the notification which can
           // be used to cancel the worker
           .addAction(android.R.drawable.ic_delete, cancel, intent)
           .build()

       return ForegroundInfo(notificationId, notification)
   }

   @RequiresApi(Build.VERSION_CODES.O)
   private fun createChannel() {
       // Create a Notification channel
   }

   companion object {
       const val KEY_INPUT_URL = "KEY_INPUT_URL"
       const val KEY_OUTPUT_FILE_NAME = "KEY_OUTPUT_FILE_NAME"
   }
}

Java

Các nhà phát triển sử dụng ListenableWorker hoặc Worker có thể gọi API setForegroundAsync(), API này sẽ trả về ListenableFuture<Void>. Bạn cũng có thể gọi setForegroundAsync() để cập nhật Notification đang diễn ra.

Sau đây là ví dụ đơn giản về một worker chạy trong thời gian dài thực hiện tải tệp xuống. Worker này sẽ theo dõi tiến trình để cập nhật Notification (để cho biết tiến trình tải xuống) đang diễn ra.

public class DownloadWorker extends Worker {
   private static final String KEY_INPUT_URL = "KEY_INPUT_URL";
   private static final String KEY_OUTPUT_FILE_NAME = "KEY_OUTPUT_FILE_NAME";

   private NotificationManager notificationManager;

   public DownloadWorker(
       @NonNull Context context,
       @NonNull WorkerParameters parameters) {
           super(context, parameters);
           notificationManager = (NotificationManager)
               context.getSystemService(NOTIFICATION_SERVICE);
   }

   @NonNull
   @Override
   public Result doWork() {
       Data inputData = getInputData();
       String inputUrl = inputData.getString(KEY_INPUT_URL);
       String outputFile = inputData.getString(KEY_OUTPUT_FILE_NAME);
       // Mark the Worker as important
       String progress = "Starting Download";
       setForegroundAsync(createForegroundInfo(progress));
       download(inputUrl, outputFile);
       return Result.success();
   }

   private void download(String inputUrl, String outputFile) {
       // Downloads a file and updates bytes read
       // Calls setForegroundAsync(createForegroundInfo(myProgress))
       // periodically when it needs to update the ongoing Notification.
   }

   @NonNull
   private ForegroundInfo createForegroundInfo(@NonNull String progress) {
       // Build a notification using bytesRead and contentLength

       Context context = getApplicationContext();
       String id = context.getString(R.string.notification_channel_id);
       String title = context.getString(R.string.notification_title);
       String cancel = context.getString(R.string.cancel_download);
       // This PendingIntent can be used to cancel the worker
       PendingIntent intent = WorkManager.getInstance(context)
               .createCancelPendingIntent(getId());

       if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
           createChannel();
       }

       Notification notification = new NotificationCompat.Builder(context, id)
               .setContentTitle(title)
               .setTicker(title)
               .setSmallIcon(R.drawable.ic_work_notification)
               .setOngoing(true)
               // Add the cancel action to the notification which can
               // be used to cancel the worker
               .addAction(android.R.drawable.ic_delete, cancel, intent)
               .build();

       return new ForegroundInfo(notificationId, notification);
   }

   @RequiresApi(Build.VERSION_CODES.O)
   private void createChannel() {
       // Create a Notification channel
   }
}

Thêm loại dịch vụ trên nền trước cho một worker chạy trong thời gian dài

Nếu ứng dụng của bạn nhắm đến Android 10 (API cấp 29) trở lên và có chứa worker chạy trong thời gian dài yêu cầu quyền truy cập vào vị trí, hãy thể hiện rằng worker đó sử dụng loại dịch vụ trên nền trước của location. Ngoài ra, nếu ứng dụng của bạn nhắm đến Android 11 (API cấp độ 30) trở lên và có chứa worker chạy trong thời gian dài yêu cầu quyền truy cập vào máy ảnh hoặc micrô, hãy khai báo loại dịch vụ trên nền trước camera hoặc microphone tương ứng.

Để thêm các loại dịch vụ trên nền trước này, hãy hoàn thành các bước được mô tả ở phần sau đây.

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

Khai báo loại dịch vụ trên nền trước của worker trong tệp kê khai của ứng dụng. Trong ví dụ sau, worker này yêu cầu quyền truy cập vào vị trí và micrô:

AndroidManifest.xml

<service
   android:name="androidx.work.impl.foreground.SystemForegroundService"
   android:foregroundServiceType="location|microphone"
   tools:node="merge" />

Chỉ định loại dịch vụ trên nền trước trong thời gian chạy

Khi bạn gọi setForeground() hoặc setForegroundAsync(), hãy chỉ định loại dịch vụ trên nền trước là FOREGROUND_SERVICE_TYPE_LOCATION, FOREGROUND_SERVICE_TYPE_CAMERA hoặc FOREGROUND_SERVICE_TYPE_MICROPHONE như được hiển thị trong đoạn mã sau đây.

MyLocationAndMicrophoneWorker

Kotlin

private fun createForegroundInfo(progress: String): ForegroundInfo {
   // ...
   return ForegroundInfo(NOTIFICATION_ID, notification,
           FOREGROUND_SERVICE_TYPE_LOCATION or
FOREGROUND_SERVICE_TYPE_MICROPHONE) }

Java

@NonNull
private ForegroundInfo createForegroundInfo(@NonNull String progress) {
   // Build a notification...
   Notification notification = ...;
   return new ForegroundInfo(NOTIFICATION_ID, notification,
           FOREGROUND_SERVICE_TYPE_LOCATION | FOREGROUND_SERVICE_TYPE_MICROPHONE);
}