Android NDK开发详解后台任务之支持长时间运行的 worker
WorkManager 为长时间运行的 worker 提供内置支持。在这种情况下,WorkManager 可以向操作系统提供一个信号,指示在此项工作执行期间应尽可能让进程保持活跃状态。这些 worker 可以运行超过 10 分钟。这一新功能的示例用例包括批量上传或下载(不可分块)、在本地进行的机器学习模型处理,或者对应用的用户很重要的任务。
在后台,WorkManager 会代表您管理和运行前台服务以执行 WorkRequest,同时还会显示可配置的通知。
ListenableWorker 现在支持 setForegroundAsync() API,而 CoroutineWorker 则支持挂起 setForeground() API。这些 API 允许开发者指定此 WorkRequest 是“重要的”(从用户的角度来看)或“长时间运行的”任务。
从 2.3.0-alpha03 开始,WorkManager 还允许您创建 PendingIntent,此 Intent 可用于取消 worker,而不必使用 createCancelPendingIntent() API 注册新的 Android 组件。此方法与 setForegroundAsync() 或 setForeground() API 一起使用时特别有用,可用于添加一个取消 Worker 的通知操作。
创建和管理长时间运行的 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。您还可以调用 setForegroundAsync() 以更新持续运行的 Notification。
下面是一个简单的示例,说明了一个下载文件的长时间运行 worker。此 worker 会跟踪进度,以更新持续显示下载进度的 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
}
}
将前台服务类型添加到长时间运行的 worker
注意 :根据应用的目标 API 级别以及服务正在执行的工作类型,您可能需要声明前台服务类型。无论您以哪个 Android 版本为目标平台,声明前台服务类型都是最佳做法。如需了解详情,请参阅前台服务文档。
如果您的应用以 Android 14(API 级别 34)或更高版本为目标平台,您必须为所有长时间运行的 worker 指定前台服务类型。如果您的应用以 Android 10(API 级别 29)或更高版本为目标平台,且包含需要位置信息访问权限的长时间运行的 worker,请指明该 worker 使用前台服务类型 location。
如果您的应用以 Android 11(API 级别 30)或更高版本为目标平台,且包含需要访问摄像头或麦克风的长时间运行的 worker,请分别声明 camera 或 microphone 前台服务类型。
如需添加这些前台服务类型,请完成以下各部分中介绍的步骤。
在应用清单中声明前台服务类型
在应用的清单中声明 worker 的前台服务类型。在以下示例中,worker 需要位置信息和麦克风访问权限:
AndroidManifest.xml
<service
android:name="androidx.work.impl.foreground.SystemForegroundService"
android:foregroundServiceType="location|microphone"
tools:node="merge" />
注意 :清单合并工具将上述代码段中的 元素声明与 WorkManager 的 SystemForegroundService 在其自己的清单中定义的声明合并在一起。
在运行时指定前台服务类型
调用 setForeground() 或 setForegroundAsync() 时,请务必指定前台服务类型。
注意 :从 Android 14(API 级别 34)开始,当您调用 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);
}