WorkManager 內建支援長時間執行的 worker。此時 WorkManager 可以向 OS 提供信號,表明應讓程序持續運作 盡可能避免出現這種情況這些工作站的執行時間超過 10 個 分鐘。這項新功能的用途範例包括大量上傳或 下載 (無法進行分塊)、在本機運算機器學習模型,或是工作 對應用程式使用者來說很重要。
以技術實際上來說,WorkManager 會代替您執行前景服務,以執行 WorkRequest
,並顯示可設定的通知。
ListenableWorker
現在支援 setForegroundAsync()
API,並 CoroutineWorker
支援停權的 setForeground()
API。開發人員可藉由這些 API 指定 WorkRequest
是「重要」 (從使用者的角度來看) 或「長時間執行」。
從 2.3.0-alpha03
開始,WorkManager 也可讓您建立 PendingIntent
,其可用於取消工作站,而無須使用 createCancelPendingIntent()
API 註冊新的 Android 元件。此方法在與 setForegroundAsync()
或 setForeground()
API 搭配使用時特別實用,可用於新增通知動作來取消 Worker
。
建立及管理長時間執行的工作站
視您是否需要使用 Kotlin 或 Java 編寫程式碼而定,做法會略有不同。
Kotlin
Kotlin 開發人員應使用 CoroutineWorker
。除了使用 setForegroundAsync()
,您也可以使用這個方法的暫停版本 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
使用 ListenableWorker
或 Worker
的開發人員可以呼叫 setForegroundAsync()
API,此 API 會回傳 ListenableFuture<Void>
。您也可以呼叫 setForegroundAsync()
來更新進行中的 Notification
。
下列為長時間執行下載檔案的工作站範例。此工作站會追蹤進度,以更新正在進行的 Notification
,並顯示下載進度。
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
}
}
將前景服務類型新增至長時間執行的工作站
如果您的應用程式指定 Android 14 (API 級別 34) 以上版本,您必須指定
前景服務類型。
如果應用程式指定 Android 10 (API 級別 29) 以上版本,且包含
需要存取位置資訊的長時間執行工作站,表示工作站
使用前景服務類型 location
。
如果應用程式指定 Android 11 (API 級別 30) 以上版本為目標
當中含有長時間執行的 worker,需要存取相機或麥克風;
宣告 camera
或 microphone
前景
服務類型
如要新增這些前景服務類型,請完成下列各節說明的步驟。
在應用程式資訊清單中宣告前景服務類型
在應用程式的資訊清單中宣告工作站的前景服務類型。在下列範例中,工作站需要存取位置資訊和麥克風:
AndroidManifest.xml
<service android:name="androidx.work.impl.foreground.SystemForegroundService" android:foregroundServiceType="location|microphone" tools:node="merge" />
指定執行階段的前景服務類型
呼叫 setForeground()
或 setForegroundAsync()
時,請務必指定
前景服務類型。
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); }